diff --git a/.travis.yml b/.travis.yml index e3fd1fab2..221b04f4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,11 @@ android: - tools - platform-tools - build-tools-27.0.3 - - android-23 + - android-26 + licenses: + - android-sdk-preview-license-.+ + - android-sdk-license-.+ + - google-gdk-license-.+ before_script: - export TERM=dumb - curl -L http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O @@ -23,18 +27,18 @@ before_script: - export PATH=$PATH:${ANDROID_NDK_HOME} - cd xwords4/android/ before_install: +- yes | sdkmanager "platforms;android-27" - openssl aes-256-cbc -K $encrypted_8436f2891714_key -iv $encrypted_8436f2891714_iv -in id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d - chmod 600 \/tmp\/id_rsa_uploader - sudo apt-get -qq update - sudo apt-get install -y python-lxml imagemagick script: -- "./gradlew -PuseCrashlytics assXw4dDeb" +- "./gradlew -PuseCrashlytics asXw4dDeb" - scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/xw4d/debug/*.apk uploader@eehouse.org:XW4D_UPLOAD notifications: email: recipients: - xwords@eehouse.org - on_success: always on_failure: always diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 5f7a1d111..b10c48bb1 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 132 -def VERSION_NAME = '4.4.136' +def VERSION_CODE_BASE = 133 +def VERSION_NAME = '4.4.137' 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" @@ -24,6 +24,7 @@ if ( FABRIC_API_KEY && hasProperty('useCrashlytics') ) { } repositories { maven { url 'https://maven.fabric.io/public' } + google() } android { @@ -32,7 +33,7 @@ android { buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 8 - targetSdkVersion 23 + targetSdkVersion 26 versionCode VERSION_CODE_BASE versionName VERSION_NAME } diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml index 6fa3ddb0f..27df1905e 100644 --- a/xwords4/android/app/src/main/AndroidManifest.xml +++ b/xwords4/android/app/src/main/AndroidManifest.xml @@ -43,6 +43,16 @@ android:theme="@style/AppTheme" > + + + + -

CrossWords 4.4.136 release

+

CrossWords 4.4.137 release

-

This release fixes a couple of rare bugs.

+

This release paves the way for play-via-SMS improvements.

Please take @@ -25,9 +25,12 @@

New with this release

    -
  • Flip arrow to match when board flipped
  • -
  • Fix problems when undos arrive at unexpected times
  • -
  • Don't crash when game connects while app's in background
  • +
  • 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.
  • +
  • Include new Catalan translations
  • +
  • Fix bug causing (rarely) unending notifications of wordlist + upgrade

(The full changelog diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java index a73490b3f..5a165bcf1 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java @@ -175,8 +175,7 @@ public class DictBrowseDelegate extends DelegateBase String[] names = { name }; DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names ); m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0], - name, pairs.m_paths[0], - JNIUtilsImpl.get(m_activity) ); + name, pairs.m_paths[0] ); m_browseState = DBUtils.dictsGetOffset( m_activity, name, m_loc ); boolean newState = null == m_browseState; @@ -280,7 +279,7 @@ public class DictBrowseDelegate extends DelegateBase { TextView text = (TextView)view; // null text seems to have generated at least one google play report - if ( null != text ) { + if ( null != text && null != m_browseState ) { int newval = Integer.parseInt( text.getText().toString() ); switch ( parent.getId() ) { case R.id.wordlen_min: diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java index fdab651b8..b614446b2 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java @@ -486,7 +486,6 @@ public class DictLangCache { info = new DictInfo(); if ( XwJNI.dict_getInfo( pairs.m_bytes[0], dal.name, pairs.m_paths[0], - JNIUtilsImpl.get( context ), DictLoc.DOWNLOAD == dal.loc, info ) ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java index 743d4b675..f354b1e29 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java @@ -151,14 +151,16 @@ public class DictUtils { // changes? } - private static void addLogDup( Map map, String path, - DictLoc loc ) + private static void addLogDupIf( Context context, Map map, + String path, File dir, DictLoc loc ) { - String name = removeDictExtn( new File(path).getName() ); - if ( map.containsKey( name ) ) { - Log.d( TAG, "replacing info for %s with from %s", name, loc ); + if ( isDict( context, path, dir ) ) { + String name = removeDictExtn( new File(path).getName() ); + if ( map.containsKey( name ) ) { + Log.d( TAG, "replacing info for %s with from %s", name, loc ); + } + map.put( name, new DictAndLoc( name, loc ) ); } - map.put( name, new DictAndLoc( name, loc ) ); } private static void tryDir( Context context, File dir, boolean strict, @@ -168,9 +170,7 @@ public class DictUtils { String[] list = dir.list(); if ( null != list ) { for ( String file : list ) { - if ( isDict( context, file, strict? dir : null ) ) { - addLogDup( map, file, loc ); - } + addLogDupIf( context, map, file, strict? dir : null, loc ); } } } @@ -190,15 +190,11 @@ public class DictUtils { Map map = new HashMap<>(); for ( String file : getAssets( context ) ) { - if ( isDict( context, file, null ) ) { - addLogDup( map, file, DictLoc.BUILT_IN ); - } + addLogDupIf( context, map, file, null, DictLoc.BUILT_IN ); } for ( String file : context.fileList() ) { - if ( isDict( context, file, null ) ) { - addLogDup( map, file, DictLoc.INTERNAL ); - } + addLogDupIf( context, map, file, null, DictLoc.INTERNAL ); } tryDir( context, getSDDir( context ), false, DictLoc.EXTERNAL, map ); @@ -578,7 +574,7 @@ public class DictUtils { if ( ok && null != dir ) { String fullPath = new File( dir, file ).getPath(); ok = XwJNI.dict_getInfo( null, removeDictExtn( file ), fullPath, - JNIUtilsImpl.get(context), true, null ); + true, null ); } return ok; } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java index f7f6c1577..858787047 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java @@ -172,7 +172,7 @@ public class DwnldDelegate extends ListDelegateBase { callListener( m_uri, true ); } else if ( null != m_appFile ) { // launch the installer - Intent intent = Utils.makeInstallIntent( m_appFile ); + Intent intent = Utils.makeInstallIntent( m_activity, m_appFile ); startActivity( intent ); } else { // we failed at something.... @@ -473,5 +473,4 @@ public class DwnldDelegate extends ListDelegateBase { intent.putExtra( APK_EXTRA, url ); return intent; } - } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java index 97e6b8b70..56b71011b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java @@ -25,8 +25,8 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.text.Html; import android.text.TextUtils; import android.view.Display; @@ -131,8 +131,8 @@ public class GameUtils { gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths, gi.langName( context ), (UtilCtxt)null, - JNIUtilsImpl.get( context ), (DrawCtx)null, - CommonPrefs.get( context ), (TransportProcs)null ); + (DrawCtx)null, CommonPrefs.get( context ), + (TransportProcs)null ); if ( juggle ) { gi.juggle(); @@ -372,15 +372,13 @@ public class GameUtils { gamePtr = XwJNI.initFromStream( rowid, stream, gi, dictNames, pairs.m_bytes, pairs.m_paths, langName, util, - JNIUtilsImpl.get( context ), null, CommonPrefs.get(context), tp ); if ( null == gamePtr ) { gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths, - langName, (UtilCtxt)null, - JNIUtilsImpl.get(context), null, + langName, (UtilCtxt)null, null, CommonPrefs.get(context), null ); } } @@ -485,12 +483,12 @@ public class GameUtils { } if ( force ) { - new ResendTask( context, filter, proc ).execute(); - System.arraycopy( sendTimes, 0, /* src */ sendTimes, 1, /* dest */ sendTimes.length - 1 ); sendTimes[0] = now; + + new Resender( context, filter, proc ).start(); } } @@ -914,6 +912,7 @@ public class GameUtils { m_gotMsg = false; m_gameOver = false; } + @Override public void showChat( String msg, int fromIndx, String fromName, int tsSeconds ) { @@ -923,11 +922,14 @@ public class GameUtils { m_chat = msg; m_ts = tsSeconds; } + + @Override public void turnChanged( int newTurn ) { m_gotMsg = true; } + @Override public void notifyGameOver() { m_gameOver = true; @@ -1024,8 +1026,7 @@ public class GameUtils { XwJNI.initFromStream( rowid, stream, gi, dictNames, pairs.m_bytes, pairs.m_paths, gi.langName( context ), null, - JNIUtilsImpl.get(context), null, - CommonPrefs.get( context ), null ); + null, CommonPrefs.get( context ), null ); // second time required as game_makeFromStream can overwrite gi.replaceDicts( context, newDict ); @@ -1075,8 +1076,7 @@ public class GameUtils { new CurGameInfo(context), dictNames, pairs.m_bytes, pairs.m_paths, langName, - null, JNIUtilsImpl.get(context), - null, cp, null ); + null, null, cp, null ); madeGame = null != gamePtr; } @@ -1084,8 +1084,7 @@ public class GameUtils { Assert.assertNull( gamePtr ); gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths, langName, util, - JNIUtilsImpl.get(context), (DrawCtx)null, - cp, sink ); + (DrawCtx)null, cp, sink ); } if ( null != car ) { @@ -1255,23 +1254,27 @@ public class GameUtils { return result; } - private static class ResendTask extends AsyncTask { + private static class Resender extends Thread { private Context m_context; private ResendDoneProc m_doneProc; private CommsConnType m_filter; - private int m_nSent = 0; + private Handler m_handler; - public ResendTask( Context context, CommsConnType filter, - ResendDoneProc proc ) + public Resender( Context context, CommsConnType filter, + ResendDoneProc proc ) { m_context = context; m_filter = filter; m_doneProc = proc; + if ( null != proc ) { + m_handler = new Handler(); + } } @Override - protected Void doInBackground( Void... unused ) + public void run() { + int nSentTotal = 0; HashMap games = DBUtils.getGamesWithSendsPending( m_context ); @@ -1296,11 +1299,11 @@ public class GameUtils { int nSent = XwJNI.comms_resendAll( gamePtr, true, m_filter, false ); gamePtr.release(); - Log.d( TAG, "ResendTask.doInBackground(): sent %d " + Log.d( TAG, "Resender.doInBackground(): sent %d " + "messages for rowid %d", nSent, rowid ); - m_nSent += sink.numSent(); + nSentTotal += sink.numSent(); } else { - Log.d( TAG, "ResendTask.doInBackground(): loadMakeGame()" + Log.d( TAG, "Resender.doInBackground(): loadMakeGame()" + " failed for rowid %d", rowid ); } lock.unlock(); @@ -1311,19 +1314,21 @@ public class GameUtils { false, false ); jniThread.release(); } else { - Log.w( TAG, "ResendTask.doInBackground: unable to unlock %d", + Log.w( TAG, "Resender.doInBackground: unable to unlock %d", rowid ); } } } - return null; - } - @Override - protected void onPostExecute( Void unused ) - { if ( null != m_doneProc ) { - m_doneProc.onResendDone( m_context, m_nSent ); + final int fSentTotal = nSentTotal; + m_handler + .post( new Runnable() { + @Override + public void run() { + m_doneProc.onResendDone( m_context, fSentTotal ); + } + }); } } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 9296d70d6..7838c07e3 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -1451,8 +1451,6 @@ public class GamesListDelegate extends ListDelegateBase showItemsIf( ONEGAME_ITEMS, menu, 1 == nGamesSelected ); showItemsIf( ONEGROUP_ITEMS, menu, 1 == nGroupsSelected ); - // check for updates only serves release builds, so don't offer in - // DEBUG case boolean enable = showDbg && nothingSelected && UpdateCheckReceiver.haveToCheck( m_activity ); Utils.setItemVisible( menu, R.id.games_menu_checkupdates, enable ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java index b4ce7a02f..cadb8f6af 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java @@ -34,7 +34,7 @@ import org.eehouse.android.xw4.MultiService.DictFetchOwner; 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.UtilCtxt.DevIDType; +import org.eehouse.android.xw4.jni.DUtilCtxt.DevIDType; import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.loc.LocUtils; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java index 30b35be4f..17cf0f21e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java @@ -1,6 +1,6 @@ -/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */ +/* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */ /* - * Copyright 2010 by Eric House (xwords@eehouse.org). All rights + * Copyright 2010 - 2018 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -40,6 +40,7 @@ import org.eehouse.android.xw4.MultiService.DictFetchOwner; 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.loc.LocUtils; import java.io.ByteArrayInputStream; @@ -55,19 +56,11 @@ import java.util.Set; public class SMSService extends XWService { private static final String TAG = SMSService.class.getSimpleName(); - private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a "; - private static final int MAX_SMS_LEN = 140; // ??? differs by network - private static final int KITKAT = 19; - private static final String MSG_SENT = "MSG_SENT"; private static final String MSG_DELIVERED = "MSG_DELIVERED"; - private static final int SMS_PROTO_VERSION_ORIG = 0; private static final int SMS_PROTO_VERSION_WITHPORT = 1; private static final int SMS_PROTO_VERSION = SMS_PROTO_VERSION_WITHPORT; - private static final int MAX_LEN_TEXT = 100; - private static final int MAX_LEN_BINARY = 100; - private static final int MAX_MSG_COUNT = 16; // 1.6K enough? Should be.... private enum SMSAction { _NONE, INVITE, SEND, @@ -75,6 +68,7 @@ public class SMSService extends XWService { ADDED_MISSING, STOP_SELF, HANDLEDATA, + RESEND, }; private static final String CMD_STR = "CMD"; @@ -98,8 +92,6 @@ public class SMSService extends XWService { private int m_nReceived = 0; private static int s_nSent = 0; - private static Map> s_partialMsgs - = new HashMap>(); private static Set s_sentDied = new HashSet(); public static class SMSPhoneInfo { @@ -366,6 +358,9 @@ public class SMSService extends XWService { phone = intent.getStringExtra( PHONE ); sendDiedPacket( phone, gameID ); break; + case RESEND: + phone = intent.getStringExtra( PHONE ); + resendFor( phone, null, false ); } } @@ -434,18 +429,18 @@ public class SMSService extends XWService { dos.writeInt( gameID ); dos.write( bytes, 0, bytes.length ); dos.flush(); - if ( send( SMS_CMD.DATA, bas.toByteArray(), phone ) ) { - nSent = bytes.length; - } + send( SMS_CMD.DATA, bas.toByteArray(), phone ); + nSent = bytes.length; } catch ( java.io.IOException ioe ) { Log.ex( TAG, ioe ); } return nSent; } - private boolean send( SMS_CMD cmd, byte[] bytes, String phone ) + private void send( SMS_CMD cmd, byte[] bytes, String phone ) throws java.io.IOException { + 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 ); @@ -457,42 +452,44 @@ public class SMSService extends XWService { dos.flush(); byte[] data = bas.toByteArray(); - byte[][] msgs = breakAndEncode( data ); - boolean result = null != msgs && sendBuffers( msgs, phone ); - return result; + + boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this ); + boolean forceNow = !newSMSEnabled; // || cmd == SMS_CMD.INVITE; + resendFor( phone, data, forceNow ); } - private byte[][] breakAndEncode( byte msg[] ) throws java.io.IOException + private void resendFor( String phone, byte[] data, boolean forceNow ) { - byte[][] result = null; - int count = (msg.length + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY; - if ( count < MAX_MSG_COUNT ) { - result = new byte[count][]; - int msgID = ++s_nSent % 0x000000FF; - - int start = 0; - int end = 0; - for ( int ii = 0; ii < count; ++ii ) { - int len = msg.length - end; - if ( len > MAX_LEN_BINARY ) { - len = MAX_LEN_BINARY; - } - end += len; - byte[] part = new byte[4 + len]; - part[0] = (byte)SMS_PROTO_VERSION; - part[1] = (byte)msgID; - part[2] = (byte)ii; - part[3] = (byte)count; - System.arraycopy( msg, start, part, 4, len ); - - result[ii] = part; - start = end; - } - } else { - Log.w( TAG, "breakAndEncode(): msg count %d too large; dropping", - count ); + int[] waitSecs = { 0 }; + byte[][] msgs = XwJNI.smsproto_prepOutbound( data, phone, forceNow, waitSecs ); + if ( null != msgs ) { + sendBuffers( msgs, phone ); } - return result; + if ( waitSecs[0] > 0 ) { + Assert.assertFalse( forceNow ); + postResend( phone, waitSecs[0] ); + } + } + + private void postResend( final String phone, final int waitSecs ) + { + Log.d( TAG, "postResend" ); + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep( waitSecs * 1000 ); + + Log.d( TAG, "postResend.run()" ); + Intent intent = getIntentTo( SMSService.this, + SMSAction.RESEND ); + intent.putExtra( PHONE, phone ); + startService( intent ); + } catch ( InterruptedException ie ) { + Log.e( TAG, ie.getMessage() ); + } + } + } ).start(); } private void receive( SMS_CMD cmd, byte[] data, String phone ) @@ -534,51 +531,18 @@ public class SMSService extends XWService { private void receiveBuffer( byte[] buffer, String senderPhone ) { - byte proto = buffer[0]; - int id = buffer[1]; - int index = buffer[2]; - int count = buffer[3]; - byte[] rest = new byte[buffer.length - 4]; - System.arraycopy( buffer, 4, rest, 0, rest.length ); - if ( tryAssemble( senderPhone, id, index, count, rest ) ) { - postEvent( MultiEvent.SMS_RECEIVE_OK ); - } else { - // Will see this when don't have SMS permission - Log.w( TAG, "receiveBuffer(): bogus message from phone %s", - senderPhone ); - } - } - - private boolean tryAssemble( String senderPhone, int id, int index, - int count, byte[] msg ) - { - boolean success = true; - if ( index == 0 && count == 1 ) { // most common case - success = disAssemble( senderPhone, msg ); - } else if ( count > 0 && count < MAX_MSG_COUNT && index < count ) { - // required? Should always be in main thread. - synchronized( s_partialMsgs ) { - HashMap perPhone = - s_partialMsgs.get( senderPhone ); - if ( null == perPhone ) { - perPhone = new HashMap (); - s_partialMsgs.put( senderPhone, perPhone ); - } - MsgStore store = perPhone.get( id ); - if ( null == store ) { - store = new MsgStore( id, count, false ); - perPhone.put( id, store ); - } - - if ( store.add( index, msg ).isComplete() ) { - success = disAssemble( senderPhone, store.messageData() ); - perPhone.remove( id ); + byte[][] msgs = XwJNI.smsproto_prepInbound( buffer, senderPhone ); + if ( null != msgs ) { + for ( byte[] msg : msgs ) { + if ( !disAssemble( senderPhone, msg ) ) { + Log.e( TAG, "failed on message from %s", senderPhone ); } } + postEvent( MultiEvent.SMS_RECEIVE_OK ); } else { - success = false; + Log.w( TAG, "receiveBuffer(): bogus or incomplete message from phone %s", + senderPhone ); } - return success; } private boolean disAssemble( String senderPhone, byte[] fullMsg ) @@ -685,7 +649,7 @@ public class SMSService extends XWService { Log.i( TAG, "dropping because SMS disabled" ); } - if ( showToasts( this ) && success && (0 == (s_nSent % 5)) ) { + if ( showToasts( this ) && success && (0 == (++s_nSent % 5)) ) { DbgUtils.showf( this, "Sent msg %d", s_nSent ); } @@ -777,70 +741,4 @@ public class SMSService extends XWService { return sendPacket( addr.sms_phone, gameID, buf ); } } - - private class MsgStore { - String[] m_msgsText; - byte[][] m_msgsData; - int m_msgID; - int m_haveCount; - int m_fullLength; - - public MsgStore( int id, int count, boolean usingStrings ) - { - m_msgID = id; - if ( usingStrings ) { - m_msgsText = new String[count]; - } else { - m_msgsData = new byte[count][]; - } - m_fullLength = 0; - } - - public MsgStore add( int index, String msg ) - { - if ( null == m_msgsText[index] ) { - ++m_haveCount; - m_fullLength += msg.length(); - } - m_msgsText[index] = msg; - return this; - } - - public MsgStore add( int index, byte[] msg ) - { - if ( null == m_msgsData[index] ) { - ++m_haveCount; - m_fullLength += msg.length; - } - m_msgsData[index] = msg; - return this; - } - - public boolean isComplete() - { - int count = null != m_msgsText ? m_msgsText.length : m_msgsData.length; - boolean complete = count == m_haveCount; - return complete; - } - - public String messageText() - { - StringBuffer sb = new StringBuffer(m_fullLength); - for ( String msg : m_msgsText ) { - sb.append( msg ); - } - return sb.toString(); - } - - public byte[] messageData() - { - byte[] result = new byte[m_fullLength]; - int indx = 0; - for ( byte[] msg : m_msgsData ) { - System.arraycopy( msg, 0, result, indx, msg.length ); - indx += msg.length; - } - return result; - } - } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java index 0a599d6e9..300a96ddf 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java @@ -256,7 +256,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver { m_dals = dals; } - @Override protected String doInBackground( Void... unused ) + @Override + protected String doInBackground( Void... unused ) { HttpURLConnection conn = NetUtils.makeHttpUpdateConn( m_context, "getUpdates" ); @@ -267,7 +268,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver { return json; } - @Override protected void onPostExecute( String json ) + @Override + protected void onPostExecute( String json ) { if ( null != json ) { makeNotificationsIf( json, m_params ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java index 4c073a829..c5a02bf0e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java @@ -34,17 +34,16 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.content.res.Configuration; -import android.text.ClipboardManager; - import android.database.Cursor; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.provider.ContactsContract.PhoneLookup; import android.support.v4.app.NotificationCompat; +import android.support.v4.content.FileProvider; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; - +import android.text.ClipboardManager; import android.util.Base64; import android.view.Menu; import android.view.MenuItem; @@ -486,13 +485,16 @@ public class Utils { return null == s_appVersion? 0 : s_appVersion; } - public static Intent makeInstallIntent( File file ) + public static Intent makeInstallIntent( Context context, File file ) { - String withScheme = "file://" + file.getPath(); - Uri uri = Uri.parse( withScheme ); + Uri uri = FileProvider + .getUriForFile( context, + BuildConfig.APPLICATION_ID + ".provider", + file ); Intent intent = new Intent( Intent.ACTION_VIEW ); intent.setDataAndType( uri, XWConstants.APK_TYPE ); - intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); + intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_GRANT_READ_URI_PERMISSION ); return intent; } @@ -501,7 +503,7 @@ public class Utils { { boolean result = false; PackageManager pm = context.getPackageManager(); - Intent intent = makeInstallIntent( path ); + Intent intent = makeInstallIntent( context, path ); List doers = pm.queryIntentActivities( intent, PackageManager.MATCH_DEFAULT_ONLY ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java index 5bad7482e..4de7cb416 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java @@ -82,6 +82,11 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_enable_sms_toself, false ); } + public static boolean getSMSProtoEnabled( Context context ) + { + return getPrefsBoolean( context, R.string.key_enable_smsproto, false ); + } + public static boolean getHideNewgameButtons( Context context ) { return getPrefsBoolean( context, R.string.key_hide_newgames, diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java new file mode 100644 index 000000000..8192636f7 --- /dev/null +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java @@ -0,0 +1,238 @@ +/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */ +/* + * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4.jni; + +import android.content.Context; +import android.telephony.PhoneNumberUtils; + +import junit.framework.Assert; + +import org.eehouse.android.xw4.XWApp; +import org.eehouse.android.xw4.DBUtils; +import org.eehouse.android.xw4.DevID; +import org.eehouse.android.xw4.R; +import org.eehouse.android.xw4.Log; +import org.eehouse.android.xw4.loc.LocUtils; + +public class DUtilCtxt { + private static final String TAG = DUtilCtxt.class.getSimpleName(); + + private Context m_context; + + public DUtilCtxt() { + m_context = XWApp.getContext(); + } + + // Possible values for typ[0], these must match enum in xwrelay.sh + public enum DevIDType { ID_TYPE_NONE + , ID_TYPE_RELAY + , ID_TYPE_LINUX + , ID_TYPE_ANDROID_GCM + , ID_TYPE_ANDROID_OTHER + , ID_TYPE_ANON + } + + public String getDevID( /*out*/ byte[] typa ) + { + DevIDType typ = DevIDType.ID_TYPE_NONE; + String result = DevID.getRelayDevID( m_context ); + if ( null != result ) { + typ = DevIDType.ID_TYPE_RELAY; + } else { + result = DevID.getGCMDevID( m_context ); + if ( result.equals("") ) { + result = null; + } else { + typ = DevIDType.ID_TYPE_ANDROID_GCM; + } + } + typa[0] = (byte)typ.ordinal(); + return result; + } + + public void deviceRegistered( DevIDType devIDType, String idRelay ) + { + switch ( devIDType ) { + case ID_TYPE_RELAY: + DevID.setRelayDevID( m_context, idRelay ); + break; + case ID_TYPE_NONE: + DevID.clearRelayDevID( m_context ); + break; + default: + Assert.fail(); + break; + } + } + + static final int STRD_ROBOT_TRADED = 1; + static final int STR_ROBOT_MOVED = 2; + static final int STRS_VALUES_HEADER = 3; + static final int STRD_REMAINING_TILES_ADD = 4; + static final int STRD_UNUSED_TILES_SUB = 5; + static final int STRS_REMOTE_MOVED = 6; + static final int STRD_TIME_PENALTY_SUB = 7; + static final int STR_PASS = 8; + static final int STRS_MOVE_ACROSS = 9; + static final int STRS_MOVE_DOWN = 10; + static final int STRS_TRAY_AT_START = 11; + static final int STRSS_TRADED_FOR = 12; + static final int STR_PHONY_REJECTED = 13; + static final int STRD_CUMULATIVE_SCORE = 14; + static final int STRS_NEW_TILES = 15; + static final int STR_COMMIT_CONFIRM = 16; + static final int STR_BONUS_ALL = 17; + static final int STRD_TURN_SCORE = 18; + static final int STRD_REMAINS_HEADER = 19; + static final int STRD_REMAINS_EXPL = 20; + static final int STRSD_RESIGNED = 21; + static final int STRSD_WINNER = 22; + static final int STRDSD_PLACER = 23; + + + public String getUserString( int stringCode ) + { + Log.d( TAG, "getUserString(%d)", stringCode ); + int id = 0; + switch( stringCode ) { + case STR_ROBOT_MOVED: + id = R.string.str_robot_moved_fmt; + break; + case STRS_VALUES_HEADER: + id = R.string.strs_values_header_fmt; + break; + case STRD_REMAINING_TILES_ADD: + id = R.string.strd_remaining_tiles_add_fmt; + break; + case STRD_UNUSED_TILES_SUB: + id = R.string.strd_unused_tiles_sub_fmt; + break; + case STRS_REMOTE_MOVED: + id = R.string.str_remote_moved_fmt; + break; + case STRD_TIME_PENALTY_SUB: + id = R.string.strd_time_penalty_sub_fmt; + break; + case STR_PASS: + id = R.string.str_pass; + break; + case STRS_MOVE_ACROSS: + id = R.string.strs_move_across_fmt; + break; + case STRS_MOVE_DOWN: + id = R.string.strs_move_down_fmt; + break; + case STRS_TRAY_AT_START: + id = R.string.strs_tray_at_start_fmt; + break; + case STRSS_TRADED_FOR: + id = R.string.strss_traded_for_fmt; + break; + case STR_PHONY_REJECTED: + id = R.string.str_phony_rejected; + break; + case STRD_CUMULATIVE_SCORE: + id = R.string.strd_cumulative_score_fmt; + break; + case STRS_NEW_TILES: + id = R.string.strs_new_tiles_fmt; + break; + case STR_COMMIT_CONFIRM: + id = R.string.str_commit_confirm; + break; + case STR_BONUS_ALL: + id = R.string.str_bonus_all; + break; + case STRD_TURN_SCORE: + id = R.string.strd_turn_score_fmt; + break; + case STRSD_RESIGNED: + id = R.string.str_resigned_fmt; + break; + case STRSD_WINNER: + id = R.string.str_winner_fmt; + break; + case STRDSD_PLACER: + id = R.string.str_placer_fmt; + break; + + default: + Log.w( TAG, "no such stringCode: %d", stringCode ); + } + + String result = (0 == id) ? "" : LocUtils.getString( m_context, id ); + Log.d( TAG, "getUserString() => %s", result ); + return result; + } + + public String getUserQuantityString( int stringCode, int quantity ) + { + int pluralsId = 0; + switch ( stringCode ) { + case STRD_ROBOT_TRADED: + pluralsId = R.plurals.strd_robot_traded_fmt; + break; + case STRD_REMAINS_HEADER: + pluralsId = R.plurals.strd_remains_header_fmt; + break; + case STRD_REMAINS_EXPL: + pluralsId = R.plurals.strd_remains_expl_fmt; + break; + } + + String result = ""; + if ( 0 != pluralsId ) { + result = LocUtils.getQuantityString( m_context, pluralsId, quantity ); + } + return result; + } + + public boolean phoneNumbersSame( String num1, String num2 ) + { + boolean same = PhoneNumberUtils.compare( m_context, num1, num2 ); + return same; + } + + public void store( String key, byte[] data ) + { + Log.d( TAG, "store(key=%s)", key ); + + if ( null == data ) { + } else { + DBUtils.setBytesFor( m_context, key, data ); + } + } + + public byte[] load( String key ) + { + byte[] result = null; + int resultLen = 0; + Log.d( TAG, "load(key=%s)", key ); + + result = DBUtils.getBytesFor( m_context, key ); + if ( result != null ) { + resultLen = result.length; + } + + Log.d( TAG, "load(%s) returning %d bytes", key, resultLen ); + return result; + } +} diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java index 28bf81107..d6c9a6c37 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java @@ -212,7 +212,6 @@ public class JNIThread extends Thread { } CommonPrefs cp = CommonPrefs.get( context ); - JNIUtils jniUtils = JNIUtilsImpl.get( context ); // Assert.assertNull( m_jniGamePtr ); // fired!! if ( null != m_jniGamePtr ) { @@ -224,15 +223,13 @@ public class JNIThread extends Thread { dictNames, pairs.m_bytes, pairs.m_paths, m_gi.langName( m_context ), - utils, jniUtils, - null, cp, m_xport ); + utils, null, cp, m_xport ); } if ( null == m_jniGamePtr ) { m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes, pairs.m_paths, m_gi.langName(m_context), - utils, jniUtils, null, cp, - m_xport ); + utils, null, cp, m_xport ); } Assert.assertNotNull( m_jniGamePtr ); m_lastSavedState = Arrays.hashCode( stream ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java index 05506184c..79743be24 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java @@ -25,6 +25,7 @@ import android.content.Context; import junit.framework.Assert; import org.eehouse.android.xw4.DBUtils; +import org.eehouse.android.xw4.XWApp; import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.Utils; @@ -41,14 +42,13 @@ public class JNIUtilsImpl implements JNIUtils { private static JNIUtilsImpl s_impl = null; private Context m_context; - private JNIUtilsImpl(){} + private JNIUtilsImpl(Context context) { m_context = context; } - public static JNIUtils get( Context context ) + public static synchronized JNIUtils get() { if ( null == s_impl ) { - s_impl = new JNIUtilsImpl(); + s_impl = new JNIUtilsImpl( XWApp.getContext() ); } - s_impl.m_context = context; return s_impl; } @@ -133,24 +133,28 @@ public class JNIUtilsImpl implements JNIUtils { public String getMD5SumFor( byte[] bytes ) { - byte[] digest = null; - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] buf = new byte[128]; - int nLeft = bytes.length; - int offset = 0; - while ( 0 < nLeft ) { - int len = Math.min( buf.length, nLeft ); - System.arraycopy( bytes, offset, buf, 0, len ); - md.update( buf, 0, len ); - nLeft -= len; - offset += len; + String result = null; + if ( bytes != null ) { + byte[] digest = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buf = new byte[128]; + int nLeft = bytes.length; + int offset = 0; + while ( 0 < nLeft ) { + int len = Math.min( buf.length, nLeft ); + System.arraycopy( bytes, offset, buf, 0, len ); + md.update( buf, 0, len ); + nLeft -= len; + offset += len; + } + digest = md.digest(); + } catch ( java.security.NoSuchAlgorithmException nsae ) { + Log.ex( TAG, nsae ); } - digest = md.digest(); - } catch ( java.security.NoSuchAlgorithmException nsae ) { - Log.ex( TAG, nsae ); + result = Utils.digestToString( digest ); } - return Utils.digestToString( digest ); + return result; } public String getMD5SumFor( String dictName, byte[] bytes ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java index cc34891be..8812323e6 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -60,50 +60,10 @@ public interface UtilCtxt { void remSelected(); void setIsServer( boolean isServer ); - // Possible values for typ[0], these must match enum in xwrelay.sh - public enum DevIDType { ID_TYPE_NONE - , ID_TYPE_RELAY - , ID_TYPE_LINUX - , ID_TYPE_ANDROID_GCM - , ID_TYPE_ANDROID_OTHER - , ID_TYPE_ANON - } - - String getDevID( /*out*/ byte[] typ ); - void deviceRegistered( DevIDType devIDType, String idRelay ); - void bonusSquareHeld( int bonus ); void playerScoreHeld( int player ); void cellSquareHeld( String words ); - static final int STRD_ROBOT_TRADED = 1; - static final int STR_ROBOT_MOVED = 2; - static final int STRS_VALUES_HEADER = 3; - static final int STRD_REMAINING_TILES_ADD = 4; - static final int STRD_UNUSED_TILES_SUB = 5; - static final int STRS_REMOTE_MOVED = 6; - static final int STRD_TIME_PENALTY_SUB = 7; - static final int STR_PASS = 8; - static final int STRS_MOVE_ACROSS = 9; - static final int STRS_MOVE_DOWN = 10; - static final int STRS_TRAY_AT_START = 11; - static final int STRSS_TRADED_FOR = 12; - static final int STR_PHONY_REJECTED = 13; - static final int STRD_CUMULATIVE_SCORE = 14; - static final int STRS_NEW_TILES = 15; - static final int STR_COMMIT_CONFIRM = 16; - static final int STR_BONUS_ALL = 17; - static final int STRD_TURN_SCORE = 18; - static final int STRD_REMAINS_HEADER = 19; - static final int STRD_REMAINS_EXPL = 20; - static final int STRSD_RESIGNED = 21; - static final int STRSD_WINNER = 22; - static final int STRDSD_PLACER = 23; - - - String getUserString( int stringCode ); - String getUserQuantityString( int stringCode, int quantity ); - void notifyMove( String query ); void notifyTrade( String[] tiles ); @@ -146,6 +106,4 @@ public interface UtilCtxt { boolean turnLost ); void showChat( String msg, int fromIndx, String fromName, int tsSeconds ); - - boolean phoneNumbersSame( String num1, String num2 ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index d18821342..6b3883086 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -21,16 +21,11 @@ package org.eehouse.android.xw4.jni; import android.content.Context; -import android.telephony.PhoneNumberUtils; import junit.framework.Assert; -import org.eehouse.android.xw4.DevID; import org.eehouse.android.xw4.Log; -import org.eehouse.android.xw4.R; -import org.eehouse.android.xw4.XWApp; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet; -import org.eehouse.android.xw4.loc.LocUtils; public class UtilCtxtImpl implements UtilCtxt { private static final String TAG = UtilCtxtImpl.class.getSimpleName(); @@ -44,222 +39,114 @@ public class UtilCtxtImpl implements UtilCtxt { m_context = context; } + @Override public void requestTime() { subclassOverride( "requestTime" ); } + @Override public void notifyPickTileBlank( int playerNum, int col, int row, String[] texts ) { subclassOverride( "userPickTileBlank" ); } + @Override public void informNeedPickTiles( boolean isInitial, int playerNum, int nToPick, String[] texts, int[] counts ) { subclassOverride( "informNeedPickTiles" ); } + @Override public void informNeedPassword( int player, String name ) { subclassOverride( "informNeedPassword" ); } + @Override public void turnChanged( int newTurn ) { subclassOverride( "turnChanged" ); } + @Override public boolean engineProgressCallback() { // subclassOverride( "engineProgressCallback" ); return true; } + @Override public void setTimer( int why, int when, int handle ) { subclassOverride( "setTimer" ); } + @Override public void clearTimer( int why ) { subclassOverride( "clearTimer" ); } + @Override public void remSelected() { subclassOverride( "remSelected" ); } + @Override public void setIsServer( boolean isServer ) { subclassOverride( "setIsServer" ); } - public String getDevID( /*out*/ byte[] typa ) - { - UtilCtxt.DevIDType typ = UtilCtxt.DevIDType.ID_TYPE_NONE; - String result = DevID.getRelayDevID( m_context ); - if ( null != result ) { - typ = UtilCtxt.DevIDType.ID_TYPE_RELAY; - } else { - result = DevID.getGCMDevID( m_context ); - if ( result.equals("") ) { - result = null; - } else { - typ = UtilCtxt.DevIDType.ID_TYPE_ANDROID_GCM; - } - } - typa[0] = (byte)typ.ordinal(); - return result; - } - - public void deviceRegistered( UtilCtxt.DevIDType devIDType, String idRelay ) - { - switch ( devIDType ) { - case ID_TYPE_RELAY: - DevID.setRelayDevID( m_context, idRelay ); - break; - case ID_TYPE_NONE: - DevID.clearRelayDevID( m_context ); - break; - default: - Assert.fail(); - break; - } - } - + @Override public void bonusSquareHeld( int bonus ) { } + @Override public void playerScoreHeld( int player ) { } + @Override public void cellSquareHeld( String words ) { } - public String getUserString( int stringCode ) - { - int id = 0; - switch( stringCode ) { - case UtilCtxt.STR_ROBOT_MOVED: - id = R.string.str_robot_moved_fmt; - break; - case UtilCtxt.STRS_VALUES_HEADER: - id = R.string.strs_values_header_fmt; - break; - case UtilCtxt.STRD_REMAINING_TILES_ADD: - id = R.string.strd_remaining_tiles_add_fmt; - break; - case UtilCtxt.STRD_UNUSED_TILES_SUB: - id = R.string.strd_unused_tiles_sub_fmt; - break; - case UtilCtxt.STRS_REMOTE_MOVED: - id = R.string.str_remote_moved_fmt; - break; - case UtilCtxt.STRD_TIME_PENALTY_SUB: - id = R.string.strd_time_penalty_sub_fmt; - break; - case UtilCtxt.STR_PASS: - id = R.string.str_pass; - break; - case UtilCtxt.STRS_MOVE_ACROSS: - id = R.string.strs_move_across_fmt; - break; - case UtilCtxt.STRS_MOVE_DOWN: - id = R.string.strs_move_down_fmt; - break; - case UtilCtxt.STRS_TRAY_AT_START: - id = R.string.strs_tray_at_start_fmt; - break; - case UtilCtxt.STRSS_TRADED_FOR: - id = R.string.strss_traded_for_fmt; - break; - case UtilCtxt.STR_PHONY_REJECTED: - id = R.string.str_phony_rejected; - break; - case UtilCtxt.STRD_CUMULATIVE_SCORE: - id = R.string.strd_cumulative_score_fmt; - break; - case UtilCtxt.STRS_NEW_TILES: - id = R.string.strs_new_tiles_fmt; - break; - case UtilCtxt.STR_COMMIT_CONFIRM: - id = R.string.str_commit_confirm; - break; - case UtilCtxt.STR_BONUS_ALL: - id = R.string.str_bonus_all; - break; - case UtilCtxt.STRD_TURN_SCORE: - id = R.string.strd_turn_score_fmt; - break; - case UtilCtxt.STRSD_RESIGNED: - id = R.string.str_resigned_fmt; - break; - case UtilCtxt.STRSD_WINNER: - id = R.string.str_winner_fmt; - break; - case UtilCtxt.STRDSD_PLACER: - id = R.string.str_placer_fmt; - break; - - default: - Log.w( TAG, "no such stringCode: %d", stringCode ); - } - - String result = (0 == id) ? "" : LocUtils.getString( m_context, id ); - return result; - } - - public String getUserQuantityString( int stringCode, int quantity ) - { - int pluralsId = 0; - switch ( stringCode ) { - case UtilCtxt.STRD_ROBOT_TRADED: - pluralsId = R.plurals.strd_robot_traded_fmt; - break; - case UtilCtxt.STRD_REMAINS_HEADER: - pluralsId = R.plurals.strd_remains_header_fmt; - break; - case UtilCtxt.STRD_REMAINS_EXPL: - pluralsId = R.plurals.strd_remains_expl_fmt; - break; - } - - String result = ""; - if ( 0 != pluralsId ) { - result = LocUtils.getQuantityString( m_context, pluralsId, quantity ); - } - return result; - } - + @Override public void notifyMove( String query ) { subclassOverride( "notifyMove" ); } + + @Override public void notifyTrade( String[] tiles ) { subclassOverride( "notifyTrade" ); } + @Override public void userError( int id ) { subclassOverride( "userError" ); } + @Override public void informMove( int turn, String expl, String words ) { subclassOverride( "informMove" ); } + @Override public void informUndo() { subclassOverride( "informUndo" ); } + @Override public void informNetDict( int lang, String oldName, String newName, String newSum, CurGameInfo.XWPhoniesChoice phonies ) @@ -267,6 +154,7 @@ public class UtilCtxtImpl implements UtilCtxt { subclassOverride( "informNetDict" ); } + @Override public void informMissing( boolean isServer, CommsConnTypeSet connTypes, int nDevices, int nMissingPlayers ) @@ -276,11 +164,13 @@ public class UtilCtxtImpl implements UtilCtxt { // Probably want to cache the fact that the game over notification // showed up and then display it next time game's opened. + @Override public void notifyGameOver() { subclassOverride( "notifyGameOver" ); } + @Override public void notifyIllegalWords( String dict, String[] words, int turn, boolean turnLost ) { @@ -288,17 +178,12 @@ public class UtilCtxtImpl implements UtilCtxt { } // These need to go into some sort of chat DB, not dropped. + @Override public void showChat( String msg, int fromIndx, String fromName, int tsSeconds ) { subclassOverride( "showChat" ); } - public boolean phoneNumbersSame( String num1, String num2 ) - { - boolean same = PhoneNumberUtils.compare( m_context, num1, num2 ); - return same; - } - private void subclassOverride( String name ) { // DbgUtils.logf( "%s::%s() called", getClass().getName(), name ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java index 3a7c4ba48..a2a701bcd 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java @@ -1,7 +1,7 @@ /* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */ /* - * Copyright 2009-2010 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 @@ -97,7 +97,7 @@ public class XwJNI { private int m_ptr; private XwJNI() { - m_ptr = initGlobals(); + m_ptr = initGlobals( new DUtilCtxt(), JNIUtilsImpl.get() ); } public static void cleanGlobals() @@ -134,23 +134,30 @@ public class XwJNI { public static native boolean timerFired( GamePtr gamePtr, int why, int when, int handle ); - // Stateless methods - public static native byte[] gi_to_stream( CurGameInfo gi ); - public static native void gi_from_stream( CurGameInfo gi, byte[] stream ); + public static byte[] gi_to_stream( CurGameInfo gi ) + { + return gi_to_stream( getJNI().m_ptr, gi ); + } + + public static void gi_from_stream( CurGameInfo gi, byte[] stream ) + { + gi_from_stream( getJNI().m_ptr, gi, stream ); + } + public static byte[] nliToStream( NetLaunchInfo nli ) { nli.freezeAddrs(); - return nli_to_stream( nli ); + return nli_to_stream( getJNI().m_ptr, nli ); } - private static native byte[] nli_to_stream( NetLaunchInfo nli ); + public static NetLaunchInfo nliFromStream( byte[] stream ) { NetLaunchInfo nli = new NetLaunchInfo(); - nli_from_stream( nli, stream ); + nli_from_stream( getJNI().m_ptr, nli, stream ); nli.unfreezeAddrs(); return nli; } - private static native void nli_from_stream( NetLaunchInfo nli, byte[] stream ); + public static native void comms_getInitialAddr( CommsAddrRec addr, String relayHost, int relayPort ); @@ -160,8 +167,7 @@ public class XwJNI { private static GamePtr initJNI( long rowid ) { int seed = Utils.nextRandomInt(); - String tag = String.format( "%d", rowid ); - int ptr = initJNI( getJNI().m_ptr, seed, tag ); + int ptr = initJNI( getJNI().m_ptr, seed ); GamePtr result = 0 == ptr ? null : new GamePtr( ptr, rowid ); return result; } @@ -170,13 +176,13 @@ public class XwJNI { initFromStream( long rowid, byte[] stream, CurGameInfo gi, String[] dictNames, byte[][] dictBytes, String[] dictPaths, String langName, - UtilCtxt util, JNIUtils jniu, DrawCtx draw, + UtilCtxt util, DrawCtx draw, CommonPrefs cp, TransportProcs procs ) { GamePtr gamePtr = initJNI( rowid ); if ( game_makeFromStream( gamePtr, stream, gi, dictNames, dictBytes, - dictPaths, langName, util, jniu, draw, + dictPaths, langName, util, draw, cp, procs ) ) { gamePtr.retain(); } else { @@ -189,12 +195,11 @@ public class XwJNI { public static synchronized GamePtr initNew( CurGameInfo gi, String[] dictNames, byte[][] dictBytes, String[] dictPaths, String langName, UtilCtxt util, - JNIUtils jniu, DrawCtx draw, CommonPrefs cp, - TransportProcs procs ) + DrawCtx draw, CommonPrefs cp, TransportProcs procs ) { GamePtr gamePtr = initJNI( 0 ); game_makeNewGame( gamePtr, gi, dictNames, dictBytes, dictPaths, - langName, util, jniu, draw, cp, procs ); + langName, util, draw, cp, procs ); return gamePtr.retain(); } @@ -211,7 +216,6 @@ public class XwJNI { String[] dictPaths, String langName, UtilCtxt util, - JNIUtils jniu, DrawCtx draw, CommonPrefs cp, TransportProcs procs ); @@ -223,7 +227,6 @@ public class XwJNI { String[] dictPaths, String langName, UtilCtxt util, - JNIUtils jniu, DrawCtx draw, CommonPrefs cp, TransportProcs procs ); @@ -400,6 +403,19 @@ 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 static byte[][] smsproto_prepInbound( byte[] data, String fromPhone ) + { + return smsproto_prepInbound( getJNI().m_ptr, data, fromPhone ); + } + // Dicts public static class DictWrapper { private int m_dictPtr; @@ -439,12 +455,10 @@ public class XwJNI { public static native boolean dict_tilesAreSame( int dict1, int dict2 ); public static native String[] dict_getChars( int dict ); - public static boolean dict_getInfo( byte[] dict, String name, - String path, JNIUtils jniu, + public static boolean dict_getInfo( byte[] dict, String name, String path, boolean check, DictInfo info ) { - return dict_getInfo( getJNI().m_ptr, dict, name, path, jniu, - check, info ); + return dict_getInfo( getJNI().m_ptr, dict, name, path, check, info ); } public static native int dict_getTileValue( int dictPtr, int tile ); @@ -452,9 +466,9 @@ public class XwJNI { // Dict iterator public final static int MAX_COLS_DICT = 15; // from dictiter.h public static int dict_iter_init( byte[] dict, String name, - String path, JNIUtils jniu ) + String path ) { - return dict_iter_init( getJNI().m_ptr, dict, name, path, jniu ); + return dict_iter_init( getJNI().m_ptr, dict, name, path ); } public static native void dict_iter_setMinMax( int closure, int min, int max ); @@ -469,19 +483,32 @@ public class XwJNI { public static native String dict_iter_getDesc( int closure ); // Private methods -- called only here - private static native int initGlobals(); - private static native void cleanGlobals( int globals ); - private static native int initJNI( int jniState, int seed, String tag ); + private static native int initGlobals(DUtilCtxt dutil, JNIUtils jniu); + private static native void cleanGlobals( int jniState ); + private static native byte[] gi_to_stream( int jniState, CurGameInfo gi ); + private static native void gi_from_stream( int jniState, CurGameInfo gi, + byte[] stream ); + private static native byte[] nli_to_stream( int jniState, NetLaunchInfo nli ); + private static native void nli_from_stream( int jniState, NetLaunchInfo nli, + byte[] stream ); + private static native int initJNI( int jniState, int seed ); private static native void envDone( int globals ); private static native void dict_ref( int dictPtr ); private static native void dict_unref( int dictPtr ); private static native boolean dict_getInfo( int jniState, byte[] dict, String name, String path, - JNIUtils jniu, boolean check, + boolean check, DictInfo info ); private static native int dict_iter_init( int jniState, byte[] dict, - String name, String path, - JNIUtils jniu ); + String name, String path ); + + private static native byte[][] smsproto_prepOutbound( int jniState, byte[] buf, + String phone, int nowSeconds, + boolean forceNow, + /*out*/int[] waitSecs ); + + private static native byte[][] smsproto_prepInbound( int jniState, byte[] data, + String fromPhone ); private static native boolean haveEnv( int jniState ); } diff --git a/xwords4/android/app/src/main/res/values/common_rsrc.xml b/xwords4/android/app/src/main/res/values/common_rsrc.xml index 984ff5cdc..b776b8be4 100644 --- a/xwords4/android/app/src/main/res/values/common_rsrc.xml +++ b/xwords4/android/app/src/main/res/values/common_rsrc.xml @@ -125,6 +125,7 @@ key_enable_dup_invite key_enable_nfc_toself key_enable_sms_toself + key_enable_smsproto key_ignore_gcm key_show_gcm key_nag_intervals diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 8a35abac2..7427f169a 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2516,6 +2516,10 @@ Reminder intervals (minutes1,minutes2,...) Enable NFC to self Fake invitation to aid debugging + + Use new/experimental SMS code + (Requires that opponent be using it too) + Short-circuit SMS to self Skip radio when phone numbers same diff --git a/xwords4/android/app/src/main/res/xml/provider_paths.xml b/xwords4/android/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 000000000..ed52e8162 --- /dev/null +++ b/xwords4/android/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml index 8ea8c8a6d..8d6204d6d 100644 --- a/xwords4/android/app/src/main/res/xml/xwprefs.xml +++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml @@ -437,6 +437,11 @@ + vtMgr, globals, 0, NULL ); diff --git a/xwords4/android/jni/andutils.h b/xwords4/android/jni/andutils.h index 22602e32f..1a156dee7 100644 --- a/xwords4/android/jni/andutils.h +++ b/xwords4/android/jni/andutils.h @@ -31,7 +31,7 @@ /* callback for streams */ void and_send_on_close( XWStreamCtxt* stream, void* closure ); -XWStreamCtxt* and_empty_stream( MPFORMAL AndGlobals* globals ); +XWStreamCtxt* and_empty_stream( MPFORMAL AndGameGlobals* globals ); typedef struct _SetInfo { const char* name; diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c index fb6aa18f5..4e948a906 100644 --- a/xwords4/android/jni/utilwrapper.c +++ b/xwords4/android/jni/utilwrapper.c @@ -30,6 +30,18 @@ #define MAX_QUANTITY_STRS 4 +typedef struct _AndDUtil { + XW_DUtilCtxt dutil; + EnvThreadInfo* ti; + JNIUtilCtxt* jniutil; + jobject jdutil; /* global ref to object implementing XW_DUtilCtxt */ + XP_UCHAR* userStrings[N_AND_USER_STRINGS]; + XP_U32 userStringsBits; +#ifdef XWFEATURE_DEVID + XP_UCHAR* devIDStorage; +#endif +} AndDUtil; + typedef struct _TimerStorage { XWTimerProc proc; void* closure; @@ -40,21 +52,8 @@ typedef struct _AndUtil { EnvThreadInfo* ti; jobject jutil; /* global ref to object implementing XW_UtilCtxt */ TimerStorage timerStorage[NUM_TIMERS_PLUS_ONE]; - XP_UCHAR* userStrings[N_AND_USER_STRINGS]; - XP_U32 userStringsBits; -#ifdef XWFEATURE_DEVID - XP_UCHAR* devIDStorage; -#endif } AndUtil; - -static VTableMgr* -and_util_getVTManager( XW_UtilCtxt* uc ) -{ - AndGlobals* globals = (AndGlobals*)uc->closure; - return globals->vtMgr; -} - #ifndef XWFEATURE_STANDALONE_ONLY static XWStreamCtxt* and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) @@ -62,7 +61,7 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) #ifdef DEBUG AndUtil* util = (AndUtil*)uc; #endif - AndGlobals* globals = (AndGlobals*)uc->closure; + AndGameGlobals* globals = (AndGameGlobals*)uc->closure; XWStreamCtxt* stream = and_empty_stream( MPPARM(util->util.mpool) globals ); stream_setAddress( stream, channelNo ); @@ -82,6 +81,14 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) XP_LOGF( "%s: skipping call into java because jutil==NULL", \ __func__ ); \ } + +#define DUTIL_CBK_HEADER(nam,sig) \ + AndDUtil* dutil = (AndDUtil*)duc; \ + JNIEnv* env = ENVFORME( dutil->ti ); \ + if ( NULL != dutil->jdutil ) { \ + jmethodID mid = getMethodID( env, dutil->jdutil, nam, sig ) + +#define DUTIL_CBK_TAIL() UTIL_CBK_TAIL() static XWBonusType and_util_getSquareBonus( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 boardSize, @@ -349,26 +356,24 @@ and_util_altKeyDown( XW_UtilCtxt* uc ) return XP_FALSE; } - XP_U32 -and_util_getCurSeconds( XW_UtilCtxt* uc ) +and_dutil_getCurSeconds( XW_DUtilCtxt* duc ) { - AndUtil* andutil = (AndUtil*)uc; - XP_U32 curSeconds = getCurSeconds( ENVFORME( andutil->ti ) ); + AndDUtil* anddutil = (AndDUtil*)duc; + XP_U32 curSeconds = getCurSeconds( ENVFORME( anddutil->ti ) ); /* struct timeval tv; */ /* gettimeofday( &tv, NULL ); */ /* XP_LOGF( "%s: %d vs %d", __func__, (int)tv.tv_sec, (int)curSeconds ); */ return curSeconds; } - static DictionaryCtxt* and_util_makeEmptyDict( XW_UtilCtxt* uc ) { #ifdef STUBBED_DICT XP_ASSERT(0); #else - AndGlobals* globals = (AndGlobals*)uc->closure; + AndGameGlobals* globals = (AndGameGlobals*)uc->closure; AndUtil* andutil = (AndUtil*)uc; DictionaryCtxt* result = and_dictionary_make_empty( MPPARM( ((AndUtil*)uc)->util.mpool ) @@ -378,51 +383,51 @@ and_util_makeEmptyDict( XW_UtilCtxt* uc ) } static const XP_UCHAR* -and_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ) +and_dutil_getUserString( XW_DUtilCtxt* duc, XP_U16 stringCode ) { XP_UCHAR* result = ""; - UTIL_CBK_HEADER("getUserString", "(I)Ljava/lang/String;" ); + DUTIL_CBK_HEADER("getUserString", "(I)Ljava/lang/String;" ); int index = stringCode - 1; /* see LocalizedStrIncludes.h */ - XP_ASSERT( index < VSIZE( util->userStrings ) ); + XP_ASSERT( index < VSIZE( dutil->userStrings ) ); - XP_ASSERT( 0 == (util->userStringsBits & (1 << index)) ); + XP_ASSERT( 0 == (dutil->userStringsBits & (1 << index)) ); - if ( ! util->userStrings[index] ) { - jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, + if ( ! dutil->userStrings[index] ) { + jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid, stringCode ); jsize len = (*env)->GetStringUTFLength( env, jresult ); - XP_UCHAR* buf = XP_MALLOC( util->util.mpool, len + 1 ); + XP_UCHAR* buf = XP_MALLOC( dutil->dutil.mpool, len + 1 ); const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL ); XP_MEMCPY( buf, jchars, len ); buf[len] = '\0'; (*env)->ReleaseStringUTFChars( env, jresult, jchars ); deleteLocalRef( env, jresult ); - util->userStrings[index] = buf; + dutil->userStrings[index] = buf; } - result = util->userStrings[index]; - UTIL_CBK_TAIL(); + result = dutil->userStrings[index]; + DUTIL_CBK_TAIL(); return result; } static const XP_UCHAR* -and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quantity ) +and_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 stringCode, XP_U16 quantity ) { XP_UCHAR* result = ""; - UTIL_CBK_HEADER("getUserQuantityString", "(II)Ljava/lang/String;" ); + DUTIL_CBK_HEADER("getUserQuantityString", "(II)Ljava/lang/String;" ); int index = stringCode - 1; /* see LocalizedStrIncludes.h */ - XP_ASSERT( index < VSIZE( util->userStrings ) ); + XP_ASSERT( index < VSIZE( dutil->userStrings ) ); XP_UCHAR** ptrs; - util->userStringsBits |= 1 << index; - ptrs = (XP_UCHAR**)util->userStrings[index]; + dutil->userStringsBits |= 1 << index; + ptrs = (XP_UCHAR**)dutil->userStrings[index]; if ( !ptrs ) { - ptrs = (XP_UCHAR**)XP_CALLOC( util->util.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) ); - util->userStrings[index] = (XP_UCHAR*)ptrs; + ptrs = (XP_UCHAR**)XP_CALLOC( dutil->dutil.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) ); + dutil->userStrings[index] = (XP_UCHAR*)ptrs; } - jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, + jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid, stringCode, quantity ); const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL ); int indx = 0; @@ -439,7 +444,7 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant if ( !ptrs[indx] ) { XP_ASSERT( indx < MAX_QUANTITY_STRS ); jsize len = (*env)->GetStringUTFLength( env, jresult ); - XP_UCHAR* buf = XP_MALLOC( util->util.mpool, len + 1 ); + XP_UCHAR* buf = XP_MALLOC( dutil->dutil.mpool, len + 1 ); XP_MEMCPY( buf, jchars, len ); buf[len] = '\0'; ptrs[indx] = buf; @@ -449,10 +454,83 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant deleteLocalRef( env, jresult ); result = ptrs[indx]; - UTIL_CBK_TAIL(); + DUTIL_CBK_TAIL(); return result; } +static void +and_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + const void* data, XP_U16 len ) +{ + DUTIL_CBK_HEADER( "store", "(Ljava/lang/String;[B)V" ); + + jbyteArray jdata = makeByteArray( env, len, data ); + jstring jkey = (*env)->NewStringUTF( env, key ); + + (*env)->CallVoidMethod( env, dutil->jdutil, mid, jkey, jdata ); + + deleteLocalRefs( env, jdata, jkey, DELETE_NO_REF ); + + DUTIL_CBK_TAIL(); +} + +static void +and_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* stream ) +{ + const void* ptr = stream_getPtr( stream ); + XP_U16 len = stream_getSize( stream ); + + and_dutil_storePtr( duc, key, ptr, len ); +} + +static jbyteArray +loadToByteArray( XW_DUtilCtxt* duc, const XP_UCHAR* key ) +{ + jbyteArray result = NULL; + DUTIL_CBK_HEADER( "load", "(Ljava/lang/String;)[B"); + + jstring jkey = (*env)->NewStringUTF( env, key ); + result = (*env)->CallObjectMethod( env, dutil->jdutil, mid, jkey ); + deleteLocalRef( env, jkey ); + DUTIL_CBK_TAIL(); + return result; +} + +static void +and_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + void* data, XP_U16* lenp ) +{ + AndDUtil* dutil = (AndDUtil*)duc; + JNIEnv* env = ENVFORME( dutil->ti ); + jbyteArray jvalue = loadToByteArray( duc, key ); + if ( jvalue != NULL ) { + jsize len = (*env)->GetArrayLength( env, jvalue ); + if ( len <= *lenp ) { + jbyte* jelems = (*env)->GetByteArrayElements( env, jvalue, NULL ); + XP_MEMCPY( data, jelems, len ); + (*env)->ReleaseByteArrayElements( env, jvalue, jelems, 0 ); + } + *lenp = len; + deleteLocalRef( env, jvalue ); + } +} + +static void +and_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream ) +{ + AndDUtil* dutil = (AndDUtil*)duc; + JNIEnv* env = ENVFORME( dutil->ti ); + jbyteArray jvalue = loadToByteArray( duc, key ); + if ( jvalue != NULL ) { + jbyte* jelems = (*env)->GetByteArrayElements( env, jvalue, NULL ); + jsize len = (*env)->GetArrayLength( env, jvalue ); + stream_putBytes( stream, jelems, len ); + (*env)->ReleaseByteArrayElements( env, jvalue, jelems, 0 ); + deleteLocalRef( env, jvalue ); + } +} + static void and_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 turn, XP_Bool turnLost ) @@ -519,18 +597,18 @@ and_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) #ifdef XWFEATURE_SMS static XP_Bool -and_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1, - const XP_UCHAR* p2 ) +and_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, const XP_UCHAR* p1, + const XP_UCHAR* p2 ) { XP_Bool same = 0 == strcmp( p1, p2 ); if ( !same ) { - UTIL_CBK_HEADER( "phoneNumbersSame", - "(Ljava/lang/String;Ljava/lang/String;)Z" ); + DUTIL_CBK_HEADER( "phoneNumbersSame", + "(Ljava/lang/String;Ljava/lang/String;)Z" ); jstring js1 = (*env)->NewStringUTF( env, p1 ); jstring js2 = (*env)->NewStringUTF( env, p2 ); - same = (*env)->CallBooleanMethod( env, util->jutil, mid, js1, js2 ); + same = (*env)->CallBooleanMethod( env, dutil->jdutil, mid, js1, js2 ); deleteLocalRefs( env, js1, js2, DELETE_NO_REF ); - UTIL_CBK_TAIL(); + DUTIL_CBK_TAIL(); } return same; } @@ -576,61 +654,61 @@ and_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, } static void -and_util_setIsServer(XW_UtilCtxt* uc, XP_Bool isServer ) +and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) { /* Change both the C and Java structs, which need to stay in sync */ uc->gameInfo->serverRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; - UTIL_CBK_HEADER("setIsServer", "(Z)V" ); + UTIL_CBK_HEADER( "setIsServer", "(Z)V" ); (*env)->CallVoidMethod( env, util->jutil, mid, isServer ); UTIL_CBK_TAIL(); } #ifdef XWFEATURE_DEVID static const XP_UCHAR* -and_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) +and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ ) { const XP_UCHAR* result = NULL; *typ = ID_TYPE_NONE; - UTIL_CBK_HEADER( "getDevID", "([B)Ljava/lang/String;" ); + DUTIL_CBK_HEADER( "getDevID", "([B)Ljava/lang/String;" ); jbyteArray jbarr = makeByteArray( env, 1, NULL ); - jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, jbarr ); + jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid, jbarr ); if ( NULL != jresult ) { const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL ); jsize len = (*env)->GetStringUTFLength( env, jresult ); - if ( NULL != util->devIDStorage - && 0 == XP_MEMCMP( util->devIDStorage, jchars, len ) ) { + if ( NULL != dutil->devIDStorage + && 0 == XP_MEMCMP( dutil->devIDStorage, jchars, len ) ) { XP_LOGF( "%s: already have matching devID", __func__ ); } else { XP_LOGF( "%s: allocating storage for devID", __func__ ); - XP_FREEP( util->util.mpool, &util->devIDStorage ); - util->devIDStorage = XP_MALLOC( util->util.mpool, len + 1 ); - XP_MEMCPY( util->devIDStorage, jchars, len ); - util->devIDStorage[len] = '\0'; + XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage ); + dutil->devIDStorage = XP_MALLOC( dutil->dutil.mpool, len + 1 ); + XP_MEMCPY( dutil->devIDStorage, jchars, len ); + dutil->devIDStorage[len] = '\0'; } (*env)->ReleaseStringUTFChars( env, jresult, jchars ); - result = (const XP_UCHAR*)util->devIDStorage; + result = (const XP_UCHAR*)dutil->devIDStorage; jbyte* elems = (*env)->GetByteArrayElements( env, jbarr, NULL ); *typ = (DevIDType)elems[0]; (*env)->ReleaseByteArrayElements( env, jbarr, elems, 0 ); } deleteLocalRef( env, jbarr ); - UTIL_CBK_TAIL(); + DUTIL_CBK_TAIL(); return result; } static void -and_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, - const XP_UCHAR* idRelay ) +and_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ, + const XP_UCHAR* idRelay ) { - UTIL_CBK_HEADER( "deviceRegistered", - "(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" ); + DUTIL_CBK_HEADER( "deviceRegistered", + "(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" ); jstring jstr = (*env)->NewStringUTF( env, idRelay ); jobject jtyp = intToJEnum( env, typ, PKG_PATH("jni/UtilCtxt$DevIDType") ); - (*env)->CallVoidMethod( env, util->jutil, mid, jtyp, jstr ); + (*env)->CallVoidMethod( env, dutil->jdutil, mid, jtyp, jstr ); deleteLocalRefs( env, jstr, jtyp, DELETE_NO_REF ); - UTIL_CBK_TAIL(); + DUTIL_CBK_TAIL(); } #endif /* XWFEATURE_DEVID */ @@ -664,16 +742,23 @@ and_util_engineStopping( XW_UtilCtxt* uc ) } #endif +static XW_DUtilCtxt* +and_util_getDevUtilCtxt( XW_UtilCtxt* uc ) +{ + AndGameGlobals* globals = (AndGameGlobals*)uc->closure; + XP_ASSERT( !!globals->dutil ); + return globals->dutil; +} + #ifdef COMMS_CHECKSUM static XP_UCHAR* -and_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len ) +and_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len ) { - AndUtil* util = (AndUtil*)uc; - JNIEnv* env = ENVFORME( util->ti ); - AndGlobals* globals = (AndGlobals*)uc->closure; - struct JNIUtilCtxt* jniutil = globals->jniutil; + AndDUtil* dutil = (AndDUtil*)duc; + JNIEnv* env = ENVFORME( dutil->ti ); + struct JNIUtilCtxt* jniutil = dutil->jniutil; jstring jsum = and_util_getMD5SumForBytes( jniutil, ptr, len ); - XP_UCHAR* result = getStringCopy( MPPARM(uc->mpool) env, jsum ); + XP_UCHAR* result = getStringCopy( MPPARM(duc->mpool) env, jsum ); deleteLocalRef( env, jsum ); return result; } @@ -681,8 +766,8 @@ and_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len ) XW_UtilCtxt* -makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, - AndGlobals* closure ) +makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, + AndGameGlobals* closure ) { AndUtil* util = (AndUtil*)XP_CALLOC( mpool, sizeof(*util) ); UtilVtable* vtable = (UtilVtable*)XP_CALLOC( mpool, sizeof(*vtable) ); @@ -697,7 +782,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, util->util.gameInfo = gi; #define SET_PROC(nam) vtable->m_util_##nam = and_util_##nam - SET_PROC(getVTManager); + #ifndef XWFEATURE_STANDALONE_ONLY SET_PROC(makeStreamFromAddr); #endif @@ -725,10 +810,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, SET_PROC(clearTimer); SET_PROC(requestTime); SET_PROC(altKeyDown); - SET_PROC(getCurSeconds); SET_PROC(makeEmptyDict); - SET_PROC(getUserString); - SET_PROC(getUserQuantityString); SET_PROC(notifyIllegalWords); #ifdef XWFEATURE_CHAT SET_PROC(showChat); @@ -740,10 +822,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, SET_PROC(playerScoreHeld); #endif -#ifdef XWFEATURE_SMS - SET_PROC(phoneNumbersSame); -#endif - #ifdef XWFEATURE_BOARDWORDS SET_PROC(cellSquareHeld); #endif @@ -752,10 +830,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, SET_PROC(informMissing); SET_PROC(addrChange); SET_PROC(setIsServer); -# ifdef XWFEATURE_DEVID - SET_PROC(getDevID); - SET_PROC(deviceRegistered); -# endif #endif #ifdef XWFEATURE_SEARCHLIMIT SET_PROC(getTraySearchLimits); @@ -764,9 +838,8 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, SET_PROC(engineStarting); SET_PROC(engineStopping); #endif -#ifdef COMMS_CHECKSUM - SET_PROC(md5sum); -#endif + + SET_PROC(getDevUtilCtxt); #undef SET_PROC return (XW_UtilCtxt*)util; @@ -778,31 +851,81 @@ destroyUtil( XW_UtilCtxt** utilc ) AndUtil* util = (AndUtil*)*utilc; JNIEnv* env = ENVFORME( util->ti ); - for ( int ii = 0; ii < VSIZE(util->userStrings); ++ii ) { - XP_UCHAR* ptr = util->userStrings[ii]; + if ( NULL != util->jutil ) { + (*env)->DeleteGlobalRef( env, util->jutil ); + } + XP_FREE( util->util.mpool, util->util.vtable ); + XP_FREE( util->util.mpool, util ); + *utilc = NULL; +} + +XW_DUtilCtxt* +makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject jdutil, VTableMgr* vtMgr, + JNIUtilCtxt* jniutil, void* closure ) +{ + AndDUtil* dutil = (AndDUtil*)XP_CALLOC( mpool, sizeof(*dutil) ); + dutil->ti = ti; + dutil->jniutil = jniutil; + dutil->dutil.closure = closure; + dutil->dutil.vtMgr = vtMgr; + + if ( NULL != jdutil ) { + JNIEnv* env = ENVFORME( ti ); + dutil->jdutil = (*env)->NewGlobalRef( env, jdutil ); + } + + MPASSIGN( dutil->dutil.mpool, mpool ); + + DUtilVtable* vtable = &dutil->dutil.vtable; +#define SET_DPROC(nam) vtable->m_dutil_##nam = and_dutil_##nam + SET_DPROC(getCurSeconds); + SET_DPROC(getUserString); + SET_DPROC(getUserQuantityString); + SET_DPROC(storeStream); + SET_DPROC(loadStream); + SET_DPROC(storePtr); + SET_DPROC(loadPtr); +# ifdef XWFEATURE_DEVID + SET_DPROC(getDevID); + SET_DPROC(deviceRegistered); +# endif +#ifdef XWFEATURE_SMS + SET_DPROC(phoneNumbersSame); +#endif +#ifdef COMMS_CHECKSUM + SET_DPROC(md5sum); +#endif + + return &dutil->dutil; +} + +void +destroyDUtil( XW_DUtilCtxt** dutilp ) +{ + AndDUtil* dutil = (AndDUtil*)*dutilp; + JNIEnv* env = ENVFORME( dutil->ti ); + if ( NULL != dutil->jdutil ) { + (*env)->DeleteGlobalRef( env, dutil->jdutil ); + } + + for ( int ii = 0; ii < VSIZE(dutil->userStrings); ++ii ) { + XP_UCHAR* ptr = dutil->userStrings[ii]; if ( NULL != ptr ) { - if ( 0 == (util->userStringsBits & (1 << ii)) ) { - XP_FREE( util->util.mpool, ptr ); + if ( 0 == (dutil->userStringsBits & (1 << ii)) ) { + XP_FREE( dutil->dutil.mpool, ptr ); } else { XP_UCHAR** ptrs = (XP_UCHAR**)ptr; for ( int jj = 0; jj < MAX_QUANTITY_STRS; ++jj ) { ptr = ptrs[jj]; if ( !!ptr ) { - XP_FREE( util->util.mpool, ptr ); + XP_FREE( dutil->dutil.mpool, ptr ); } } - XP_FREE( util->util.mpool, ptrs ); + XP_FREE( dutil->dutil.mpool, ptrs ); } } } - - if ( NULL != util->jutil ) { - (*env)->DeleteGlobalRef( env, util->jutil ); - } #ifdef XWFEATURE_DEVID - XP_FREEP( util->util.mpool, &util->devIDStorage ); + XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage ); #endif - XP_FREE( util->util.mpool, util->util.vtable ); - XP_FREE( util->util.mpool, util ); - *utilc = NULL; } diff --git a/xwords4/android/jni/utilwrapper.h b/xwords4/android/jni/utilwrapper.h index 44a21acc3..b317d4ff5 100644 --- a/xwords4/android/jni/utilwrapper.h +++ b/xwords4/android/jni/utilwrapper.h @@ -25,10 +25,17 @@ #include "game.h" #include "util.h" +#include "dutil.h" #include "andglobals.h" +#include "jniutlswrapper.h" + +XW_DUtilCtxt* makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject j_dutil, + VTableMgr* vtMgr, JNIUtilCtxt* jniutil, + void* closure ); +void destroyDUtil( XW_DUtilCtxt** dutilp ); XW_UtilCtxt* makeUtil( MPFORMAL EnvThreadInfo* ti, jobject j_util, - CurGameInfo* gi, AndGlobals* globals ); + CurGameInfo* gi, AndGameGlobals* globals ); void destroyUtil( XW_UtilCtxt** util ); bool utilTimerFired( XW_UtilCtxt* util, XWTimerReason why, int handle ); diff --git a/xwords4/android/jni/xwjni.c b/xwords4/android/jni/xwjni.c index e5b867e5d..f3413a878 100644 --- a/xwords4/android/jni/xwjni.c +++ b/xwords4/android/jni/xwjni.c @@ -1,6 +1,7 @@ -/* -*- compile-command: "find-and-gradle.sh installXw4Debug"; -*- */ + +/* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */ /* - * Copyright © 2009 - 2014 by Eric House (xwords@eehouse.org). All rights + * Copyright © 2009 - 2018 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -34,6 +35,7 @@ #include "dictiter.h" #include "dictmgr.h" #include "nli.h" +#include "smsproto.h" #include "utilwrapper.h" #include "drawwrapper.h" @@ -60,9 +62,33 @@ struct _EnvThreadInfo { typedef struct _JNIGlobalState { EnvThreadInfo ti; DictMgrCtxt* dictMgr; + SMSProto* smsProto; + VTableMgr* vtMgr; + XW_DUtilCtxt* dutil; + JNIUtilCtxt* jniutil; + XP_Bool mpoolInUse; MPSLOT } JNIGlobalState; +#ifdef MEM_DEBUG +static MemPoolCtx* +getMPool( JNIGlobalState* globalState ) +{ + XP_ASSERT( !globalState->mpoolInUse ); + globalState->mpoolInUse = XP_TRUE; + return globalState->mpool; +} + +static void +releaseMPool( JNIGlobalState* globalState ) +{ + XP_ASSERT( globalState->mpoolInUse ); + globalState->mpoolInUse = XP_FALSE; +} +#else +# define releaseMPool(s) +#endif + #define LOG_MAPPING // #define LOG_MAPPING_ALL @@ -157,7 +183,7 @@ static void map_init( MPFORMAL EnvThreadInfo* ti, JNIEnv* env ) { pthread_mutex_init( &ti->mtxThreads, NULL ); - MPASSIGN(ti->mpool, mpool); + MPASSIGN( ti->mpool, mpool ); map_thread( ti, env ); } @@ -229,6 +255,7 @@ static void tilesArrayToTileSet( JNIEnv* env, jintArray jtiles, TrayTileSet* tset ) { if ( jtiles != NULL ) { + XP_ASSERT( !!jtiles ); jsize nTiles = (*env)->GetArrayLength( env, jtiles ); int tmp[MAX_TRAY_TILES]; getIntsFromArray( env, tmp, jtiles, nTiles, XP_FALSE ); @@ -255,33 +282,43 @@ getState( JNIEnv* env, GamePtrType gamePtr ) JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals -( JNIEnv* env, jclass C ) +( JNIEnv* env, jclass C, jobject jdutil, jobject jniu ) { #ifdef MEM_DEBUG MemPoolCtx* mpool = mpool_make( NULL ); #endif - JNIGlobalState* state = (JNIGlobalState*)XP_CALLOC( mpool, sizeof(*state) ); - map_init( MPPARM(mpool) &state->ti, env ); - state->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); - MPASSIGN( state->mpool, mpool ); - // LOG_RETURNF( "%p", state ); - return (jint)state; + JNIGlobalState* globalState = (JNIGlobalState*)XP_CALLOC( mpool, + sizeof(*globalState) ); + map_init( MPPARM(mpool) &globalState->ti, env ); + globalState->jniutil = makeJNIUtil( MPPARM(mpool) env, &globalState->ti, jniu ); + globalState->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); + globalState->dutil = makeDUtil( MPPARM(mpool) &globalState->ti, jdutil, + globalState->vtMgr, globalState->jniutil, NULL ); + globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); + globalState->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil ); + MPASSIGN( globalState->mpool, mpool ); + // LOG_RETURNF( "%p", globalState ); + return (jint)globalState; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals -( JNIEnv* env, jclass C, jint ptr ) +( JNIEnv* env, jclass C, jint jniGlobalPtr ) { // LOG_FUNC(); - if ( 0 != ptr ) { - JNIGlobalState* state = (JNIGlobalState*)ptr; - XP_ASSERT( ENVFORME(&state->ti) == env ); - dmgr_destroy( state->dictMgr ); + if ( 0 != jniGlobalPtr ) { + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG - MemPoolCtx* mpool = state->mpool; + MemPoolCtx* mpool = getMPool( globalState ); #endif - map_destroy( &state->ti ); - XP_FREE( mpool, state ); + XP_ASSERT( ENVFORME(&globalState->ti) == env ); + smsproto_free( globalState->smsProto ); + vtmgr_destroy( MPPARM(mpool) globalState->vtMgr ); + dmgr_destroy( globalState->dictMgr ); + destroyDUtil( &globalState->dutil ); + destroyJNIUtil( env, &globalState->jniutil ); + map_destroy( &globalState->ti ); + XP_FREE( mpool, globalState ); mpool_destroy( mpool ); } } @@ -511,6 +548,7 @@ loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp ) static XWStreamCtxt* streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream ) { + XP_ASSERT( !!jstream ); int len = (*env)->GetArrayLength( env, jstream ); XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr, len, NULL, 0, NULL ); @@ -525,15 +563,15 @@ streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream ) ****************************************************/ JNIEXPORT jbyteArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream -(JNIEnv* env, jclass C, jobject jgi ) +( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jgi ) { jbyteArray result; + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG - MemPoolCtx* mpool = mpool_make( NULL ); + MemPoolCtx* mpool = getMPool( globalState ); #endif CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); - VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); - XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr, + XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr, NULL, 0, NULL ); game_saveToStream( NULL, gi, stream, 0 ); @@ -541,28 +579,24 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream result = streamToBArray( env, stream ); stream_destroy( stream ); - - vtmgr_destroy( MPPARM(mpool) vtMgr ); -#ifdef MEM_DEBUG - mpool_destroy( mpool ); -#endif + releaseMPool( globalState ); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream -( JNIEnv* env, jclass C, jobject jgi, jbyteArray jstream ) +( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jgi, jbyteArray jstream ) { + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG - MemPoolCtx* mpool = mpool_make( NULL ); + MemPoolCtx* mpool = getMPool( globalState ); #endif - VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); - - XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream ); + XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, + globalState->vtMgr, jstream ); CurGameInfo gi; XP_MEMSET( &gi, 0, sizeof(gi) ); - if ( game_makeFromStream( MPPARM(mpool) stream, NULL, + if ( game_makeFromStream( MPPARM(mpool) stream, NULL, &gi, NULL, NULL, NULL, NULL, NULL, NULL ) ) { setJGI( env, jgi, &gi ); } else { @@ -572,50 +606,45 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream gi_disposePlayerInfo( MPPARM(mpool) &gi ); stream_destroy( stream ); - vtmgr_destroy( MPPARM(mpool) vtMgr ); -#ifdef MEM_DEBUG - mpool_destroy( mpool ); -#endif + releaseMPool( globalState ); } JNIEXPORT jbyteArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream -( JNIEnv* env, jclass C, jobject njli ) +( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject njli ) { LOG_FUNC(); - jbyteArray result; + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG - MemPoolCtx* mpool = mpool_make( NULL ); + MemPoolCtx* mpool = getMPool( globalState ); #endif + + jbyteArray result; NetLaunchInfo nli = {0}; loadNLI( env, &nli, njli ); /* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */ - VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); - XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr, + XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr, NULL, 0, NULL ); nli_saveToStream( &nli, stream ); result = streamToBArray( env, stream ); stream_destroy( stream ); - - vtmgr_destroy( MPPARM(mpool) vtMgr ); -#ifdef MEM_DEBUG - mpool_destroy( mpool ); -#endif + releaseMPool( globalState ); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream -( JNIEnv* env, jclass C, jobject jnli, jbyteArray jstream ) +( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jnli, jbyteArray jstream ) { LOG_FUNC(); + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG - MemPoolCtx* mpool = mpool_make( NULL ); + MemPoolCtx* mpool = getMPool( globalState ); #endif - VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); - XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream ); + XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, + globalState->vtMgr, jstream ); NetLaunchInfo nli = {0}; if ( nli_makeFromStream( &nli, stream ) ) { @@ -625,10 +654,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream } stream_destroy( stream ); - vtmgr_destroy( MPPARM(mpool) vtMgr ); -#ifdef MEM_DEBUG - mpool_destroy( mpool ); -#endif + releaseMPool( globalState ); } JNIEXPORT void JNICALL @@ -684,14 +710,18 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1unref JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo ( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, - jstring jname, jstring jpath, jobject jniu, jboolean check, jobject jinfo ) + jstring jname, jstring jpath, jboolean check, jobject jinfo ) { jboolean result = false; - JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; - map_thread( &state->ti, env ); - JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env, &state->ti, jniu ); - DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr, - jniutil, jname, jDictBytes, jpath, + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; + map_thread( &globalState->ti, env ); + +#ifdef MEM_DEBUG + MemPoolCtx* mpool = getMPool( globalState ); +#endif + + DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, globalState->dictMgr, + globalState->jniutil, jname, jDictBytes, jpath, NULL, check ); if ( NULL != dict ) { if ( NULL != jinfo ) { @@ -704,8 +734,8 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo dict_unref( dict ); result = true; } - destroyJNIUtil( env, &jniutil ); + releaseMPool( globalState ); return result; } @@ -739,10 +769,88 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue return dict_getTileValue( (DictionaryCtxt*)dictPtr, tile ); } +static jobjectArray +msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr ) +{ + 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]; + jbyteArray arr = makeByteArray( env, msg->len, (const jbyte*)msg->data ); + (*env)->SetObjectArrayElement( env, result, ii, arr ); + deleteLocalRef( env, arr ); + } + 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 ) +{ + jobjectArray result = NULL; + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; + map_thread( &globalState->ti, env ); + + jbyte* data = NULL; + int len = 0; + if ( NULL != jData ) { + len = (*env)->GetArrayLength( env, jData ); + data = (*env)->GetByteArrayElements( env, jData, NULL ); + } + 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 ); + if ( !!arr ) { + result = msgArrayToByteArrays( env, arr ); + smsproto_freeMsgArray( globalState->smsProto, arr ); + } + + setIntInArray( env, jWaitSecsArr, 0, waitSecs ); + + (*env)->ReleaseStringUTFChars( env, jToPhone, toPhone ); + if ( NULL != jData ) { + (*env)->ReleaseByteArrayElements( env, jData, data, 0 ); + } + + return result; +} + +JNIEXPORT jobjectArray JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound +( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData, + jstring jFromPhone ) +{ + jobjectArray result = NULL; + 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 ); + if ( !!arr ) { + result = msgArrayToByteArrays( env, arr ); + smsproto_freeMsgArray( globalState->smsProto, arr ); + } + + (*env)->ReleaseStringUTFChars( env, jFromPhone, fromPhone ); + (*env)->ReleaseByteArrayElements( env, jData, data, 0 ); + } else { + XP_LOGF( "%s() => null (null input)", __func__ ); + } + return result; +} + struct _JNIState { XWGame game; JNIGlobalState* globalJNI; - AndGlobals globals; + AndGameGlobals globals; // pthread_mutex_t msgMutex; XP_U16 curSaveCount; XP_U16 lastSavedSize; @@ -761,27 +869,26 @@ struct _JNIState { #define XWJNI_START_GLOBALS() \ XWJNI_START() \ - AndGlobals* globals = &state->globals; \ + AndGameGlobals* globals = &state->globals; \ #define XWJNI_END() \ } \ JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_initJNI -( JNIEnv* env, jclass C, int jniGlobalPtr, jint seed, jstring jtag ) +( JNIEnv* env, jclass C, int jniGlobalPtr, jint seed ) { /* Why am I doing this twice? */ /* struct timeval tv; */ /* gettimeofday( &tv, NULL ); */ /* srandom( tv.tv_sec ); */ #ifdef MEM_DEBUG - const char* tag = (*env)->GetStringUTFChars( env, jtag, NULL ); - MemPoolCtx* mpool = mpool_make( tag ); - (*env)->ReleaseStringUTFChars( env, jtag, tag ); + MemPoolCtx* mpool = ((JNIGlobalState*)jniGlobalPtr)->mpool; #endif JNIState* state = (JNIState*)XP_CALLOC( mpool, sizeof(*state) ); state->globalJNI = (JNIGlobalState*)jniGlobalPtr; - AndGlobals* globals = &state->globals; + AndGameGlobals* globals = &state->globals; + globals->dutil = state->globalJNI->dutil; globals->state = (JNIState*)state; MPASSIGN( state->mpool, mpool ); globals->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool)); @@ -807,8 +914,8 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject j_gi, jobjectArray j_names, jobjectArray j_dicts, jobjectArray j_paths, - jstring j_lang, jobject j_util, jobject jniu, jobject j_draw, - jobject j_cp, jobject j_procs ) + jstring j_lang, jobject j_util, jobject j_draw, jobject j_cp, + jobject j_procs ) { XWJNI_START_GLOBALS(); EnvThreadInfo* ti = &state->globalJNI->ti; @@ -816,7 +923,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame globals->gi = gi; globals->util = makeUtil( MPPARM(mpool) ti, j_util, gi, globals ); - globals->jniutil = makeJNIUtil( MPPARM(mpool) env, ti, jniu ); + globals->jniutil = state->globalJNI->jniutil; DrawCtx* dctx = NULL; if ( !!j_draw ) { dctx = makeDraw( MPPARM(mpool) ti, j_draw ); @@ -858,7 +965,7 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose #ifdef MEM_DEBUG MemPoolCtx* mpool = state->mpool; #endif - AndGlobals* globals = &state->globals; + AndGameGlobals* globals = &state->globals; destroyGI( MPPARM(mpool) &globals->gi ); @@ -867,22 +974,19 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose destroyDraw( &globals->dctx ); destroyXportProcs( &globals->xportProcs ); destroyUtil( &globals->util ); - destroyJNIUtil( env, &globals->jniutil ); vtmgr_destroy( MPPARM(mpool) globals->vtMgr ); MAP_REMOVE( &state->globalJNI->ti, env ); /* pthread_mutex_destroy( &state->msgMutex ); */ XP_FREE( mpool, state ); - mpool_destroy( mpool ); } /* game_dispose */ JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream ( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject /*out*/jgi, jobjectArray jdictNames, jobjectArray jdicts, jobjectArray jpaths, - jstring jlang, jobject jutil, jobject jniu, jobject jdraw, jobject jcp, - jobject jprocs ) + jstring jlang, jobject jutil, jobject jdraw, jobject jcp, jobject jprocs ) { jboolean result; DictionaryCtxt* dict; @@ -892,7 +996,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream globals->gi = (CurGameInfo*)XP_CALLOC( mpool, sizeof(*globals->gi) ); globals->util = makeUtil( MPPARM(mpool) ti, jutil, globals->gi, globals); - globals->jniutil = makeJNIUtil( MPPARM(mpool) env, ti, jniu ); + globals->jniutil = state->globalJNI->jniutil; makeDicts( MPPARM(state->globalJNI->mpool) env, state->globalJNI->dictMgr, globals->jniutil, &dict, &dicts, jdictNames, jdicts, jpaths, jlang ); @@ -923,7 +1027,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream destroyDraw( &globals->dctx ); destroyXportProcs( &globals->xportProcs ); destroyUtil( &globals->util ); - destroyJNIUtil( env, &globals->jniutil ); destroyGI( MPPARM(mpool) &globals->gi ); } @@ -1556,7 +1659,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1writeFinalScores void and_send_on_close( XWStreamCtxt* stream, void* closure ) { - AndGlobals* globals = (AndGlobals*)closure; + AndGameGlobals* globals = (AndGameGlobals*)closure; JNIState* state = (JNIState*)globals->state; XP_ASSERT( !!state->game.comms ); @@ -2049,8 +2152,8 @@ JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_haveEnv ( JNIEnv* env, jclass C, jint jniGlobalPtr ) { - JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; - jboolean result = NULL != prvEnvForMe(&state->ti); + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; + jboolean result = NULL != prvEnvForMe(&globalState->ti); return result; } @@ -2102,29 +2205,25 @@ static void freeIndices( DictIterData* data ); JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init ( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, jstring jname, - jstring jpath, jobject jniu ) + jstring jpath ) { jint closure = 0; - JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; + JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; - JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env, - &state->ti, jniu ); - DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr, - jniutil, jname, jDictBytes, jpath, NULL, - false ); + DictionaryCtxt* dict = makeDict( MPPARM(globalState->mpool) env, + globalState->dictMgr, globalState->jniutil, + jname, jDictBytes, jpath, NULL, false ); if ( !!dict ) { - DictIterData* data = XP_CALLOC( state->mpool, sizeof(*data) ); + DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) ); data->env = env; - data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(state->mpool) ); - data->jniutil = jniutil; + data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globalState->mpool) ); + data->jniutil = globalState->jniutil; data->dict = dict; data->depth = 2; #ifdef MEM_DEBUG - data->mpool = state->mpool; + data->mpool = globalState->mpool; #endif closure = (int)data; - } else { - destroyJNIUtil( env, &jniutil ); } return closure; } @@ -2152,7 +2251,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1destroy #endif dict_unref( data->dict ); - destroyJNIUtil( env, &data->jniutil ); freeIndices( data ); vtmgr_destroy( MPPARM(mpool) data->vtMgr ); XP_FREE( mpool, data ); diff --git a/xwords4/android/scripts/arelease-clone.sh b/xwords4/android/scripts/arelease-clone.sh index fe244b2b1..05c7dda9e 100755 --- a/xwords4/android/scripts/arelease-clone.sh +++ b/xwords4/android/scripts/arelease-clone.sh @@ -53,7 +53,7 @@ git checkout ${TAG}${BRANCH} cd ./xwords4/android/ ./scripts/arelease.sh --apk-list $OUT_FILE mkdir -p /tmp/releases -cp app/build/outputs/apk/*.apk /tmp/releases +cp app/build/outputs/apk/xw4/release/*.apk /tmp/releases if [ -n "$XW_RELEASE_SCP_DEST" ]; then cat $OUT_FILE | while read APK; do diff --git a/xwords4/android/scripts/arelease.sh b/xwords4/android/scripts/arelease.sh index d51f1d464..1493bb26a 100755 --- a/xwords4/android/scripts/arelease.sh +++ b/xwords4/android/scripts/arelease.sh @@ -54,7 +54,7 @@ fi if [ -z "$FILES" ]; then do_build - for f in $(dirname $0)/../app/build/outputs/apk/*-unsigned-*.apk; do + for f in $(dirname $0)/../app/build/outputs/apk/xw4/release/*-unsigned-*.apk; do $(dirname $0)/sign-align.sh --apk $f done fi diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 774b1bf51..ad97837bf 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -166,6 +166,7 @@ board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, DrawCtx* draw, result->draw = draw; result->util = util; + result->dutil = util_getDevUtilCtxt( util ); result->gi = util->gameInfo; XP_ASSERT( !!result->gi ); @@ -1090,12 +1091,11 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed, XP_MEMSET( &bwl, 0, sizeof(bwl) ); if ( !legal ) { - stream = mem_stream_make( MPPARM(board->mpool) - util_getVTManager(board->util), NULL, - CHANNEL_NONE, (MemStreamCloseCallback)NULL ); + stream = mem_stream_make_raw( MPPARM(board->mpool) + dutil_getVTManager(board->dutil) ); - const XP_UCHAR* str = util_getUserString(board->util, - STR_COMMIT_CONFIRM); + const XP_UCHAR* str = dutil_getUserString( board->dutil, + STR_COMMIT_CONFIRM ); stream_catString( stream, str ); XP_Bool warn = board->util->gameInfo->phoniesAction == PHONIES_WARN; @@ -1319,10 +1319,8 @@ timerFiredForPen( BoardCtxt* board ) NULL, NULL, NULL ); if ( listWords ) { XWStreamCtxt* stream = - mem_stream_make( MPPARM(board->mpool) - util_getVTManager(board->util), NULL, - CHANNEL_NONE, - (MemStreamCloseCallback)NULL ); + mem_stream_make_raw( MPPARM(board->mpool) + dutil_getVTManager(board->dutil) ); model_listWordsThrough( board->model, modelCol, modelRow, stream ); util_cellSquareHeld( board->util, stream ); @@ -1428,7 +1426,7 @@ board_pushTimerSave( BoardCtxt* board ) { if ( board->gi->timerEnabled ) { if ( board->timerSaveCount++ == 0 ) { - board->timerStoppedTime = util_getCurSeconds( board->util ); + board->timerStoppedTime = dutil_getCurSeconds( board->dutil ); #ifdef DEBUG board->timerStoppedTurn = server_getCurrentTurn( board->server, NULL ); @@ -1451,7 +1449,7 @@ board_popTimerSave( BoardCtxt* board ) XP_ASSERT( board->timerStoppedTurn == turn ); if ( --board->timerSaveCount == 0 && turn >= 0 ) { - XP_U32 curTime = util_getCurSeconds( board->util ); + XP_U32 curTime = dutil_getCurSeconds( board->dutil ); XP_U32 elapsed; XP_ASSERT( board->timerStoppedTime != 0 ); diff --git a/xwords4/common/boarddrw.c b/xwords4/common/boarddrw.c index 386b77841..f8f5bb495 100644 --- a/xwords4/common/boarddrw.c +++ b/xwords4/common/boarddrw.c @@ -371,7 +371,7 @@ static XP_Bool drawCell( BoardCtxt* board, const XP_U16 col, const XP_U16 row, XP_Bool skipBlanks ) { XP_Bool success = XP_TRUE; - XP_Rect cellRect; + XP_Rect cellRect = {0}; Tile tile; XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE; XWBonusType bonus; diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h index f3d3db64f..b9e3e9462 100644 --- a/xwords4/common/boardp.h +++ b/xwords4/common/boardp.h @@ -139,6 +139,7 @@ struct BoardCtxt { ModelCtxt* model; ServerCtxt* server; DrawCtx* draw; + XW_DUtilCtxt* dutil; XW_UtilCtxt* util; struct CurGameInfo* gi; diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 5e2a73dae..af33b24e8 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -25,6 +25,7 @@ #include "comms.h" #include "util.h" +#include "dutil.h" #include "game.h" #include "xwstream.h" #include "memstream.h" @@ -100,6 +101,7 @@ typedef struct AddressRecord { struct CommsCtxt { XW_UtilCtxt* util; + XW_DUtilCtxt* dutil; XP_U32 connID; /* set from gameID: 0 means ignore; otherwise must match. Set by server. */ @@ -353,8 +355,7 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer, #endif ) { - CommsCtxt* comms = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*comms) ); - XP_MEMSET( comms, 0, sizeof(*comms) ); + CommsCtxt* comms = (CommsCtxt*)XP_CALLOC( mpool, sizeof(*comms) ); #ifdef DEBUG comms->tag = mpool_getTag(mpool); XP_LOGF( TAGFMT(isServer=%d; forceChannel=%d), TAGPRMS, isServer, forceChannel ); @@ -372,7 +373,9 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer, comms->xportFlags = comms->procs.flags; #endif } + comms->dutil = util_getDevUtilCtxt( util ); comms->util = util; + comms->dutil = util_getDevUtilCtxt( util ); #ifdef XWFEATURE_RELAY init_relay( comms, nPlayersHere, nPlayersTotal ); @@ -722,7 +725,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len ); stream_getBytes( stream, msg->msg, msg->len ); #ifdef COMMS_CHECKSUM - msg->checksum = util_md5sum( comms->util, msg->msg, msg->len ); + msg->checksum = dutil_md5sum( comms->dutil, msg->msg, msg->len ); #endif msg->next = (MsgQueueElem*)NULL; *prevsQueueNext = comms->msgQueueTail = msg; @@ -802,6 +805,11 @@ sendConnect( CommsCtxt* comms, XP_Bool breakExisting ) (void)send_via_bt_or_ip( comms, BTIPMSG_RESET, CHANNEL_NONE, typ, NULL, 0, NULL ); (void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE ); break; +#endif +#if defined XWFEATURE_SMS + case COMMS_CONN_SMS: + (void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE ); + break; #endif default: break; @@ -973,7 +981,6 @@ comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr ) setDoHeartbeat( comms ); #endif sendConnect( comms, XP_TRUE ); - } /* comms_setAddr */ void @@ -1136,10 +1143,8 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec, newMsgElem->sendCount = 0; #endif - hdrStream = mem_stream_make( MPPARM(comms->mpool) - util_getVTManager(comms->util), - NULL, 0, - (MemStreamCloseCallback)NULL ); + hdrStream = mem_stream_make_raw( MPPARM(comms->mpool) + dutil_getVTManager(comms->dutil)); stream_open( hdrStream ); #if 0 < COMMS_VERSION stream_putU16( hdrStream, HAS_VERSION_FLAG ); @@ -1168,8 +1173,8 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec, } #ifdef COMMS_CHECKSUM - newMsgElem->checksum = util_md5sum( comms->util, newMsgElem->msg, - newMsgElem->len ); + newMsgElem->checksum = dutil_md5sum( comms->dutil, newMsgElem->msg, + newMsgElem->len ); #endif return newMsgElem; } /* makeElemWithID */ @@ -1508,7 +1513,7 @@ comms_resendAll( CommsCtxt* comms, CommsConnType filter, XP_Bool force ) XP_Bool success = XP_TRUE; XP_ASSERT( !!comms ); - XP_U32 now = util_getCurSeconds( comms->util ); + XP_U32 now = dutil_getCurSeconds( comms->dutil ); if ( !force && (now < comms->nextResend) ) { XP_LOGF( "%s: aborting: %d seconds left in backoff", __func__, comms->nextResend - now ); @@ -1664,7 +1669,7 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream, } if ( ID_TYPE_NONE == typ /* error case */ || '\0' != devID[0] ) /* new info case */ { - util_deviceRegistered( comms->util, typ, devID ); + dutil_deviceRegistered( comms->dutil, typ, devID ); } #endif @@ -1845,7 +1850,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID ) static void noteHBReceived( CommsCtxt* comms/* , const CommsAddrRec* addr */ ) { - comms->lastMsgRcvdTime = util_getCurSeconds( comms->util ); + comms->lastMsgRcvdTime = dutil_getCurSeconds( comms->dutil ); setHeartbeatTimer( comms ); } #else @@ -1976,11 +1981,14 @@ getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr, break; case COMMS_CONN_SMS: #ifdef XWFEATURE_SMS - if ( util_phoneNumbersSame( comms->util, addr->u.sms.phone, - rec->addr.u.sms.phone ) - && addr->u.sms.port == rec->addr.u.sms.port ) { - matched = XP_TRUE; - XP_ASSERT( 0 ); + { + XW_DUtilCtxt* duc = util_getDevUtilCtxt( comms->util ); + if ( dutil_phoneNumbersSame( duc, addr->u.sms.phone, + rec->addr.u.sms.phone ) + && addr->u.sms.port == rec->addr.u.sms.port ) { + matched = XP_TRUE; + XP_ASSERT( 0 ); + } } #endif break; @@ -2227,7 +2235,7 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream, XP_U16 len = stream_getSize( stream ); // stream_getPtr pts at base, but sum excludes relay header const XP_U8* ptr = initialLen - len + stream_getPtr( stream ); - XP_UCHAR* sum = util_md5sum( comms->util, ptr, len ); + XP_UCHAR* sum = dutil_md5sum( comms->dutil, ptr, len ); XP_LOGF( TAGFMT() "got message of len %d with sum %s", TAGPRMS, len, sum ); XP_FREE( comms->mpool, sum ); @@ -2433,7 +2441,7 @@ heartbeat_checks( CommsCtxt* comms ) do { if ( comms->lastMsgRcvdTime > 0 ) { - XP_U32 now = util_getCurSeconds( comms->util ); + XP_U32 now = dutil_getCurSeconds( comms->dutil ); XP_U32 tooLongAgo = now - (HB_INTERVAL * 2); if ( comms->lastMsgRcvdTime < tooLongAgo ) { XP_LOGF( "%s: calling reset proc; last was %ld secs too long " @@ -2637,10 +2645,8 @@ logAddr( const CommsCtxt* comms, const CommsAddrRec* addr, const char* caller ) { if ( !!addr ) { char buf[128]; - XWStreamCtxt* stream = mem_stream_make( MPPARM(comms->mpool) - util_getVTManager(comms->util), - NULL, 0, - (MemStreamCloseCallback)NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(comms->mpool) + dutil_getVTManager(comms->dutil)); snprintf( buf, sizeof(buf), TAGFMT() "called on %p from %s:\n", TAGPRMS, addr, caller ); stream_catString( stream, buf ); @@ -2942,10 +2948,8 @@ relay_msg_to_stream( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID, { XP_LOGF( "%s(cmd=%s, destID=%x)", __func__, relayCmdToStr(cmd), destID ); XWStreamCtxt* stream; - stream = mem_stream_make( MPPARM(comms->mpool) - util_getVTManager(comms->util), - NULL, 0, - (MemStreamCloseCallback)NULL ); + stream = mem_stream_make_raw( MPPARM(comms->mpool) + dutil_getVTManager(comms->dutil) ); if ( stream != NULL ) { CommsAddrRec addr; stream_open( stream ); @@ -3200,7 +3204,7 @@ putDevID( const CommsCtxt* comms, XWStreamCtxt* stream ) { # if XWRELAY_PROTO_VERSION >= XWRELAY_PROTO_VERSION_CLIENTID DevIDType typ; - const XP_UCHAR* devID = util_getDevID( comms->util, &typ ); + const XP_UCHAR* devID = dutil_getDevID( comms->dutil, &typ ); XP_ASSERT( ID_TYPE_NONE <= typ && typ < ID_TYPE_NTYPES ); stream_putU8( stream, typ ); if ( ID_TYPE_NONE != typ ) { diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index f7f4476e1..2e916c7c4 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -129,6 +129,22 @@ typedef enum { OBJ_TRAY } BoardObjectType; +enum { + SERVER_STANDALONE, + SERVER_ISSERVER, + SERVER_ISCLIENT +}; +typedef XP_U8 DeviceRole; + +enum { + PHONIES_IGNORE, + PHONIES_WARN, + PHONIES_DISALLOW +}; +typedef XP_U8 XWPhoniesChoice; + +typedef XP_U8 XP_LangCode; + /* I'm going to try putting all forward "class" decls in the same file */ typedef struct BoardCtxt BoardCtxt; typedef struct CommMgrCtxt CommMgrCtxt; @@ -144,6 +160,7 @@ typedef struct XWStreamCtxt XWStreamCtxt; typedef struct TrayContext TrayContext; typedef struct PoolContext PoolContext; typedef struct XW_UtilCtxt XW_UtilCtxt; +typedef struct XW_DUtilCtxt XW_DUtilCtxt; /* Low two bits treated as channel, third as short-term flag indicating * sender's role; rest can be random to aid detection of duplicate packets. */ @@ -191,6 +208,8 @@ typedef enum { BONUS_LAST } XWBonusType; +#define PERSIST_KEY(str) __FILE__ ":" str + /* I need a way to communiate prefs to common/ code. For now, though, I'll * leave storage of these values up to the platforms. First, because I don't * want to deal with versioning in the common code. Second, becuase they diff --git a/xwords4/common/config.mk b/xwords4/common/config.mk index 022fe1b2c..ac15f4e62 100644 --- a/xwords4/common/config.mk +++ b/xwords4/common/config.mk @@ -48,6 +48,7 @@ COMMONSRC = \ $(COMMONDIR)/vtabmgr.c \ $(COMMONDIR)/dictmgr.c \ $(COMMONDIR)/dbgutil.c \ + $(COMMONDIR)/smsproto.c \ # PENDING: define this in terms of above!!! @@ -85,5 +86,6 @@ COMMON5 = \ $(COMMONOBJDIR)/vtabmgr.o \ $(COMMONOBJDIR)/dictmgr.o \ $(COMMONOBJDIR)/dbgutil.o \ + $(COMMONOBJDIR)/smsproto.o \ COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5) diff --git a/xwords4/common/dictnry.h b/xwords4/common/dictnry.h index 759ef358a..fbe8f7499 100644 --- a/xwords4/common/dictnry.h +++ b/xwords4/common/dictnry.h @@ -44,8 +44,6 @@ extern "C" { #define DICT_HEADER_MASK 0x08 #define DICT_SYNONYMS_MASK 0x10 -typedef XP_U8 XP_LangCode; - typedef enum { INTRADE_MW_TEXT = BONUS_LAST } XWMiniTextType; diff --git a/xwords4/common/dutil.h b/xwords4/common/dutil.h new file mode 100644 index 000000000..70aa8c70c --- /dev/null +++ b/xwords4/common/dutil.h @@ -0,0 +1,104 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2018 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DEVUTIL_H_ +#define _DEVUTIL_H_ + +#include "mempool.h" +#include "comtypes.h" +#include "xwrelay.h" +#include "vtabmgr.h" + +typedef struct _DUtilVtable { + XP_U32 (*m_dutil_getCurSeconds)( XW_DUtilCtxt* duc ); + const XP_UCHAR* (*m_dutil_getUserString)( XW_DUtilCtxt* duc, + XP_U16 stringCode ); + const XP_UCHAR* (*m_dutil_getUserQuantityString)( XW_DUtilCtxt* duc, + XP_U16 stringCode, + XP_U16 quantity ); + void (*m_dutil_storeStream)( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* data ); + /* Pass in an empty stream, and it'll be returned full */ + void (*m_dutil_loadStream)( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* inOut ); + void (*m_dutil_storePtr)( XW_DUtilCtxt* duc, const XP_UCHAR* key, + const void* data, XP_U16 len ); + void (*m_dutil_loadPtr)( XW_DUtilCtxt* duc, const XP_UCHAR* key, + void* data, XP_U16* lenp ); +#ifdef XWFEATURE_SMS + XP_Bool (*m_dutil_phoneNumbersSame)( XW_DUtilCtxt* uc, const XP_UCHAR* p1, + const XP_UCHAR* p2 ); +#endif + +#ifdef XWFEATURE_DEVID + const XP_UCHAR* (*m_dutil_getDevID)( XW_DUtilCtxt* duc, DevIDType* typ ); + void (*m_dutil_deviceRegistered)( XW_DUtilCtxt* duc, DevIDType typ, + const XP_UCHAR* idRelay ); +#endif + +#ifdef COMMS_CHECKSUM + XP_UCHAR* (*m_dutil_md5sum)( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len ); +#endif +} DUtilVtable; + +struct XW_DUtilCtxt { + DUtilVtable vtable; + + void* closure; + VTableMgr* vtMgr; + MPSLOT +}; + +/* This one cheats: direct access */ +#define dutil_getVTManager(duc) (duc)->vtMgr + +#define dutil_getCurSeconds(duc) \ + (duc)->vtable.m_dutil_getCurSeconds((duc)) +#define dutil_getUserString( duc, c ) \ + (duc)->vtable.m_dutil_getUserString((duc),(c)) +#define dutil_getUserQuantityString( duc, c, q ) \ + (duc)->vtable.m_dutil_getUserQuantityString((duc),(c),(q)) + +#define dutil_storeStream(duc, k, s) \ + (duc)->vtable.m_dutil_storeStream((duc), (k), (s)); +#define dutil_storePtr(duc, k, p, l) \ + (duc)->vtable.m_dutil_storePtr((duc), (k), (p), (l)); +#define dutil_loadStream(duc, k, s) \ + (duc)->vtable.m_dutil_loadStream((duc), (k), (s)); +#define dutil_loadPtr(duc, k, p, l) \ + (duc)->vtable.m_dutil_loadPtr((duc), (k), (p), (l)); + +#ifdef XWFEATURE_SMS +# define dutil_phoneNumbersSame(duc,p1,p2) \ + (duc)->vtable.m_dutil_phoneNumbersSame( (duc), (p1), (p2) ) +#endif + +#ifdef XWFEATURE_DEVID +# define dutil_getDevID( duc, t ) \ + (duc)->vtable.m_dutil_getDevID((duc),(t)) +# define dutil_deviceRegistered( duc, typ, id ) \ + (duc)->vtable.m_dutil_deviceRegistered( (duc), (typ), (id) ) +#endif + +#ifdef COMMS_CHECKSUM +# define dutil_md5sum( duc, p, l ) (duc)->vtable.m_dutil_md5sum((duc), (p), (l)) +#endif + +#endif diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 9bb55135f..cfb1ffe40 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -80,7 +80,7 @@ makeGameID( XW_UtilCtxt* util ) XP_U32 gameID = 0; assertUtilOK( util ); while ( 0 == gameID ) { - gameID = util_getCurSeconds( util ); + gameID = dutil_getCurSeconds( util_getDevUtilCtxt( util ) ); } return gameID; } diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c index 1e2ba20d3..cbc68fc0d 100644 --- a/xwords4/common/mempool.c +++ b/xwords4/common/mempool.c @@ -258,20 +258,27 @@ void* mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file, const char* func, XP_U32 lineNo ) { - MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL ); - - if ( !entry ) { - XP_LOGF( "findEntryFor failed; called from %s, line %d", - file, lineNo ); + // XP_LOGF( "%s(func=%s, line=%d): newsize: %d", __func__, func, lineNo, newsize ); + void* result; + if ( ptr == NULL ) { + result = mpool_alloc( mpool, newsize, file, func, lineNo ); } else { - entry->ptr = XP_PLATREALLOC( entry->ptr, newsize ); - XP_ASSERT( !!entry->ptr ); - entry->fileName = file; - entry->func = func; - entry->lineNo = lineNo; - entry->size = newsize; + MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL ); + + if ( !entry ) { + XP_LOGF( "findEntryFor failed; called from %s, line %d", + file, lineNo ); + } else { + entry->ptr = XP_PLATREALLOC( entry->ptr, newsize ); + XP_ASSERT( !!entry->ptr ); + entry->fileName = file; + entry->func = func; + entry->lineNo = lineNo; + entry->size = newsize; + } + result = entry->ptr; } - return entry->ptr; + return result; } /* mpool_realloc */ void diff --git a/xwords4/common/memstream.c b/xwords4/common/memstream.c index cfec0b4aa..f7cd5f53b 100644 --- a/xwords4/common/memstream.c +++ b/xwords4/common/memstream.c @@ -67,6 +67,12 @@ static StreamCtxVTable* make_vtable( MemStreamCtxt* stream ); * top of the file (first executable code). */ XWStreamCtxt* +mem_stream_make_raw( MPFORMAL VTableMgr* vtmgr ) +{ + return mem_stream_make( MPPARM(mpool) vtmgr, NULL, 0, NULL ); +} + +XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure, XP_PlayerAddr channelNo, MemStreamCloseCallback onClose ) { diff --git a/xwords4/common/memstream.h b/xwords4/common/memstream.h index 5091088f2..9e7617559 100644 --- a/xwords4/common/memstream.h +++ b/xwords4/common/memstream.h @@ -32,6 +32,8 @@ extern "C" { typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream, void* closure ); +XWStreamCtxt* mem_stream_make_raw( MPFORMAL VTableMgr* vtmgr); + XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure, XP_PlayerAddr addr, /* should be in a diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 76f89a292..1c6788e10 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -108,6 +108,7 @@ model_make( MPFORMAL DictionaryCtxt* dict, const PlayerDicts* dicts, MPASSIGN(result->vol.mpool, mpool); result->vol.util = util; + result->vol.dutil = util_getDevUtilCtxt( util ); result->vol.wni.proc = recordWord; result->vol.wni.closure = &result->vol.rwi; @@ -295,7 +296,7 @@ model_setSize( ModelCtxt* model, XP_U16 nCols ) stack_init( model->vol.stack ); } else { model->vol.stack = stack_make( MPPARM(model->vol.mpool) - util_getVTManager(model->vol.util)); + dutil_getVTManager(model->vol.dutil)); } } /* model_setSize */ @@ -2090,13 +2091,13 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry } if ( isPass ) { - format = util_getUserString( model->vol.util, STR_PASS ); + format = dutil_getUserString( model->vol.dutil, STR_PASS ); XP_SNPRINTF( buf, VSIZE(buf), "%s", format ); } else { if ( isHorizontal ) { - format = util_getUserString( model->vol.util, STRS_MOVE_ACROSS ); + format = dutil_getUserString( model->vol.dutil, STRS_MOVE_ACROSS ); } else { - format = util_getUserString( model->vol.util, STRS_MOVE_DOWN ); + format = dutil_getUserString( model->vol.dutil, STRS_MOVE_DOWN ); } row = mi->commonCoord; @@ -2114,7 +2115,7 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry } if ( !closure->keepHidden ) { - format = util_getUserString( model->vol.util, STRS_TRAY_AT_START ); + format = dutil_getUserString( model->vol.dutil, STRS_TRAY_AT_START ); formatTray( model_getPlayerTiles( model, entry->playerNum ), closure->dict, (XP_UCHAR*)traybuf, sizeof(traybuf), XP_FALSE ); @@ -2149,17 +2150,17 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), formatTray( (const TrayTileSet*) &entry->u.trade.newTiles, dict, traybuf2, sizeof(traybuf2), closure->keepHidden ); - format = util_getUserString( model->vol.util, STRSS_TRADED_FOR ); + format = dutil_getUserString( model->vol.dutil, STRSS_TRADED_FOR ); XP_SNPRINTF( buf, sizeof(buf), format, traybuf1, traybuf2 ); printString( stream, buf ); printString( stream, (XP_UCHAR*)XP_CR ); break; case PHONY_TYPE: - format = util_getUserString( model->vol.util, STR_PHONY_REJECTED ); + format = dutil_getUserString( model->vol.dutil, STR_PHONY_REJECTED ); printString( stream, format ); case MOVE_TYPE: - format = util_getUserString( model->vol.util, STRD_CUMULATIVE_SCORE ); + format = dutil_getUserString( model->vol.dutil, STRD_CUMULATIVE_SCORE ); XP_SNPRINTF( buf, sizeof(buf), format, totalScore ); printString( stream, buf ); @@ -2174,7 +2175,7 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), if ( entry->moveType == PHONY_TYPE ) { /* printString( stream, (XP_UCHAR*)"phony rejected " ); */ } else if ( !closure->keepHidden ) { - format = util_getUserString(model->vol.util, STRS_NEW_TILES); + format = dutil_getUserString( model->vol.dutil, STRS_NEW_TILES ); XP_SNPRINTF( buf, sizeof(buf), format, formatTray( &entry->u.move.newTiles, dict, traybuf1, sizeof(traybuf1), @@ -2195,9 +2196,9 @@ static void copyStack( const ModelCtxt* model, StackCtxt* destStack, const StackCtxt* srcStack ) { - XWStreamCtxt* stream = mem_stream_make( MPPARM(model->vol.mpool) - util_getVTManager(model->vol.util), - NULL, 0, NULL ); + XWStreamCtxt* stream = + mem_stream_make_raw( MPPARM(model->vol.mpool) + dutil_getVTManager(model->vol.dutil) ); stack_writeToStream( (StackCtxt*)srcStack, stream ); stack_loadFromStream( destStack, stream ); diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h index 47cca9080..e560e87b5 100644 --- a/xwords4/common/modelp.h +++ b/xwords4/common/modelp.h @@ -51,6 +51,7 @@ typedef struct _RecordWordsInfo { } RecordWordsInfo; typedef struct ModelVolatiles { + XW_DUtilCtxt* dutil; XW_UtilCtxt* util; struct CurGameInfo* gi; DictionaryCtxt* dict; diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c index a76450fb5..9f9f2130a 100644 --- a/xwords4/common/movestak.c +++ b/xwords4/common/movestak.c @@ -120,9 +120,7 @@ stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream ) stack->highWaterMark = stream_getU16( stream ); stack->nEntries = stream_getU16( stream ); stack->top = stream_getU32( stream ); - stack->data = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, - NULL, 0, - (MemStreamCloseCallback)NULL ); + stack->data = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr ); stream_getFromStream( stack->data, stream, nBytes ); } else { @@ -164,8 +162,8 @@ StackCtxt* stack_copy( const StackCtxt* stack ) { StackCtxt* newStack = NULL; - XWStreamCtxt* stream = mem_stream_make( MPPARM(stack->mpool) - stack->vtmgr, NULL, 0, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(stack->mpool) + stack->vtmgr ); stack_writeToStream( stack, stream ); newStack = stack_make( MPPARM(stack->mpool) stack->vtmgr ); @@ -184,8 +182,7 @@ pushEntryImpl( StackCtxt* stack, const StackEntry* entry ) XWStreamCtxt* stream = stack->data; if ( !stream ) { - stream = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, NULL, 0, - (MemStreamCloseCallback)NULL ); + stream = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr ); stack->data = stream; } diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 3024474f5..5132eb9d6 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -513,8 +513,8 @@ figureMoveScore( const ModelCtxt* model, XP_U16 turn, MoveInfo* moveInfo, score += EMPTIED_TRAY_BONUS; if ( !!stream ) { - const XP_UCHAR* bstr = util_getUserString( model->vol.util, - STR_BONUS_ALL ); + const XP_UCHAR* bstr = dutil_getUserString( model->vol.dutil, + STR_BONUS_ALL ); stream_catString( stream, bstr ); } } @@ -844,9 +844,9 @@ static void formatSummary( XWStreamCtxt* stream, const ModelCtxt* model, XP_U16 score ) { XP_UCHAR buf[60]; - XP_SNPRINTF(buf, sizeof(buf), - util_getUserString(model->vol.util, STRD_TURN_SCORE), - score); + XP_SNPRINTF( buf, sizeof(buf), + dutil_getUserString(model->vol.dutil, STRD_TURN_SCORE), + score ); XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) ); stream_catString( stream, buf ); } /* formatSummary */ diff --git a/xwords4/common/nwgamest.c b/xwords4/common/nwgamest.c index 08c54e236..f1296886f 100644 --- a/xwords4/common/nwgamest.c +++ b/xwords4/common/nwgamest.c @@ -531,7 +531,7 @@ setRoleStrings( NewGameCtx* ngc ) strID = STR_TOTALPLAYERS; } - value.ng_cp = util_getUserString( ngc->util, strID ); + value.ng_cp = dutil_getUserString( util_getDevUtilCtxt(ngc->util), strID ); (*ngc->setAttrProc)( closure, NG_ATTR_NPLAYHEADER, value ); } /* setRoleStrings */ diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 00bcd2e30..3f6652409 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -72,6 +72,7 @@ typedef struct ServerVolatiles { ModelCtxt* model; CommsCtxt* comms; XW_UtilCtxt* util; + XW_DUtilCtxt* dutil; CurGameInfo* gi; TurnChangeListener turnChangeListener; void* turnChangeData; @@ -271,6 +272,7 @@ server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, XW_UtilCtxt* util ) result->vol.model = model; result->vol.comms = comms; result->vol.util = util; + result->vol.dutil = util_getDevUtilCtxt( util ); result->vol.gi = util->gameInfo; initServer( result ); @@ -672,7 +674,7 @@ sendChatToClientsExcept( ServerCtxt* server, XP_U16 skip, const XP_UCHAR* msg, void server_sendChat( ServerCtxt* server, const XP_UCHAR* msg, XP_S16 from ) { - XP_U32 timestamp = util_getCurSeconds( server->vol.util ); + XP_U32 timestamp = dutil_getCurSeconds( server->vol.dutil ); if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { sendChatTo( server, SERVER_DEVICE, msg, from, timestamp ); } else { @@ -828,10 +830,8 @@ static XWStreamCtxt* mkServerStream( ServerCtxt* server ) { XWStreamCtxt* stream; - stream = mem_stream_make( MPPARM(server->mpool) - util_getVTManager(server->vol.util), - NULL, CHANNEL_NONE, - (MemStreamCloseCallback)NULL ); + stream = mem_stream_make_raw( MPPARM(server->mpool) + dutil_getVTManager(server->vol.dutil) ); XP_ASSERT( !!stream ); return stream; } /* mkServerStream */ @@ -849,11 +849,11 @@ makeRobotMove( ServerCtxt* server ) XP_Bool timerEnabled = gi->timerEnabled; XP_Bool canMove; XP_U32 time = 0L; /* stupid compiler.... */ - XW_UtilCtxt* util = server->vol.util; + XW_DUtilCtxt* dutil = server->vol.dutil; XP_Bool forceTrade = XP_FALSE; if ( timerEnabled ) { - time = util_getCurSeconds( util ); + time = dutil_getCurSeconds( dutil ); } #ifdef XWFEATURE_SLOW_ROBOT @@ -917,8 +917,8 @@ makeRobotMove( ServerCtxt* server ) if ( !!stream ) { XP_UCHAR buf[64]; - str = util_getUserQuantityString( util, STRD_ROBOT_TRADED, - MAX_TRAY_TILES ); + str = dutil_getUserQuantityString( dutil, STRD_ROBOT_TRADED, + MAX_TRAY_TILES ); XP_SNPRINTF( buf, sizeof(buf), str, MAX_TRAY_TILES ); stream_catString( stream, buf ); @@ -951,7 +951,7 @@ makeRobotMove( ServerCtxt* server ) if ( timerEnabled ) { gi->players[turn].secondsUsed += - (XP_U16)(util_getCurSeconds( util ) - time); + (XP_U16)(dutil_getCurSeconds( dutil ) - time); } else { XP_ASSERT( gi->players[turn].secondsUsed == 0 ); } @@ -1013,6 +1013,7 @@ showPrevScore( ServerCtxt* server ) { if ( server->nv.showRobotScores ) { /* this can be changed between turns */ XW_UtilCtxt* util = server->vol.util; + XW_DUtilCtxt* dutil = server->vol.dutil; XWStreamCtxt* stream; const XP_UCHAR* str; XP_UCHAR buf[128]; @@ -1025,9 +1026,9 @@ showPrevScore( ServerCtxt* server ) lp = &gi->players[prevTurn]; if ( LP_IS_LOCAL(lp) ) { - str = util_getUserString( util, STR_ROBOT_MOVED ); + str = dutil_getUserString( dutil, STR_ROBOT_MOVED ); } else { - str = util_getUserString( util, STRS_REMOTE_MOVED ); + str = dutil_getUserString( dutil, STRS_REMOTE_MOVED ); } XP_SNPRINTF( buf, sizeof(buf), str, lp->name ); str = buf; @@ -2243,8 +2244,8 @@ makeTradeReportIf( ServerCtxt* server, const TrayTileSet* tradedTiles ) if ( server->nv.showRobotScores ) { XP_UCHAR tradeBuf[64]; const XP_UCHAR* tradeStr = - util_getUserQuantityString( server->vol.util, STRD_ROBOT_TRADED, - tradedTiles->nTiles ); + dutil_getUserQuantityString( server->vol.dutil, STRD_ROBOT_TRADED, + tradedTiles->nTiles ); XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), tradeStr, tradedTiles->nTiles ); stream = mkServerStream( server ); @@ -2503,6 +2504,9 @@ server_commitMove( ServerCtxt* server, TrayTileSet* newTilesP ) } else { nextTurn( server, PICK_NEXT ); } + + XP_LOGF( "%s(): player %d now has %d tiles", __func__, turn, + model_getNumTilesInTray( model, turn ) ); return XP_TRUE; } /* server_commitMove */ @@ -2706,7 +2710,7 @@ setTurn( ServerCtxt* server, XP_S16 turn ) || (!amServer(server) || (0 == server->nv.pendingRegistrations))); if ( server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) { server->nv.currentTurn = turn; - server->nv.lastMoveTime = util_getCurSeconds( server->vol.util ); + server->nv.lastMoveTime = dutil_getCurSeconds( server->vol.dutil ); callTurnChangeListener( server ); } } @@ -3025,8 +3029,8 @@ server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream, Tile tile; XP_U16 nChars, nPrinted; XP_UCHAR buf[48]; - const XP_UCHAR* fmt = util_getUserString( server->vol.util, - STRS_VALUES_HEADER ); + const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil, + STRS_VALUES_HEADER ); const XP_UCHAR* langName; XP_ASSERT( !!server->vol.model ); @@ -3096,9 +3100,9 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream, XP_ASSERT( !!server->vol.model ); - const XP_UCHAR* fmt = util_getUserQuantityString( server->vol.util, - STRD_REMAINS_HEADER, - nLeft ); + const XP_UCHAR* fmt = dutil_getUserQuantityString( server->vol.dutil, + STRD_REMAINS_HEADER, + nLeft ); XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft ); stream_catString( stream, buf ); stream_catString( stream, "\n\n" ); @@ -3136,8 +3140,8 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream, XP_ASSERT( offset < sizeof(cntsBuf) ); } - fmt = util_getUserQuantityString( server->vol.util, STRD_REMAINS_EXPL, - nLeft ); + fmt = dutil_getUserQuantityString( server->vol.dutil, STRD_REMAINS_EXPL, + nLeft ); XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft ); stream_catString( stream, buf ); @@ -3226,10 +3230,10 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) XP_S16 quitter = server->nv.quitter; XP_Bool quitterDone = XP_FALSE; ModelCtxt* model = server->vol.model; - const XP_UCHAR* addString = util_getUserString( server->vol.util, - STRD_REMAINING_TILES_ADD ); - const XP_UCHAR* subString = util_getUserString( server->vol.util, - STRD_UNUSED_TILES_SUB ); + const XP_UCHAR* addString = dutil_getUserString( server->vol.dutil, + STRD_REMAINING_TILES_ADD ); + const XP_UCHAR* subString = dutil_getUserString( server->vol.dutil, + STRD_UNUSED_TILES_SUB ); XP_UCHAR* timeStr; CurGameInfo* gi = server->vol.gi; const XP_U16 nPlayers = gi->nPlayers; @@ -3279,9 +3283,8 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) XP_U16 penalty = player_timePenalty( gi, thisIndex ); if ( penalty > 0 ) { XP_SNPRINTF( timeBuf, sizeof(timeBuf), - util_getUserString( - server->vol.util, - STRD_TIME_PENALTY_SUB ), + dutil_getUserString( server->vol.dutil, + STRD_TIME_PENALTY_SUB ), penalty ); /* positive for formatting */ timeStr = timeBuf; } @@ -3296,12 +3299,12 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) const XP_UCHAR* name = emptyStringIfNull(gi->players[thisIndex].name); if ( 0 == placeKey ) { - const XP_UCHAR* fmt = util_getUserString( server->vol.util, + const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil, STRDSD_PLACER ); XP_SNPRINTF( buf, sizeof(buf), fmt, place, name, scores.arr[thisIndex] ); } else { - const XP_UCHAR* fmt = util_getUserString( server->vol.util, + const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil, placeKey ); XP_SNPRINTF( buf, sizeof(buf), fmt, name, scores.arr[thisIndex] ); diff --git a/xwords4/common/server.h b/xwords4/common/server.h index 0ca3a6a0f..5813a7f89 100644 --- a/xwords4/common/server.h +++ b/xwords4/common/server.h @@ -30,20 +30,6 @@ extern "C" { #endif -enum { - PHONIES_IGNORE, - PHONIES_WARN, - PHONIES_DISALLOW -}; -typedef XP_U8 XWPhoniesChoice; - -enum { - SERVER_STANDALONE, - SERVER_ISSERVER, - SERVER_ISCLIENT -}; -typedef XP_U8 DeviceRole; - /* typedef struct ServerCtxt ServerCtxt; */ /* typedef struct ServerVtable { */ diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c new file mode 100644 index 000000000..19958772a --- /dev/null +++ b/xwords4/common/smsproto.c @@ -0,0 +1,870 @@ +/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "util.h" +#include "smsproto.h" +#include "comtypes.h" +#include "strutils.h" + +# define MAX_WAIT 3 +// # define MAX_MSG_LEN 50 /* for testing */ +# define MAX_LEN_BINARY 115 +/* 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 +# define SMS_PROTO_VERSION 1 +# define SMS_PROTO_VERSION_COMBO 2 + +# define PARTIALS_FORMAT 0 + +typedef struct _MsgRec { + XP_U32 createSeconds; + SMSMsg msg; +} MsgRec; + +typedef struct _ToPhoneRec { + XP_UCHAR phone[32]; + XP_U32 createSeconds; + XP_U16 nMsgs; + XP_U16 totalSize; + MsgRec** msgs; +} ToPhoneRec; + +typedef struct _MsgIDRec { + int msgID; + int count; + struct { + XP_U16 len; + XP_U8* data; + }* parts; +} MsgIDRec; + +typedef struct _FromPhoneRec { + XP_UCHAR phone[32]; + int nMsgIDs; + MsgIDRec* msgIDRecs; +} FromPhoneRec; + +struct SMSProto { + XW_DUtilCtxt* dutil; + pthread_t creator; + XP_U16 nNextID; + int lastStoredSize; + XP_U16 nToPhones; + ToPhoneRec* toPhoneRecs; + + int nFromPhones; + FromPhoneRec* fromPhoneRecs; + + MPSLOT; +}; + +#define KEY_PARTIALS PERSIST_KEY("partials") +#define KEY_NEXTID PERSIST_KEY("nextID") + +static int nextMsgID( SMSProto* state ); +static SMSMsgArray* toMsgs( 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, + 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 ); +static void savePartials( SMSProto* state ); +static void restorePartials( SMSProto* state ); +static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex ); +static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, + int msgIDIndex ); +static void freeForPhone( SMSProto* state, const XP_UCHAR* phone ); +static void freeMsg( SMSProto* state, MsgRec** msg ); +static void freeRec( SMSProto* state, ToPhoneRec* rec ); +#ifdef DEBUG +static void checkThread( SMSProto* state ); +#else +# define checkThread(p) +#endif + +SMSProto* +smsproto_init( MPFORMAL XW_DUtilCtxt* dutil ) +{ + SMSProto* state = (SMSProto*)XP_CALLOC( mpool, sizeof(*state) ); + state->dutil = dutil; + // checkThread( state ); <-- Android's calling this on background thread now + MPASSIGN( state->mpool, mpool ); + + XP_U16 siz = sizeof(state->nNextID); + dutil_loadPtr( state->dutil, KEY_NEXTID, &state->nNextID, &siz ); + XP_LOGF( "%s(): loaded nextMsgID: %d", __func__, state->nNextID ); + + restorePartials( state ); + + return state; +} + +void +smsproto_free( SMSProto* state ) +{ + if ( NULL != state ) { + // checkThread( state ); <-- risky (see above) + XP_ASSERT( state->creator == 0 || state->creator == pthread_self() ); + + for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) { + freeRec( state, &state->toPhoneRecs[ii] ); + } + XP_FREEP( state->mpool, &state->toPhoneRecs ); + + if ( 0 < state->nFromPhones ) { + XP_LOGF( "%s(): freeing undelivered partial messages", __func__ ); + } + while (0 < state->nFromPhones) { + FromPhoneRec* ffr = &state->fromPhoneRecs[0]; + while ( 0 < ffr->nMsgIDs ) { + freeMsgIDRec( state, &ffr->msgIDRecs[0], 0, 0 ); + } + } + XP_ASSERT( !state->fromPhoneRecs ); /* above nulls this once empty */ + + XP_FREEP( state->mpool, &state ); + } +} + +/* Maintain a list of pending messages per phone number. When called and it's + * been at least some amount of time since we last added something, or at + * least some longer time since the oldest message was added, return an array + * of messages ready to send via the device's raw SMS (i.e. respecting its + * size limits.) + + * Pass in the current time, as that's easier than keeping an instance of + * UtilCtxt around. + */ +SMSMsgArray* +smsproto_prepOutbound( SMSProto* state, const XP_U8* buf, + XP_U16 buflen, const XP_UCHAR* toPhone, + XP_Bool forceOld, XP_U16* waitSecsP ) +{ + 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_FREEP( state->mpool, &checksum ); +#endif + + checkThread( state ); + ToPhoneRec* rec = getForPhone( state, toPhone, !!buf ); + + /* 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 ); + } + + /* rec will be non-null if there's something in it */ + XP_Bool doSend = XP_FALSE; + if ( rec != NULL ) { + doSend = forceOld + || rec->totalSize > SEND_NOW_SIZE + || MAX_WAIT <= nowSeconds - rec->createSeconds; + /* other criteria? */ + } + + if ( doSend ) { + result = toMsgs( state, rec, forceOld ); + freeForPhone( state, toPhone ); + } + + XP_U16 waitSecs = 0; + if ( !result && !!rec && (rec->nMsgs > 0) ) { + waitSecs = MAX_WAIT - (nowSeconds - rec->createSeconds); + } + *waitSecsP = waitSecs; + + XP_LOGF( "%s() => %p (len=%d, *waitSecs=%d)", __func__, result, + !!result ? result->nMsgs : 0, *waitSecsP ); + return result; +} + +static SMSMsgArray* +appendMsg( SMSProto* state, SMSMsgArray* arr, SMSMsg* msg ) +{ + if ( NULL == arr ) { + arr = XP_CALLOC( state->mpool, sizeof(*arr) ); + } + + arr->msgs = XP_REALLOC( state->mpool, arr->msgs, + (arr->nMsgs + 1) * sizeof(*arr->msgs) ); + arr->msgs[arr->nMsgs++] = *msg; + return arr; +} + +SMSMsgArray* +smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, + 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) ); + stream_putBytes( stream, data, len ); + + SMSMsgArray* result = NULL; + XP_U8 proto; + if ( stream_gotU8( stream, &proto ) ) { + switch ( proto ) { + case SMS_PROTO_VERSION: { + XP_U8 msgID, indx, count; + if ( stream_gotU8( stream, &msgID ) + && stream_gotU8( stream, &indx ) + && stream_gotU8( stream, &count ) + && indx < count ) { + XP_U16 len = stream_getSize( stream ); + XP_U8 buf[len]; + stream_getBytes( stream, buf, len ); + addMessage( state, fromPhone, msgID, indx, count, buf, len ); + result = completeMsgs( state, result, fromPhone, msgID ); + savePartials( state ); + } + } + break; + case SMS_PROTO_VERSION_COMBO: { + 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_MEMCPY( msg.data, buf, oneLen ); + result = appendMsg( state, result, &msg ); + } + } + } + break; + default: + XP_LOGF( "%s(): unexpected proto %d", __func__, proto ); + break; + } + } + + stream_destroy( stream ); + + XP_LOGF( "%s() => %p (len=%d)", __func__, result, (!!result) ? result->nMsgs : 0 ); + return result; +} + +void +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_FREEP( state->mpool, &arr->msgs ); + XP_FREEP( state->mpool, &arr ); +} + +static void +freeMsg( SMSProto* state, MsgRec** msgp ) +{ + XP_FREEP( state->mpool, &(*msgp)->msg.data ); + XP_FREEP( state->mpool, msgp ); +} + +static void +freeRec( SMSProto* state, ToPhoneRec* rec ) +{ + for ( XP_U16 jj = 0; jj < rec->nMsgs; ++jj ) { + freeMsg( state, &rec->msgs[jj] ); + } + XP_FREEP( state->mpool, &rec->msgs ); +} + +static ToPhoneRec* +getForPhone( SMSProto* state, const XP_UCHAR* phone, XP_Bool create ) +{ + ToPhoneRec* rec = NULL; + for ( XP_U16 ii = 0; !rec && ii < state->nToPhones; ++ii ) { + if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) { + rec = &state->toPhoneRecs[ii]; + } + } + + if ( !rec && create ) { + state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs, + (1 + state->nToPhones) * sizeof(*state->toPhoneRecs) ); + rec = &state->toPhoneRecs[state->nToPhones++]; + XP_MEMSET( rec, 0, sizeof(*rec) ); + XP_STRCAT( rec->phone, phone ); + } + + return rec; +} + +static void +freeForPhone( SMSProto* state, const XP_UCHAR* phone ) +{ + for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) { + if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) { + freeRec( state, &state->toPhoneRecs[ii] ); + + XP_U16 nAbove = state->nToPhones - ii - 1; + XP_ASSERT( nAbove >= 0 ); + if ( nAbove > 0 ) { + XP_MEMMOVE( &state->toPhoneRecs[ii], &state->toPhoneRecs[ii+1], + nAbove * sizeof(*state->toPhoneRecs) ); + } + --state->nToPhones; + if ( 0 == state->nToPhones ) { + XP_FREEP( state->mpool, &state->toPhoneRecs ); + } else { + state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs, + state->nToPhones * sizeof(*state->toPhoneRecs) ); + } + break; + } + } +} + +static void +addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf, XP_U16 buflen, + XP_U32 nowSeconds ) +{ + 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 ); + 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; + XP_LOGF( "%s(): added msg to %s of len %d; total now %d", __func__, rec->phone, + buflen, rec->totalSize ); + + if ( rec->nMsgs == 1 ) { + rec->createSeconds = nowSeconds; + } +} + +static MsgIDRec* +getMsgIDRec( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, + XP_Bool addMissing, int* fromPhoneIndex, int* msgIDIndex ) +{ + MsgIDRec* result = NULL; + + FromPhoneRec* fromPhoneRec = NULL; + for ( int ii = 0; ii < state->nFromPhones; ++ii ) { + if ( 0 == XP_STRCMP( state->fromPhoneRecs[ii].phone, fromPhone ) ) { + fromPhoneRec = &state->fromPhoneRecs[ii]; + *fromPhoneIndex = ii; + break; + } + } + + // create and add if not found + if ( NULL == fromPhoneRec && addMissing ) { + state->fromPhoneRecs = + XP_REALLOC( state->mpool, state->fromPhoneRecs, + (state->nFromPhones + 1) * sizeof(*state->fromPhoneRecs) ); + *fromPhoneIndex = state->nFromPhones; + fromPhoneRec = &state->fromPhoneRecs[state->nFromPhones++]; + XP_MEMSET( fromPhoneRec, 0, sizeof(*fromPhoneRec) ); + XP_STRCAT( fromPhoneRec->phone, fromPhone ); + } + + // Now find msgID record + if ( NULL != fromPhoneRec ) { + for ( int ii = 0; ii < fromPhoneRec->nMsgIDs; ++ii ) { + if ( fromPhoneRec->msgIDRecs[ii].msgID == msgID ) { + result = &fromPhoneRec->msgIDRecs[ii]; + *msgIDIndex = ii; + break; + } + } + + // create and add if not found + if ( NULL == result && addMissing ) { + fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs, + (fromPhoneRec->nMsgIDs + 1) + * sizeof(*fromPhoneRec->msgIDRecs) ); + MsgIDRec newRec = { .msgID = msgID }; + *msgIDIndex = fromPhoneRec->nMsgIDs; + result = &fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs]; + fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs++] = newRec; + } + } + + return result; +} + +/* Messages that are split gather here until complete + */ +static void +addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx, + int count, const XP_U8* data, XP_U16 len ) +{ + XP_LOGF( "phone=%s, msgID=%d, %d/%d", fromPhone, msgID, indx, count ); + XP_ASSERT( 0 < len ); + MsgIDRec* msgIDRec; + for ( ; ; ) { + int fromPhoneIndex; + int msgIDIndex; + msgIDRec = getMsgIDRec( state, fromPhone, msgID, XP_TRUE, + &fromPhoneIndex, &msgIDIndex ); + + /* sanity check... */ + if ( msgIDRec->count == 0 || msgIDRec->count == count ) { + break; + } + freeMsgIDRec( state, msgIDRec, fromPhoneIndex, msgIDIndex ); + } + + /* if it's new, fill in missing fields */ + if ( msgIDRec->count == 0 ) { + msgIDRec->count = count; /* in case it's new */ + msgIDRec->parts = XP_CALLOC( state->mpool, count * sizeof(*msgIDRec->parts)); + } + + XP_ASSERT( msgIDRec->parts[indx].len == 0 + || msgIDRec->parts[indx].len == len ); /* replace with same ok */ + msgIDRec->parts[indx].len = len; + XP_FREEP( state->mpool, &msgIDRec->parts[indx].data ); /* in case non-null (replacement) */ + msgIDRec->parts[indx].data = XP_MALLOC( state->mpool, len ); + XP_MEMCPY( msgIDRec->parts[indx].data, data, len ); +} + +static void +rmFromPhoneRec( SMSProto* state, int fromPhoneIndex ) +{ + FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex]; + XP_ASSERT( fromPhoneRec->nMsgIDs == 0 ); + XP_FREEP( state->mpool, &fromPhoneRec->msgIDRecs ); + + if ( --state->nFromPhones == 0 ) { + XP_FREEP( state->mpool, &state->fromPhoneRecs ); + } else { + XP_U16 nAbove = state->nFromPhones - fromPhoneIndex; + XP_ASSERT( nAbove >= 0 ); + if ( nAbove > 0 ) { + XP_MEMMOVE( &state->fromPhoneRecs[fromPhoneIndex], &state->fromPhoneRecs[fromPhoneIndex+1], + nAbove * sizeof(*state->fromPhoneRecs) ); + } + state->fromPhoneRecs = XP_REALLOC( state->mpool, state->fromPhoneRecs, + state->nFromPhones * sizeof(*state->fromPhoneRecs)); + } +} + +static void +freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex ) +{ + FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex]; + MsgIDRec* msgIDRec = &fromPhoneRec->msgIDRecs[msgIDIndex]; + XP_ASSERT( msgIDRec == rec ); + + for ( int ii = 0; ii < msgIDRec->count; ++ii ) { + XP_FREEP( state->mpool, &msgIDRec->parts[ii].data ); + } + XP_FREEP( state->mpool, &msgIDRec->parts ); + + if ( --fromPhoneRec->nMsgIDs > 0 ) { + XP_U16 nAbove = fromPhoneRec->nMsgIDs - msgIDIndex; + XP_ASSERT( nAbove >= 0 ); + if ( nAbove > 0 ) { + XP_MEMMOVE( &fromPhoneRec->msgIDRecs[msgIDIndex], &fromPhoneRec->msgIDRecs[msgIDIndex+1], + nAbove * sizeof(*fromPhoneRec->msgIDRecs) ); + } + fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs, + fromPhoneRec->nMsgIDs + * sizeof(*fromPhoneRec->msgIDRecs)); + } else { + rmFromPhoneRec( state, fromPhoneIndex ); + } +} + +static void +savePartials( SMSProto* state ) +{ + checkThread( state ); + + XWStreamCtxt* stream + = mem_stream_make_raw( MPPARM(state->mpool) + dutil_getVTManager(state->dutil) ); + stream_putU8( stream, PARTIALS_FORMAT ); + + stream_putU8( stream, state->nFromPhones ); + for ( int ii = 0; ii < state->nFromPhones; ++ii ) { + const FromPhoneRec* rec = &state->fromPhoneRecs[ii]; + stringToStream( stream, rec->phone ); + stream_putU8( stream, rec->nMsgIDs ); + for ( int jj = 0; jj < rec->nMsgIDs; ++jj ) { + MsgIDRec* mir = &rec->msgIDRecs[jj]; + stream_putU16( stream, mir->msgID ); + stream_putU8( stream, mir->count ); + + /* There's an array here. It may be sparse. Save a len of 0 */ + for ( int kk = 0; kk < mir->count; ++kk ) { + int len = mir->parts[kk].len; + stream_putU8( stream, len ); + stream_putBytes( stream, mir->parts[kk].data, len ); + } + } + } + + XP_U16 newSize = stream_getSize( stream ); + if ( state->lastStoredSize == 2 && newSize == 2 ) { + XP_LOGF( "%s(): not storing empty again", __func__ ); + } else { + dutil_storeStream( state->dutil, KEY_PARTIALS, stream ); + state->lastStoredSize = newSize; + } + + stream_destroy( stream ); + + LOG_RETURN_VOID(); +} /* savePartials */ + +static void +restorePartials( SMSProto* state ) +{ + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool) + dutil_getVTManager(state->dutil) ); + dutil_loadStream( state->dutil, KEY_PARTIALS, stream ); + if ( stream_getSize( stream ) >= 1 + && PARTIALS_FORMAT == stream_getU8( stream ) ) { + int nFromPhones = stream_getU8( stream ); + for ( int ii = 0; ii < nFromPhones; ++ii ) { + XP_UCHAR phone[32]; + (void)stringFromStreamHere( stream, phone, VSIZE(phone) ); + int nMsgIDs = stream_getU8( stream ); + XP_LOGF( "%s(): got %d message records for phone %s", __func__, + nMsgIDs, phone ); + for ( int jj = 0; jj < nMsgIDs; ++jj ) { + XP_U16 msgID = stream_getU16( stream ); + int count = stream_getU8( stream ); + XP_LOGF( "%s(): got %d records for msgID %d", __func__, count, msgID ); + for ( int kk = 0; kk < count; ++kk ) { + int len = stream_getU8( stream ); + if ( 0 < len ) { + XP_U8 buf[len]; + stream_getBytes( stream, buf, len ); + addMessage( state, phone, msgID, kk, count, buf, len ); + } + } + } + } + } + stream_destroy( stream ); +} + +static SMSMsgArray* +completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone, + int msgID ) +{ + int fromPhoneIndex, msgIDIndex; + MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE, + &fromPhoneIndex, &msgIDIndex); + if ( !rec ) { + XP_LOGF( "%s(): no rec for phone %s, msgID %d", __func__, fromPhone, msgID ); + XP_ASSERT( 0 ); + } + + int len = 0; + XP_Bool haveAll = XP_TRUE; + for ( int ii = 0; ii < rec->count; ++ii ) { + if ( rec->parts[ii].len == 0 ) { + haveAll = XP_FALSE; + break; + } else { + len += rec->parts[ii].len; + } + } + + if ( haveAll ) { + SMSMsg msg = { .len = len, + .msgID = msgID, + .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; + } + arr = appendMsg( state, arr, &msg ); + + freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex ); + } + + return arr; +} + +static SMSMsgArray* +toMsgs( 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; + + /* First, see if this message and some number of its neighbors can be + combined */ + int last = ii; + int sum = 0; + if ( count == 1 && !forceOld ) { + for ( ; last < rec->nMsgs; ++last ) { + int nextLen = rec->msgs[last]->msg.len; + if ( sum + nextLen > MAX_LEN_BINARY ) { + break; + } + sum += nextLen; + } + } + + if ( last > ii ) { + int nMsgs = last - ii; + if ( nMsgs > 1 ) { + XP_LOGF( "%s(): combining %d through %d (%d msgs)", __func__, ii, + last - 1, nMsgs ); + } + int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */ + SMSMsg 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; + 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 ); + ii = last; + } else { + int msgID = nextMsgID( state ); + const SMSMsg* msg = &rec->msgs[ii]->msg; + XP_U8* nextStart = msg->data; + XP_U16 lenLeft = msg->len; + for ( XP_U16 indx = 0; indx < count; ++indx ) { + XP_ASSERT( lenLeft > 0 ); + XP_U16 useLen = lenLeft; + if ( useLen >= MAX_LEN_BINARY ) { + useLen = MAX_LEN_BINARY; + } + lenLeft -= useLen; + + SMSMsg newMsg = { .len = useLen + 4, + .data = XP_MALLOC( state->mpool, useLen + 4 ) + }; + newMsg.data[0] = SMS_PROTO_VERSION; + newMsg.data[1] = msgID; + newMsg.data[2] = indx; + newMsg.data[3] = count; + XP_MEMCPY( newMsg.data + 4, nextStart, useLen ); + nextStart += useLen; + + result = appendMsg( state, result, &newMsg ); + } + ++ii; + } + } + + return result; +} /* toMsgs */ + +static int +nextMsgID( SMSProto* state ) +{ + int result = ++state->nNextID % 0x000000FF; + dutil_storePtr( state->dutil, KEY_NEXTID, &state->nNextID, + sizeof(state->nNextID) ); + LOG_RETURNF( "%d", result ); + return result; +} + +#ifdef DEBUG +static void +checkThread( SMSProto* state ) +{ + pthread_t curThread = pthread_self(); + if ( 0 == state->creator ) { + state->creator = curThread; + } else { + /* If this is firing will need to use a mutex to make SMSProto access + thread-safe */ + XP_ASSERT( state->creator == curThread ); + } +} + +void +smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil ) +{ + LOG_FUNC(); + SMSProto* state = smsproto_init( mpool, dutil ); + + const int smallSiz = 20; + const char* phones[] = {"1234", "3456", "5467", "9877"}; + const char* buf = "asoidfaisdfoausdf aiousdfoiu asodfu oiuasdofi oiuaosiduf oaisudf oiasd f" + ";oiaisdjfljiojaklj asdlkjalskdjf laksjd flkjasdlfkj aldsjkf lsakdjf lkjsad flkjsd fl;kj" + "asdifaoaosidfoiauosidufoaus doifuoaiusdoifu aoisudfoaisd foia sdoifuasodfu aosiud foiuas odfiu asd" + "aosdoiaosdoiisidfoiosi isoidufoisu doifuoisud oiuoi98a90iu-asjdfoiasdfij" + ; + const XP_Bool forceOld = XP_TRUE; + + SMSMsgArray* arrs[VSIZE(phones)]; + for ( int ii = 0; ii < VSIZE(arrs); ++ii ) { + arrs[ii] = NULL; + } + + /* Loop until all the messages are ready. */ + 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 ); + } else if ( NULL == arrs[ii]) { + arrs[ii] = smsproto_prepOutbound( state, NULL, 0, phones[ii], + forceOld, &waitSecs ); + } else { + continue; + } + allDone = allDone & (waitSecs == 0 && !!arrs[ii]); + } + if ( allDone ) { + break; + } else { + (void)sleep( 2 ); + } + } + + for ( int indx = 0; ; ++indx ) { + XP_Bool haveOne = XP_FALSE; + for ( int ii = 0; ii < VSIZE(arrs); ++ii ) { + if (!!arrs[ii] && indx < arrs[ii]->nMsgs) { + haveOne = XP_TRUE; + SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii], + arrs[ii]->msgs[indx].data, + arrs[ii]->msgs[indx].len ); + if ( !!outArr ) { + SMSMsg* msg = &outArr->msgs[0]; + XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID ); + XP_ASSERT( outArr->nMsgs == 1 ); + XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) ); + smsproto_freeMsgArray( state, outArr ); + + smsproto_freeMsgArray( state, arrs[ii] ); + arrs[ii] = NULL; + } + } + } + if (!haveOne) { + break; + } + } + + /* Now let's send a bunch of small messages that should get combined */ + for ( int nUsed = 0; ; ++nUsed ) { + XP_U16 waitSecs; + SMSMsgArray* sendArr = smsproto_prepOutbound( state, (XP_U8*)&buf[nUsed], + smallSiz, phones[0], + XP_FALSE, &waitSecs ); + if ( sendArr == NULL ) { + XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz ); + continue; + } + + XP_ASSERT( waitSecs == 0 ); + 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 ); + + if ( !!recvArr ) { + 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 ); + XP_ASSERT( msg->len == smallSiz ); + XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) ); + ++totalBack; + } + + smsproto_freeMsgArray( state, recvArr ); + } + } + XP_ASSERT( forceOld || totalBack == nUsed + 1 ); + XP_LOGF( "%s(): %d messages checked out", __func__, totalBack ); + smsproto_freeMsgArray( state, sendArr ); + break; + } + + /* Now let's add a too-long message and unpack only the first part. Make + sure it's cleaned up correctly */ + XP_U16 waitSecs; + SMSMsgArray* arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", 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 ); + XP_ASSERT( !out ); + smsproto_freeMsgArray( state, arr ); + + + /* now a message that's unpacked across multiple sessions to test store/load */ + XP_LOGF( "%s(): testing store/restore", __func__ ); + arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs ); + for ( int ii = 0; ii < arr->nMsgs; ++ii ) { + SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data, + arr->msgs[ii].len ); + if ( !!out ) { + XP_ASSERT( out->nMsgs == 1); + XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii ); + XP_ASSERT( out->msgs[0].len == 200 ); + XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) ); + smsproto_freeMsgArray( state, out ); + break; + } + smsproto_free( state ); /* give it a chance to store state */ + state = smsproto_init( mpool, dutil ); + } + + /* Really bad to pass a different state than was created with, but now + since only mpool is used and it's the same for all states, let it + go. */ + smsproto_freeMsgArray( state, arr ); /* give it a chance to store state */ + + smsproto_free( state ); + LOG_RETURN_VOID(); +} +#endif diff --git a/xwords4/common/smsproto.h b/xwords4/common/smsproto.h new file mode 100644 index 000000000..76ad7c88f --- /dev/null +++ b/xwords4/common/smsproto.h @@ -0,0 +1,85 @@ +/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SMSPROTO_H__ +#define __SMSPROTO_H__ + +// The sms protocol is getting more complicated as I add message combining. So +// let's try to move it into C where it can be tested in linux. +// +// How the protocol works. Clients want to send data packets to phones and +// receive from phones. They use a send(byte[] data, String phone) method, and +// provide a callback that's called when packets arrive. Internally this +// module buffers messages on a per-number basis (new post-java feature) and +// periodically, based on a timer that's set when buffered data is waiting to +// be sent, gathers messages and transmits them. Probably it'll wait a few +// seconds after the last message was enqueued, the idea being that SMS is not +// expected to be fast and that sending lots of small messages is bad. +// +// Because there's a max size to SMS messages any [combined] message that's +// too big is broken up. To keep things simple the two processes, combining +// and breaking, are independent; there's no attempt to avoid combining +// messages even when doing so creates something that will have to be broken +// up. +// +// Received messages (buffers) are recombined (if the result of breaking up) +// then split (if the result of combining), and the constituent data packets +// are returned to the app as an array of byte[]. + +#include "xptypes.h" +#include "mempool.h" /* debug only */ + +typedef struct SMSProto SMSProto; + +typedef struct _SMSMsg { + XP_U16 len; + XP_U16 msgID; + XP_U8* data; +} SMSMsg; + +typedef struct _SMSMsgArray { + XP_U16 nMsgs; + SMSMsg* msgs; +} 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. + * + * 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, + 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 ); + +void smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr ); + +# ifdef DEBUG +void smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil ); +# endif +#endif diff --git a/xwords4/common/strutils.c b/xwords4/common/strutils.c index 9ba8a6da3..7ad645b8d 100644 --- a/xwords4/common/strutils.c +++ b/xwords4/common/strutils.c @@ -179,6 +179,26 @@ stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str ) stream_putBytes( stream, str, len ); } /* putStringToStream */ +XP_Bool +stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr ) +{ + XP_Bool success = sizeof(*ptr) <= stream_getSize( stream ); + if ( success ) { + *ptr = stream_getU8( stream ); + } + return success; +} + +XP_Bool +stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len ) +{ + XP_Bool success = len <= stream_getSize( stream ); + if ( success ) { + stream_getBytes( stream, ptr, len ); + } + return success; +} + /***************************************************************************** * ****************************************************************************/ diff --git a/xwords4/common/strutils.h b/xwords4/common/strutils.h index 1451e6dc7..f35a92630 100644 --- a/xwords4/common/strutils.h +++ b/xwords4/common/strutils.h @@ -56,6 +56,9 @@ XP_UCHAR* p_stringFromStream( MPFORMAL XWStreamCtxt* stream 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_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len ); + XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr #ifdef MEM_DEBUG , const char* file, const char* func, XP_U32 lineNo diff --git a/xwords4/common/util.h b/xwords4/common/util.h index 85a726df8..a3b67a96b 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -1,6 +1,6 @@ /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ /* - * Copyright 1997 - 2010 by Eric House (xwords@eehouse.org). All rights + * Copyright 1997 - 2018 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -26,9 +26,8 @@ #include "dawg.h" #include "model.h" #include "board.h" -#include "mempool.h" -#include "vtabmgr.h" #include "comms.h" +#include "dutil.h" #include "xwrelay.h" @@ -87,8 +86,6 @@ typedef XP_Bool (*XWTimerProc)( void* closure, XWTimerReason why ); */ typedef struct UtilVtable { - VTableMgr* (*m_util_getVTManager)(XW_UtilCtxt* uc); - #ifndef XWFEATURE_STANDALONE_ONLY XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ); @@ -144,21 +141,8 @@ typedef struct UtilVtable { void (*m_util_requestTime)( XW_UtilCtxt* uc ); XP_Bool (*m_util_altKeyDown)( XW_UtilCtxt* uc ); - - XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc ); -#ifdef XWFEATURE_DEVID - const XP_UCHAR* (*m_util_getDevID)( XW_UtilCtxt* uc, DevIDType* typ ); - void (*m_util_deviceRegistered)( XW_UtilCtxt* uc, DevIDType typ, - const XP_UCHAR* idRelay ); -#endif DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc ); - const XP_UCHAR* (*m_util_getUserString)( XW_UtilCtxt* uc, - XP_U16 stringCode ); - const XP_UCHAR* (*m_util_getUserQuantityString)( XW_UtilCtxt* uc, - XP_U16 stringCode, - XP_U16 quantity ); - void (*m_util_notifyIllegalWords)( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 turn, XP_Bool turnLost ); @@ -172,11 +156,6 @@ typedef struct UtilVtable { void (*m_util_cellSquareHeld)( XW_UtilCtxt* uc, XWStreamCtxt* words ); #endif -#ifdef XWFEATURE_SMS - XP_Bool (*m_util_phoneNumbersSame)( XW_UtilCtxt* uc, const XP_UCHAR* p1, - const XP_UCHAR* p2 ); -#endif - #ifndef XWFEATURE_STANDALONE_ONLY void (*m_util_informMissing)(XW_UtilCtxt* uc, XP_Bool isServer, const CommsAddrRec* addr, XP_U16 nDevs, @@ -201,9 +180,7 @@ typedef struct UtilVtable { void (*m_util_engineStopping)( XW_UtilCtxt* uc ); #endif -#ifdef COMMS_CHECKSUM - XP_UCHAR* (*m_util_md5sum)( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len ); -#endif + XW_DUtilCtxt* (*m_util_getDevUtilCtxt)( XW_UtilCtxt* uc ); } UtilVtable; @@ -217,9 +194,6 @@ struct XW_UtilCtxt { MPSLOT }; -#define util_getVTManager(uc) \ - (uc)->vtable->m_util_getVTManager((uc)) - #define util_makeStreamFromAddr(uc,a) \ (uc)->vtable->m_util_makeStreamFromAddr((uc),(a)) @@ -286,24 +260,9 @@ struct XW_UtilCtxt { #define util_altKeyDown( uc ) \ (uc)->vtable->m_util_altKeyDown((uc)) -#define util_getCurSeconds(uc) \ - (uc)->vtable->m_util_getCurSeconds((uc)) - -#ifdef XWFEATURE_DEVID -# define util_getDevID( uc, t ) \ - (uc)->vtable->m_util_getDevID((uc),(t)) -# define util_deviceRegistered( uc, typ, id ) \ - (uc)->vtable->m_util_deviceRegistered( (uc), (typ), (id) ) -#endif - #define util_makeEmptyDict( uc ) \ (uc)->vtable->m_util_makeEmptyDict((uc)) -#define util_getUserString( uc, c ) \ - (uc)->vtable->m_util_getUserString((uc),(c)) -#define util_getUserQuantityString( uc, c, q ) \ - (uc)->vtable->m_util_getUserQuantityString((uc),(c),(q)) - #define util_notifyIllegalWords( uc, w, p, b ) \ (uc)->vtable->m_util_notifyIllegalWords((uc),(w),(p),(b)) @@ -320,10 +279,6 @@ struct XW_UtilCtxt { #define util_cellSquareHeld(uc, s) \ (uc)->vtable->m_util_cellSquareHeld( (uc), (s) ) #endif -#ifdef XWFEATURE_SMS -#define util_phoneNumbersSame(uc,p1,p2) \ - (uc)->vtable->m_util_phoneNumbersSame( (uc), (p1), (p2) ) -#endif #ifndef XWFEATURE_STANDALONE_ONLY # define util_informMissing( uc, is, ct, nd, nm ) \ @@ -355,8 +310,7 @@ struct XW_UtilCtxt { # define util_engineStopping( uc ) # endif -#ifdef COMMS_CHECKSUM -# define util_md5sum( uc, p, l ) (uc)->vtable->m_util_md5sum((uc), (p), (l)) -#endif +# define util_getDevUtilCtxt(uc) \ + (uc)->vtable->m_util_getDevUtilCtxt( (uc) ) #endif diff --git a/xwords4/linux/.gitignore b/xwords4/linux/.gitignore index 3f284af16..053b23d2b 100644 --- a/xwords4/linux/.gitignore +++ b/xwords4/linux/.gitignore @@ -8,3 +8,4 @@ discon_ok2.sh_logs test_backsend.sh_* *.db dawg2dict +discon_ok2.py_logs \ No newline at end of file diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 801a2e8e2..c721d4bdc 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -225,6 +225,7 @@ OBJ = \ $(BUILD_PLAT_DIR)/linuxutl.o \ $(BUILD_PLAT_DIR)/gamesdb.o \ $(BUILD_PLAT_DIR)/relaycon.o \ + $(BUILD_PLAT_DIR)/lindutil.o \ $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index d7ebc15db..823b815ba 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -64,6 +64,7 @@ #include "linuxudp.h" #include "gamesdb.h" #include "relaycon.h" +#include "smsproto.h" #ifdef CURSES_SMALL_SCREEN # define MENU_WINDOW_HEIGHT 1 @@ -346,9 +347,8 @@ cursesShowFinalScores( CursesAppGlobals* globals ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) - globals->cGlobals.params->vtMgr, - globals, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool) + globals->cGlobals.params->vtMgr ); server_writeFinalScores( globals->cGlobals.game.server, stream ); text = strFromStream( stream ); @@ -1363,13 +1363,6 @@ initClientSocket( CursesAppGlobals* globals, char* serverName ) } /* initClientSocket */ #endif -static VTableMgr* -curses_util_getVTManager(XW_UtilCtxt* uc) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - return globals->cGlobals.params->vtMgr; -} /* linux_util_getVTManager */ - static void curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 XP_UNUSED_DBG(playerNum), @@ -1413,9 +1406,8 @@ curses_util_remSelected( XW_UtilCtxt* uc ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) - globals->cGlobals.params->vtMgr, - globals, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool) + globals->cGlobals.params->vtMgr ); board_formatRemainingTiles( globals->cGlobals.game.board, stream ); text = strFromStream( stream ); @@ -1464,7 +1456,6 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) { util->vtable->m_util_userError = curses_util_userError; - util->vtable->m_util_getVTManager = curses_util_getVTManager; util->vtable->m_util_informNeedPassword = curses_util_informNeedPassword; util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange; #ifdef XWFEATURE_TURNCHANGENOTIFY @@ -1740,7 +1731,8 @@ curses_requestMsgs( gpointer data ) { CursesAppGlobals* globals = (CursesAppGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch( globals->cGlobals.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch_safe( globals->cGlobals.params->pDb, KEY_RDEVID, devIDBuf, + sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { relaycon_requestMsgs( globals->cGlobals.params, devIDBuf ); } else { @@ -1772,13 +1764,13 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID, { CursesAppGlobals* globals = (CursesAppGlobals*)closure; CommonGlobals* cGlobals = &globals->cGlobals; - sqlite3* pDb = cGlobals->pDb; + sqlite3* pDb = cGlobals->params->pDb; if ( !!devID ) { XP_LOGF( "%s(devID=%s)", __func__, devID ); /* If we already have one, make sure it's the same! Else store. */ gchar buf[64]; - XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) ) + XP_Bool have = db_fetch_safe( pDb, KEY_RDEVID, buf, sizeof(buf) ) && 0 == strcmp( buf, devID ); if ( !have ) { db_store( pDb, KEY_RDEVID, devID ); @@ -1998,9 +1990,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) XP_Bool idIsNew = XP_TRUE; if ( !!params->dbName ) { - sqlite3* pDb = openGamesDB( params->dbName ); - /* Gross that both need to be set, but they do. */ - params->pDb = g_globals.cGlobals.pDb = pDb; + params->pDb = openGamesDB( params->dbName ); /* Check if we have a local ID already. If we do and it's changed, we care. */ @@ -2026,18 +2016,18 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #ifdef XWFEATURE_SMS gchar buf[32]; - const gchar* myPhone = params->connInfo.sms.phone; + const gchar* myPhone = params->connInfo.sms.myPhone; if ( !!myPhone ) { db_store( params->pDb, KEY_SMSPHONE, myPhone ); - } else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { - params->connInfo.sms.phone = myPhone = buf; + } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { + params->connInfo.sms.myPhone = myPhone = buf; } XP_U16 myPort = params->connInfo.sms.port; gchar portbuf[8]; if ( 0 < myPort ) { sprintf( portbuf, "%d", myPort ); db_store( params->pDb, KEY_SMSPORT, portbuf ); - } else if ( db_fetch( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) { + } else if ( db_fetch_safe( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) { params->connInfo.sms.port = myPort = atoi( portbuf ); } @@ -2049,6 +2039,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) }; linux_sms_init( params, myPhone, myPort, &smsProcs, &g_globals.cGlobals ); } + + if ( params->runSMSTest ) { + smsproto_runTests(g_globals.cGlobals.util->mpool, + g_globals.cGlobals.params->dutil ); + } #endif XWStreamCtxt* stream = NULL; @@ -2056,9 +2051,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) GSList* games = listGames( params->pDb ); if ( !!games ) { XP_ASSERT( 1 == g_slist_length(games) ); /* for now */ - stream = mem_stream_make( MEMPOOL params->vtMgr, - &g_globals.cGlobals, CHANNEL_NONE, - NULL ); + stream = mem_stream_make_raw( MEMPOOL params->vtMgr); sqlite3_int64 selRow = *(sqlite3_int64*)games->data; /* XP_UCHAR buf[32]; */ /* XP_SNPRINTF( buf, sizeof(buf), "%lld", selRow ); */ @@ -2074,14 +2067,13 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } else if ( !!params->fileName && file_exists( params->fileName ) ) { mpool_setTag( MEMPOOL "file" ); - stream = streamFromFile( &g_globals.cGlobals, params->fileName, - &g_globals ); + stream = streamFromFile( &g_globals.cGlobals, params->fileName ); #ifdef USE_SQLITE } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { XP_UCHAR buf[32]; XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); mpool_setTag( MEMPOOL buf ); - stream = streamFromDB( &g_globals.cGlobals, &g_globals ); + stream = streamFromDB( &g_globals.cGlobals ); #endif } @@ -2141,7 +2133,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) # ifdef XWFEATURE_SMS case COMMS_CONN_SMS: addr_addType( &addr, COMMS_CONN_SMS ); - XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.phone, + XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.myPhone, sizeof(addr.u.sms.phone) - 1 ); addr.u.sms.port = params->connInfo.sms.port; break; @@ -2238,7 +2230,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) endwin(); if ( !!params->dbName ) { - closeGamesDB( g_globals.cGlobals.pDb ); + closeGamesDB( params->pDb ); } relaycon_cleanup( params ); diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index 778417c52..a2b53e204 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -27,8 +27,9 @@ #define SNAP_WIDTH 150 #define SNAP_HEIGHT 150 -static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, - int len ); +static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, + int* len ); + #ifdef DEBUG static char* sqliteErr2str( int err ); #endif @@ -160,13 +161,16 @@ writeToDB( XWStreamCtxt* stream, void* closure ) { CommonGlobals* cGlobals = (CommonGlobals*)closure; sqlite3_int64 selRow = cGlobals->selRow; - sqlite3* pDb = cGlobals->pDb; + sqlite3* pDb = cGlobals->params->pDb; XP_Bool newGame = -1 == selRow; selRow = writeBlobColumnStream( stream, pDb, selRow, "game" ); if ( newGame ) { /* new row; need to insert blob first */ cGlobals->selRow = selRow; + XP_LOGF( "%s(): new game at row %lld", __func__, selRow ); + } else { + assert( selRow == cGlobals->selRow ); } (*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame ); @@ -184,11 +188,12 @@ addSnapshot( CommonGlobals* cGlobals ) addSurface( dctx, SNAP_WIDTH, SNAP_HEIGHT ); board_drawSnapshot( board, (DrawCtx*)dctx, SNAP_WIDTH, SNAP_HEIGHT ); - XWStreamCtxt* stream = make_simple_stream( cGlobals ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); getImage( dctx, stream ); removeSurface( dctx ); - writeBlobColumnStream( stream, cGlobals->pDb, cGlobals->selRow, "snap" ); - // XP_ASSERT( cGlobals->selRow == newRow ); + cGlobals->selRow = writeBlobColumnStream( stream, cGlobals->params->pDb, + cGlobals->selRow, "snap" ); stream_destroy( stream ); } @@ -264,8 +269,8 @@ summarize( CommonGlobals* cGlobals ) cGlobals->selRow ); XP_LOGF( "query: %s", buf ); sqlite3_stmt* stmt = NULL; - int result = sqlite3_prepare_v2( cGlobals->pDb, buf, -1, &stmt, NULL ); - assertPrintResult( cGlobals->pDb, result, SQLITE_OK ); + int result = sqlite3_prepare_v2( cGlobals->params->pDb, buf, -1, &stmt, NULL ); + assertPrintResult( cGlobals->params->pDb, result, SQLITE_OK ); result = sqlite3_step( stmt ); if ( SQLITE_DONE != result ) { XP_LOGF( "sqlite3_step=>%s", sqliteErr2str( result ) ); @@ -337,7 +342,8 @@ getRelayIDsToRowsMap( sqlite3* pDb ) case SQLITE_ROW: /* have data */ { XP_UCHAR relayID[32]; - getColumnText( ppStmt, 0, relayID, VSIZE(relayID) ); + int len = VSIZE(relayID); + getColumnText( ppStmt, 0, relayID, &len ); gpointer key = g_strdup( relayID ); sqlite3_int64* value = g_malloc( sizeof( value ) ); *value = sqlite3_column_int64( ppStmt, 1 ); @@ -375,7 +381,8 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) if ( SQLITE_ROW == result ) { success = XP_TRUE; int col = 0; - getColumnText( ppStmt, col++, gib->room, sizeof(gib->room) ); + int len = sizeof(gib->room); + getColumnText( ppStmt, col++, gib->room, &len ); gib->gameOver = 1 == sqlite3_column_int( ppStmt, col++ ); gib->turn = sqlite3_column_int( ppStmt, col++ ); gib->turnLocal = 1 == sqlite3_column_int( ppStmt, col++ ); @@ -383,10 +390,12 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) gib->nTotal = sqlite3_column_int( ppStmt, col++ ); gib->nMissing = sqlite3_column_int( ppStmt, col++ ); gib->seed = sqlite3_column_int( ppStmt, col++ ); - getColumnText( ppStmt, col++, gib->conn, sizeof(gib->conn) ); + len = sizeof(gib->conn); + getColumnText( ppStmt, col++, gib->conn, &len ); gib->gameID = sqlite3_column_int( ppStmt, col++ ); gib->lastMoveTime = sqlite3_column_int( ppStmt, col++ ); - getColumnText( ppStmt, col++, gib->relayID, sizeof(gib->relayID) ); + len = sizeof(gib->relayID); + getColumnText( ppStmt, col++, gib->relayID, &len ); snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid ); #ifdef PLATFORM_GTK @@ -488,6 +497,7 @@ loadInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) { + XP_ASSERT( !!pDb ); char query[256]; snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); sqlite3_stmt* ppStmt; @@ -502,10 +512,10 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) void db_store( sqlite3* pDb, const gchar* key, const gchar* value ) { - char buf[256]; - snprintf( buf, sizeof(buf), - "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", - key, value ); + XP_ASSERT( !!pDb ); + gchar* buf = + g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", + key, value ); sqlite3_stmt *ppStmt; int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); assertPrintResult( pDb, result, SQLITE_OK ); @@ -513,33 +523,51 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value ) assertPrintResult( pDb, result, SQLITE_DONE ); XP_USE( result ); sqlite3_finalize( ppStmt ); + g_free( buf ); } -XP_Bool -db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +FetchResult +db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ) { + XP_ASSERT( !!pDb ); + FetchResult result = NOT_THERE; char query[256]; snprintf( query, sizeof(query), "SELECT value from pairs where key = '%s'", key ); sqlite3_stmt *ppStmt; - int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); - XP_Bool found = SQLITE_OK == result; + int sqlResult = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_Bool found = SQLITE_OK == sqlResult; if ( found ) { result = sqlite3_step( ppStmt ); found = SQLITE_ROW == result; if ( found ) { - getColumnText( ppStmt, 0, buf, buflen ); - } else { + if ( getColumnText( ppStmt, 0, buf, buflen ) ) { + result = SUCCESS; + } else { + result = BUFFER_TOO_SMALL; + } + } else if ( !!buf ) { buf[0] = '\0'; } } sqlite3_finalize( ppStmt ); - return found; + return result; +} + +XP_Bool +db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +{ + XP_ASSERT( !!pDb ); + int tmp = buflen; + FetchResult result = db_fetch( pDb, key, buf, &tmp ); + XP_ASSERT( result != BUFFER_TOO_SMALL ); + return SUCCESS == result; } void db_remove( sqlite3* pDb, const gchar* key ) { + XP_ASSERT( !!pDb ); char query[256]; snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); sqlite3_stmt *ppStmt; @@ -551,15 +579,19 @@ db_remove( sqlite3* pDb, const gchar* key ) sqlite3_finalize( ppStmt ); } -static void -getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, - int XP_UNUSED_DBG(len) ) +static XP_Bool +getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len ) { - const unsigned char* txt = sqlite3_column_text( ppStmt, iCol ); - int needLen = sqlite3_column_bytes( ppStmt, iCol ); - XP_ASSERT( needLen < len ); - XP_MEMCPY( buf, txt, needLen ); - buf[needLen] = '\0'; + int colLen = sqlite3_column_bytes( ppStmt, iCol ); + + XP_Bool success = colLen < *len; + *len = colLen + 1; /* sqlite does not store the null byte */ + if ( success ) { + const unsigned char* colTxt = sqlite3_column_text( ppStmt, iCol ); + XP_MEMCPY( buf, colTxt, colLen ); + buf[colLen] = '\0'; + } + return success; } #ifdef DEBUG diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index e9803b88f..7ba93d5a3 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -47,7 +47,7 @@ typedef struct _GameInfo { } GameInfo; sqlite3* openGamesDB( const char* dbName ); -void closeGamesDB( sqlite3* dbp ); +void closeGamesDB( sqlite3* pDb ); void writeToDB( XWStreamCtxt* stream, void* closure ); sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb ); @@ -55,15 +55,15 @@ sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb ); void summarize( CommonGlobals* cGlobals ); /* Return GSList whose data is (ptrs to) rowids */ -GSList* listGames( sqlite3* dbp ); +GSList* listGames( sqlite3* pDb ); /* free list and data allocated by above */ void freeGamesList( GSList* games ); /* Mapping of relayID -> rowid */ GHashTable* getRelayIDsToRowsMap( sqlite3* pDb ); -XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib ); -void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids, +XP_Bool getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ); +void getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids, int* nRowIDs ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, @@ -78,8 +78,12 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); #define KEY_SMSPORT "SMSPORT" #define KEY_WIN_LOC "WIN_LOC" -void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); -XP_Bool db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); -void db_remove( sqlite3* dbp, const gchar* key ); +void db_store( sqlite3* pDb, const gchar* key, const gchar* value ); +void db_remove( sqlite3* pDb, const gchar* key ); + +typedef enum { NOT_THERE, BUFFER_TOO_SMALL, SUCCESS } FetchResult; +FetchResult db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ); +XP_Bool db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ); + #endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 1e506edea..1849cdbc7 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -86,7 +86,7 @@ static void gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout ); static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, XP_U32 devID, const XP_UCHAR* relayID, - const XP_UCHAR* phone ); + const CommsAddrRec* addrs ); #define GTK_TRAY_HT_ROWS 3 @@ -589,19 +589,18 @@ createOrLoadObjects( GtkGameGlobals* globals ) setTransportProcs( &procs, globals ); if ( !!params->fileName && file_exists( params->fileName ) ) { - stream = streamFromFile( cGlobals, params->fileName, globals ); + stream = streamFromFile( cGlobals, params->fileName ); #ifdef USE_SQLITE } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { XP_UCHAR buf[32]; XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); mpool_setTag( MEMPOOL buf ); - stream = streamFromDB( cGlobals, globals ); + stream = streamFromDB( cGlobals ); #endif - } else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) { - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - params->vtMgr, - cGlobals, CHANNEL_NONE, NULL ); - if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) { + } else if ( !!params->pDb && 0 <= cGlobals->selRow ) { + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); + if ( !loadGame( stream, params->pDb, cGlobals->selRow ) ) { stream_destroy( stream ); stream = NULL; } @@ -874,10 +873,9 @@ on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) CommsCtxt* comms = cGlobals->game.comms; if ( !!comms /*&& COMMS_CONN_NONE == comms_getConTypes( comms )*/ ) { /* If it has pending invite info, send the invitation! */ - XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - cGlobals, CHANNEL_NONE, NULL ); - if ( loadInviteAddrs( stream, cGlobals->pDb, cGlobals->selRow ) ) { + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); + if ( loadInviteAddrs( stream, cGlobals->params->pDb, cGlobals->selRow ) ) { CommsAddrRec addr = {0}; addrFromStream( &addr, stream ); comms_setAddr( cGlobals->game.comms, &addr ); @@ -1622,21 +1620,21 @@ handle_invite_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) XP_LOGF( "%s: inviteDlg => %d", __func__, confirmed ); if ( confirmed ) { - send_invites( cGlobals, nPlayers, devID, NULL, NULL ); + send_invites( cGlobals, nPlayers, devID, NULL, &inviteAddr ); } } /* handle_invite_button */ static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, XP_U32 devID, const XP_UCHAR* relayID, - const XP_UCHAR* phone ) + const CommsAddrRec* addrs ) { CommsAddrRec addr = {0}; CommsCtxt* comms = cGlobals->game.comms; XP_ASSERT( comms ); comms_getAddr( comms, &addr ); - gint forceChannel = 0; /* PENDING */ + gint forceChannel = 1; /* 1 is what Android does. Limits to two-device games */ NetLaunchInfo nli = {0}; nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel ); @@ -1647,9 +1645,8 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, #ifdef DEBUG { - XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); nli_saveToStream( &nli, stream ); NetLaunchInfo tmp; nli_makeFromStream( &tmp, stream ); @@ -1658,11 +1655,13 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, } #endif - if ( !!phone ) { - XP_ASSERT( 0 ); /* not implemented */ - /* linux_sms_invite( cGlobals->params, gi, &addr, gameName, */ - /* nPlayers, forceChannel, */ - /* inviteAddr.u.sms.phone, inviteAddr.u.sms.port ); */ + if ( !!addrs->u.sms.phone ) { + gchar gameName[64]; + snprintf( gameName, VSIZE(gameName), "Game %d", cGlobals->gi->gameID ); + + linux_sms_invite( cGlobals->params, cGlobals->gi, &addr, gameName, + nPlayers, forceChannel, + addrs->u.sms.phone, addrs->u.sms.port ); } if ( 0 != devID || !!relayID ) { XP_ASSERT( 0 != devID || (!!relayID && !!relayID[0]) ); @@ -1722,14 +1721,6 @@ gtkUserError( GtkGameGlobals* globals, const char* format, ... ) va_end(ap); } /* gtkUserError */ -static VTableMgr* -gtk_util_getVTManager(XW_UtilCtxt* uc) -{ - GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - return globals->cGlobals.params->vtMgr; -} /* linux_util_getVTManager */ - - static gint ask_blank( gpointer data ) { @@ -1934,9 +1925,8 @@ gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout ) XP_UCHAR* text; const CommonGlobals* cGlobals = &globals->cGlobals; - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); server_writeFinalScores( cGlobals->game.server, stream ); text = strFromStream( stream ); @@ -2272,9 +2262,8 @@ gtk_util_remSelected( XW_UtilCtxt* uc ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MEMPOOL + globals->cGlobals.params->vtMgr ); board_formatRemainingTiles( globals->cGlobals.game.board, stream ); text = strFromStream( stream ); stream_destroy( stream ); @@ -2570,7 +2559,6 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_userError = gtk_util_userError; util->vtable->m_util_notifyMove = gtk_util_notifyMove; util->vtable->m_util_notifyTrade = gtk_util_notifyTrade; - util->vtable->m_util_getVTManager = gtk_util_getVTManager; util->vtable->m_util_notifyPickTileBlank = gtk_util_notifyPickTileBlank; util->vtable->m_util_informNeedPickTiles = gtk_util_informNeedPickTiles; util->vtable->m_util_informNeedPassword = gtk_util_informNeedPassword; @@ -2935,11 +2923,9 @@ loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, CommonGlobals* cGlobals = &globals->cGlobals; cGlobals->selRow = rowid; - cGlobals->pDb = pDb; - XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - params->vtMgr, cGlobals, - CHANNEL_NONE, NULL ); - XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); + XP_Bool loaded = loadGame( stream, pDb, rowid ); if ( loaded ) { if ( NULL == cGlobals->dict ) { cGlobals->dict = makeDictForStream( cGlobals, stream ); diff --git a/xwords4/linux/gtkconnsdlg.c b/xwords4/linux/gtkconnsdlg.c index 311182799..cd0bd74e6 100644 --- a/xwords4/linux/gtkconnsdlg.c +++ b/xwords4/linux/gtkconnsdlg.c @@ -305,7 +305,7 @@ makeSMSPage( GtkConnsState* state, PageData* data ) GtkWidget* vbox = boxWithUseCheck( state, data ); XP_Bool hasSMS = addr_hasType( state->addr, data->pageType ); const gchar* phone = hasSMS ? - state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.phone; + state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.myPhone; GtkWidget* hbox = makeLabeledField( "My phone", &state->smsphone, phone ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); gtk_widget_set_sensitive( state->smsphone, !state->readOnly ); diff --git a/xwords4/linux/gtkinvit.c b/xwords4/linux/gtkinvit.c index 01809e04f..d2ca40fc1 100644 --- a/xwords4/linux/gtkinvit.c +++ b/xwords4/linux/gtkinvit.c @@ -237,7 +237,7 @@ makeSMSPage( GtkInviteState* state, PageData* data ) GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); XP_Bool hasSMS = addr_hasType( state->addr, data->pageType ); const gchar* phone = hasSMS ? - state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.phone; + state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.myPhone; GtkWidget* hbox = makeLabeledField( "Invitee phone", &state->smsphone, phone ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index e7b83f83f..453931ba2 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -21,11 +21,13 @@ #ifdef PLATFORM_GTK #include "strutils.h" +#include "smsproto.h" #include "main.h" #include "gtkmain.h" #include "gamesdb.h" #include "gtkboard.h" #include "linuxmain.h" +#include "linuxutl.h" #include "relaycon.h" #include "linuxsms.h" #include "gtkask.h" @@ -275,7 +277,6 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) freeGlobals( globals ); } else { GtkWidget* gameWindow = globals->window; - globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); @@ -293,7 +294,6 @@ open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew ) apg->params->needsNewGame = XP_FALSE; GtkGameGlobals* globals = malloc( sizeof(*globals) ); initGlobals( globals, apg->params, NULL ); - globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = row; recordOpened( apg, globals ); gtk_widget_show( globals->window ); @@ -315,10 +315,10 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) void make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) { - // LaunchParams* params = apg->params; - XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + LaunchParams* params = apg->params; + XP_ASSERT( params == cGlobals->params ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); /* Create new game. But has no addressing info, so need to set that aside for later. */ @@ -333,16 +333,15 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) game_saveNewGame( MPPARM(cGlobals->util->mpool) &gi, cGlobals->util, &cGlobals->cp, stream ); - sqlite3_int64 rowID = writeNewGameToDB( stream, cGlobals->pDb ); + sqlite3_int64 rowID = writeNewGameToDB( stream, params->pDb ); stream_destroy( stream ); gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi ); /* If it's a multi-device game, save enough information with it than when opened it can invite the other device[s] join the rematch. */ if ( !!comms ) { - XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); CommsAddrRec addr; comms_getAddr( comms, &addr ); addrToStream( stream, &addr ); @@ -364,7 +363,7 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) } addrToStream( stream, &addrs[ii] ); } - saveInviteAddrs( stream, cGlobals->pDb, rowID ); + saveInviteAddrs( stream, params->pDb, rowID ); stream_destroy( stream ); } @@ -406,7 +405,7 @@ handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure ) deleteGame( params->pDb, rowid ); XP_UCHAR devIDBuf[64] = {0}; - db_fetch( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch_safe( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { relaycon_deleted( params, devIDBuf, clientToken ); } else { @@ -482,7 +481,7 @@ setWindowTitle( GtkAppGlobals* apg ) #ifdef XWFEATURE_SMS int len = strlen( title ); snprintf( &title[len], VSIZE(title) - len, " (phone: %s, port: %d)", - params->connInfo.sms.phone, params->connInfo.sms.port ); + params->connInfo.sms.myPhone, params->connInfo.sms.port ); #endif #ifdef XWFEATURE_RELAY XP_U32 relayID = linux_getDevIDRelay( params ); @@ -501,7 +500,7 @@ trySetWinConfig( GtkAppGlobals* apg ) int height = 400; gchar buf[64]; - if ( db_fetch( apg->params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) { + if ( db_fetch_safe( apg->params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) { sscanf( buf, "%d:%d:%d:%d", &xx, &yy, &width, &height ); } @@ -684,7 +683,6 @@ relayInviteReceived( void* closure, NetLaunchInfo* invite ) // globals->cGlobals.addr = *returnAddr; GtkWidget* gameWindow = globals->window; - globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); @@ -730,7 +728,7 @@ requestMsgs( gpointer data ) { GtkAppGlobals* apg = (GtkAppGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch_safe( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { relaycon_requestMsgs( apg->params, devIDBuf ); } else { @@ -755,8 +753,9 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), { GtkAppGlobals* apg = (GtkAppGlobals*)closure; LaunchParams* params = apg->params; - XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, nHere=%d)", - __func__, gameName, gameID, dictName, nPlayers, nHere ); + XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, " + "nHere=%d, forceChannel=%d)", __func__, gameName, gameID, dictName, + nPlayers, nHere, forceChannel ); CurGameInfo gi = {0}; gi_copy( MPPARM(params->mpool) &gi, ¶ms->pgi ); @@ -765,6 +764,7 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), gi.gameID = gameID; gi.dictLang = dictLang; gi.forceChannel = forceChannel; + gi.serverRole = SERVER_ISCLIENT; /* recipient of invitation is client */ replaceStringIfDifferent( params->mpool, &gi.dictName, dictName ); GtkGameGlobals* globals = malloc( sizeof(*globals) ); @@ -773,7 +773,6 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName), globals->cGlobals.addr = *returnAddr; GtkWidget* gameWindow = globals->window; - globals->cGlobals.pDb = apg->params->pDb; globals->cGlobals.selRow = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); @@ -896,18 +895,18 @@ gtkmain( LaunchParams* params ) #ifdef XWFEATURE_SMS gchar buf[32]; - const gchar* myPhone = params->connInfo.sms.phone; + const gchar* myPhone = params->connInfo.sms.myPhone; if ( !!myPhone ) { db_store( params->pDb, KEY_SMSPHONE, myPhone ); - } else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { - params->connInfo.sms.phone = myPhone = buf; + } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { + params->connInfo.sms.myPhone = myPhone = buf; } XP_U16 myPort = params->connInfo.sms.port; gchar portbuf[8]; if ( 0 < myPort ) { sprintf( portbuf, "%d", myPort ); db_store( params->pDb, KEY_SMSPORT, portbuf ); - } else if ( db_fetch( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) { + } else if ( db_fetch_safe( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) { params->connInfo.sms.port = myPort = atoi( portbuf ); } if ( !!myPhone && 0 < myPort ) { @@ -921,7 +920,13 @@ gtkmain( LaunchParams* params ) XP_LOGF( "not activating SMS: I don't have a phone" ); } - + if ( params->runSMSTest ) { + CommonGlobals cGlobals = {.params = params }; + setupUtil( &cGlobals ); + smsproto_runTests( params->mpool, cGlobals.params->dutil ); + linux_util_vt_destroy( cGlobals.util ); + free( cGlobals.util ); + } #endif makeGamesWindow( &apg ); } else if ( !!params->dbFileName ) { diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c new file mode 100644 index 000000000..931b907b2 --- /dev/null +++ b/xwords4/linux/lindutil.c @@ -0,0 +1,315 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "dutil.h" +#include "mempool.h" +#include "lindutil.h" +#include "linuxutl.h" +#include "linuxmain.h" +#include "gamesdb.h" +#include "LocalizedStrIncludes.h" + +static XP_U32 linux_dutil_getCurSeconds( XW_DUtilCtxt* duc ); +static const XP_UCHAR* linux_dutil_getUserString( XW_DUtilCtxt* duc, XP_U16 code ); +static const XP_UCHAR* linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code, + XP_U16 quantity ); + +static void linux_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* data ); +static void linux_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* inOut ); +static void linux_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + const void* data, XP_U16 len ); +static void linux_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + void* data, XP_U16* lenp ); + + +#ifdef XWFEATURE_SMS +static XP_Bool linux_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, + const XP_UCHAR* p1, + const XP_UCHAR* p2 ); +#endif + +#ifdef XWFEATURE_DEVID +static const XP_UCHAR* linux_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ ); +static void linux_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ, + const XP_UCHAR* idRelay ); +#endif + +#ifdef COMMS_CHECKSUM +static XP_UCHAR* linux_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr, + XP_U16 len ); +#endif + +XW_DUtilCtxt* +dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure ) +{ + XW_DUtilCtxt* result = XP_CALLOC( mpool, sizeof(*result) ); + result->vtMgr = vtMgr; + result->closure = closure; + +# define SET_PROC(nam) \ + result->vtable.m_dutil_ ## nam = linux_dutil_ ## nam; + + SET_PROC(getCurSeconds); + SET_PROC(getUserString); + SET_PROC(getUserQuantityString); + SET_PROC(storeStream); + SET_PROC(loadStream); + SET_PROC(storePtr); + SET_PROC(loadPtr); + +#ifdef XWFEATURE_SMS + SET_PROC(phoneNumbersSame); +#endif + +#ifdef XWFEATURE_DEVID + SET_PROC(getDevID); + SET_PROC(deviceRegistered); +#endif + +#ifdef COMMS_CHECKSUM + SET_PROC(md5sum); +#endif + +# undef SET_PROC + + MPASSIGN( result->mpool, mpool ); + return result; +} + +void dutils_free( XW_DUtilCtxt** ducp ) +{ +# ifdef MEM_DEBUG + XP_FREEP( (*ducp)->mpool, ducp ); +# endif +} + +static XP_U32 +linux_dutil_getCurSeconds( XW_DUtilCtxt* XP_UNUSED(duc) ) +{ + return linux_getCurSeconds(); +} + +static const XP_UCHAR* +linux_dutil_getUserString( XW_DUtilCtxt* XP_UNUSED(uc), XP_U16 code ) +{ + switch( code ) { + case STRD_REMAINING_TILES_ADD: + return (XP_UCHAR*)"+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return (XP_UCHAR*)"- %d [unused tiles]"; + case STR_COMMIT_CONFIRM: + return (XP_UCHAR*)"Are you sure you want to commit the current move?\n"; + case STRD_TURN_SCORE: + return (XP_UCHAR*)"Score for turn: %d\n"; + case STR_BONUS_ALL: + return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; + case STR_LOCAL_NAME: + return (XP_UCHAR*)"%s"; + case STR_NONLOCAL_NAME: + return (XP_UCHAR*)"%s (remote)"; + case STRD_TIME_PENALTY_SUB: + return (XP_UCHAR*)" - %d [time]"; + /* added.... */ + case STRD_CUMULATIVE_SCORE: + return (XP_UCHAR*)"Cumulative score: %d\n"; + case STRS_TRAY_AT_START: + return (XP_UCHAR*)"Tray at start: %s\n"; + case STRS_MOVE_DOWN: + return (XP_UCHAR*)"move (from %s down)\n"; + case STRS_MOVE_ACROSS: + return (XP_UCHAR*)"move (from %s across)\n"; + case STRS_NEW_TILES: + return (XP_UCHAR*)"New tiles: %s\n"; + case STRSS_TRADED_FOR: + return (XP_UCHAR*)"Traded %s for %s."; + case STR_PASS: + return (XP_UCHAR*)"pass\n"; + case STR_PHONY_REJECTED: + return (XP_UCHAR*)"Illegal word in move; turn lost!\n"; + + case STRD_ROBOT_TRADED: + return (XP_UCHAR*)"%d tiles traded this turn."; + case STR_ROBOT_MOVED: + return (XP_UCHAR*)"The robot \"%s\" moved:\n"; + case STRS_REMOTE_MOVED: + return (XP_UCHAR*)"Remote player \"%s\" moved:\n"; + +#ifndef XWFEATURE_STANDALONE_ONLY + case STR_LOCALPLAYERS: + return (XP_UCHAR*)"Local players"; + case STR_REMOTE: + return (XP_UCHAR*)"Remote"; +#endif + case STR_TOTALPLAYERS: + return (XP_UCHAR*)"Total players"; + + case STRS_VALUES_HEADER: + return (XP_UCHAR*)"%s counts/values:\n"; + + case STRD_REMAINS_HEADER: + return (XP_UCHAR*)"%d tiles left in pool."; + case STRD_REMAINS_EXPL: + return (XP_UCHAR*)"%d tiles left in pool and hidden trays:\n"; + + case STRSD_RESIGNED: + return "[Resigned] %s: %d"; + case STRSD_WINNER: + return "[Winner] %s: %d"; + case STRDSD_PLACER: + return "[#%d] %s: %d"; + + default: + return (XP_UCHAR*)"unknown code to linux_util_getUserString"; + } +} /* linux_dutil_getUserString */ + +static const XP_UCHAR* +linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code, + XP_U16 XP_UNUSED(quantity) ) +{ + return linux_dutil_getUserString( duc, code ); +} + +static void +linux_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream ) +{ + const void* ptr = stream_getPtr( stream ); + XP_U16 len = stream_getSize( stream ); + linux_dutil_storePtr( duc, key, ptr, len ); +} + +static void +linux_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, + XWStreamCtxt* stream ) +{ + XP_U16 len = 0; + linux_dutil_loadPtr( duc, key, NULL, &len ); + XP_U8 buf[len]; + linux_dutil_loadPtr( duc, key, buf, &len ); + + gsize out_len; + guchar* txt = g_base64_decode( (const gchar*)buf, &out_len ); + stream_putBytes( stream, txt, out_len ); + g_free( txt ); + + XP_LOGF( "%s(key=%s) => len: %d", __func__, key, stream_getSize(stream) ); +} + +static void +linux_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + const void* data, XP_U16 len ) +{ + LaunchParams* params = (LaunchParams*)duc->closure; + sqlite3* pDb = params->pDb; + + gchar* b64 = g_base64_encode( data, len); + db_store( pDb, key, b64 ); + g_free( b64 ); +} + +static void +linux_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key, + void* data, XP_U16* lenp ) +{ + LaunchParams* params = (LaunchParams*)duc->closure; + sqlite3* pDb = params->pDb; + + gint buflen = 0; + FetchResult res = db_fetch( pDb, key, NULL, &buflen ); + if ( res == BUFFER_TOO_SMALL ) { /* expected: I passed 0 */ + void* tmp = XP_MALLOC( duc->mpool, buflen ); + res = db_fetch( pDb, key, tmp, &buflen ); + XP_ASSERT( res == SUCCESS ); + + gsize out_len; + guchar* txt = g_base64_decode( (const gchar*)tmp, &out_len ); + if ( out_len <= *lenp ) { + XP_MEMCPY( data, txt, out_len ); + *lenp = out_len; + } + XP_FREEP( duc->mpool, &tmp ); + g_free( txt ); + } else { + *lenp = 0; /* doesn't exist */ + } + + XP_LOGF( "%s(key=%s) => len: %d", __func__, key, *lenp ); +} + +#ifdef XWFEATURE_SMS +static XP_Bool +linux_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, const XP_UCHAR* p1, + const XP_UCHAR* p2 ) +{ + LOG_FUNC(); + XP_USE( duc ); + XP_Bool result = 0 == strcmp( p1, p2 ); + XP_LOGF( "%s(%s, %s) => %d", __func__, p1, p2, result ); + return result; +} +#endif + +#ifdef XWFEATURE_DEVID +static const XP_UCHAR* +linux_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ ) +{ + LaunchParams* params = (LaunchParams*)duc->closure; + return linux_getDevID( params, typ ); +} + +static void +linux_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ, + const XP_UCHAR* idRelay ) +{ + /* Script discon_ok2.sh is grepping for these strings in logs, so don't + change them! */ + LaunchParams* params = (LaunchParams*)duc->closure; + switch( typ ) { + case ID_TYPE_NONE: /* error case */ + XP_LOGF( "%s: id rejected", __func__ ); + params->lDevID = NULL; + break; + case ID_TYPE_RELAY: + if ( !!params->pDb && 0 < strlen( idRelay ) ) { + XP_LOGF( "%s: new id: %s", __func__, idRelay ); + db_store( params->pDb, KEY_RDEVID, idRelay ); + } + break; + default: + XP_ASSERT(0); + break; + } +} +#endif + +#ifdef COMMS_CHECKSUM +static XP_UCHAR* +linux_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len ) +{ + gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len ); + XP_U16 sumlen = 1 + strlen( sum ); + XP_UCHAR* result = XP_MALLOC( duc->mpool, sumlen ); + XP_MEMCPY( result, sum, sumlen ); + g_free( sum ); + return result; +} +#endif + diff --git a/xwords4/linux/lindutil.h b/xwords4/linux/lindutil.h new file mode 100644 index 000000000..c28d30e89 --- /dev/null +++ b/xwords4/linux/lindutil.h @@ -0,0 +1,28 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _LINDUTIL_H_ +#define _LINDUTIL_H_ + +#include "dutil.h" + +XW_DUtilCtxt* dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure ); +void dutils_free( XW_DUtilCtxt** ducp ); + +#endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 53ab3ea08..845e6b41d 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -57,7 +57,9 @@ #include "main.h" #include "gamesdb.h" #include "linuxdict.h" +#include "lindutil.h" #include "relaycon.h" +#include "smsproto.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif @@ -95,7 +97,7 @@ file_exists( const char* fileName ) } /* file_exists */ XWStreamCtxt* -streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) +streamFromFile( CommonGlobals* cGlobals, char* name ) { XP_U8* buf; struct stat statBuf; @@ -110,9 +112,8 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) } fclose( f ); - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - closure, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); stream_putBytes( stream, buf, statBuf.st_size ); free( buf ); @@ -121,7 +122,7 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) #ifdef USE_SQLITE XWStreamCtxt* -streamFromDB( CommonGlobals* cGlobals, void* closure ) +streamFromDB( CommonGlobals* cGlobals ) { LOG_FUNC(); XWStreamCtxt* stream = NULL; @@ -139,9 +140,8 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) XP_U8 buf[size]; res = sqlite3_blob_read( ppBlob, buf, size, 0 ); if ( SQLITE_OK == res ) { - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - params->vtMgr, - closure, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); stream_putBytes( stream, buf, size ); } } @@ -236,7 +236,7 @@ requestMsgsIdle( gpointer data ) { CommonGlobals* cGlobals = (CommonGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch( cGlobals->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch_safe( cGlobals->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { relaycon_requestMsgs( cGlobals->params, devIDBuf ); } else { @@ -352,8 +352,9 @@ void saveGame( CommonGlobals* cGlobals ) { LOG_FUNC(); + sqlite3* pDb = cGlobals->params->pDb; if ( !!cGlobals->game.model && - (!!cGlobals->params->fileName || !!cGlobals->pDb) ) { + (!!cGlobals->params->fileName || !!pDb) ) { XP_Bool doSave = XP_TRUE; XP_Bool newGame = !file_exists( cGlobals->params->fileName ) || -1 == cGlobals->selRow; @@ -364,13 +365,12 @@ saveGame( CommonGlobals* cGlobals ) } if ( doSave ) { - if ( !!cGlobals->pDb ) { + if ( !!pDb ) { summarize( cGlobals ); } XWStreamCtxt* outStream; - MemStreamCloseCallback onClose = !!cGlobals->pDb? - writeToDB : writeToFile; + MemStreamCloseCallback onClose = !!pDb? writeToDB : writeToFile; outStream = mem_stream_make_sized( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, @@ -398,8 +398,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, { LOG_FUNC(); LaunchParams* params = cGlobals->params; - XWStreamCtxt* stream = - streamFromFile( cGlobals, params->fileName, cGlobals ); + XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName ); #ifdef DEBUG XP_Bool opened = @@ -431,9 +430,8 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - params->vtMgr, cGlobals, CHANNEL_NONE, - NULL ); + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); stream_putBytes( stream, buf, len ); (void)processMessage( cGlobals, stream, NULL, XP_TRUE ); stream_destroy( stream ); @@ -447,8 +445,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) { LOG_FUNC(); LaunchParams* params = cGlobals->params; - XWStreamCtxt* stream = - streamFromFile( cGlobals, params->fileName, cGlobals ); + XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName ); #ifdef DEBUG XP_Bool opened = @@ -483,9 +480,8 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->util->mpool) - params->vtMgr, cGlobals, CHANNEL_NONE, - NULL ); + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); stream_putBytes( stream, buf, len ); (void)processMessage( cGlobals, stream, NULL, XP_TRUE ); stream_destroy( stream ); @@ -643,6 +639,7 @@ typedef enum { ,CMD_DROPSENDRELAY ,CMD_DROPRCVRELAY ,CMD_DROPSENDSMS + ,CMD_SMSFAILPCT ,CMD_DROPRCVSMS ,CMD_FORCECHANNEL @@ -656,6 +653,7 @@ typedef enum { #endif #ifdef XWFEATURE_SMS ,CMD_SMSNUMBER /* SMS phone number */ + ,CMD_SERVER_SMSNUMBER ,CMD_SMSPORT #endif #ifdef XWFEATURE_RELAY @@ -684,6 +682,7 @@ typedef enum { ,CMD_NHIDDENROWS #endif ,CMD_ASKTIME + ,CMD_SMSTEST ,N_CMDS } XwLinuxCmd; @@ -764,6 +763,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_DROPSENDRELAY, false, "drop-send-relay", "start new games with relay send disabled" } ,{ CMD_DROPRCVRELAY, false, "drop-receive-relay", "start new games with relay receive disabled" } ,{ CMD_DROPSENDSMS, false, "drop-send-sms", "start new games with sms send disabled" } + ,{ CMD_SMSFAILPCT, true, "sms-fail-pct", "percent of sms sends, randomly chosen, never arrive" } ,{ CMD_DROPRCVSMS, false, "drop-receive-sms", "start new games with sms receive disabled" } ,{ CMD_FORCECHANNEL, true, "force-channel", "force (clients) to use this hostid/channel" } @@ -779,6 +779,7 @@ static CmdInfoRec CmdInfoRecs[] = { #endif #ifdef XWFEATURE_SMS ,{ CMD_SMSNUMBER, true, "sms-number", "this devices's sms phone number" } + ,{ CMD_SERVER_SMSNUMBER, true, "server-sms-number", "number this device should connect to" } ,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" } #endif #ifdef XWFEATURE_RELAY @@ -811,6 +812,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_ASKTIME, true, "ask-timeout", "Wait this many ms before cancelling dialog (default 500 ms; 0 means forever)" } #endif + ,{ CMD_SMSTEST, false, "run-sms-test", "Run smsproto_runTests() on startup"} }; static struct option* @@ -910,7 +912,7 @@ linux_getDevIDRelay( LaunchParams* params ) { XP_U32 result = 0; gchar buf[32]; - if ( db_fetch( params->pDb, KEY_RDEVID, buf, sizeof(buf) ) ) { + if ( db_fetch_safe( params->pDb, KEY_RDEVID, buf, sizeof(buf) ) ) { sscanf( buf, "%X", &result ); XP_LOGF( "%s(): %s => %x", __func__, buf, result ); } @@ -918,6 +920,12 @@ linux_getDevIDRelay( LaunchParams* params ) return result; } +XP_U32 +linux_getCurSeconds() +{ + return (XP_U32)time(NULL);//tv.tv_sec; +} + const XP_UCHAR* linux_getDevID( LaunchParams* params, DevIDType* typ ) { @@ -928,12 +936,12 @@ linux_getDevID( LaunchParams* params, DevIDType* typ ) if ( !!params->lDevID ) { result = params->lDevID; *typ = ID_TYPE_LINUX; - } else if ( db_fetch( params->pDb, KEY_RDEVID, params->devIDStore, - sizeof(params->devIDStore) ) ) { + } else if ( db_fetch_safe( params->pDb, KEY_RDEVID, params->devIDStore, + sizeof(params->devIDStore) ) ) { result = params->devIDStore; *typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_RELAY; - } else if ( db_fetch( params->pDb, KEY_LDEVID, params->devIDStore, - sizeof(params->devIDStore) ) ) { + } else if ( db_fetch_safe( params->pDb, KEY_LDEVID, params->devIDStore, + sizeof(params->devIDStore) ) ) { result = params->devIDStore; *typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_LINUX; } else if ( !params->noAnonDevid ) { @@ -947,8 +955,8 @@ void linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew ) { gchar rDevIDBuf[64]; - if ( !db_fetch( params->pDb, KEY_RDEVID, rDevIDBuf, - sizeof(rDevIDBuf) ) ) { + if ( !db_fetch_safe( params->pDb, KEY_RDEVID, rDevIDBuf, + sizeof(rDevIDBuf) ) ) { rDevIDBuf[0] = '\0'; } DevIDType typ = ID_TYPE_NONE; @@ -964,7 +972,7 @@ linux_setupDevidParams( LaunchParams* params ) { XP_Bool idIsNew = XP_TRUE; gchar oldLDevID[256]; - if ( db_fetch( params->pDb, KEY_LDEVID, oldLDevID, sizeof(oldLDevID) ) + if ( db_fetch_safe( params->pDb, KEY_LDEVID, oldLDevID, sizeof(oldLDevID) ) && (!params->lDevID || 0 == strcmp( oldLDevID, params->lDevID )) ) { idIsNew = XP_FALSE; } else { @@ -1299,9 +1307,15 @@ linux_send( const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* XP_UNUSED_DBG(msgNo comms_getAddr( cGlobals->game.comms, &addr ); addrRec = &addr; } + + // use serverphone if I'm a client, else hope one's provided (this is + // a reply) + const XP_UCHAR* phone = cGlobals->params->connInfo.sms.serverPhone; + if ( !phone ) { + phone = addrRec->u.sms.phone; + } nSent = linux_sms_send( cGlobals->params, buf, buflen, - addrRec->u.sms.phone, addrRec->u.sms.port, - gameID ); + phone, addrRec->u.sms.port, gameID ); } break; #endif @@ -1434,9 +1448,8 @@ stream_from_msgbuf( CommonGlobals* globals, const unsigned char* bufPtr, XP_U16 nBytes ) { XWStreamCtxt* result; - result = mem_stream_make( MPPARM(globals->util->mpool) - globals->params->vtMgr, - globals, CHANNEL_NONE, NULL ); + result = mem_stream_make_raw( MPPARM(globals->util->mpool) + globals->params->vtMgr ); stream_putBytes( result, bufPtr, nBytes ); return result; @@ -1950,7 +1963,7 @@ initFromParams( CommonGlobals* cGlobals, LaunchParams* params ) #ifdef XWFEATURE_SMS case COMMS_CONN_SMS: addr_addType( addr, COMMS_CONN_SMS ); - XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.phone, + XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.myPhone, sizeof(addr->u.sms.phone) - 1 ); addr->u.sms.port = params->connInfo.sms.port; break; @@ -2001,12 +2014,15 @@ initParams( LaunchParams* params ) /* params->util->vtable->m_util_addrChange = linux_util_addrChange; */ /* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */ #endif + + params->dutil = dutils_init( MPPARM(params->mpool) params->vtMgr, params ); } static void freeParams( LaunchParams* params ) { vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); + dutils_free( ¶ms->dutil ); dmgr_destroy( params->dictMgr ); gi_disposePlayerInfo( MPPARM(params->mpool) ¶ms->pgi ); @@ -2272,7 +2288,11 @@ main( int argc, char** argv ) break; #ifdef XWFEATURE_SMS case CMD_SMSNUMBER: /* SMS phone number */ - mainParams.connInfo.sms.phone = optarg; + mainParams.connInfo.sms.myPhone = optarg; + addr_addType( &mainParams.addr, COMMS_CONN_SMS ); + break; + case CMD_SERVER_SMSNUMBER: + mainParams.connInfo.sms.serverPhone = optarg; addr_addType( &mainParams.addr, COMMS_CONN_SMS ); break; case CMD_SMSPORT: @@ -2427,6 +2447,10 @@ main( int argc, char** argv ) case CMD_DROPSENDSMS: mainParams.commsDisableds[COMMS_CONN_SMS][1] = XP_TRUE; break; + case CMD_SMSFAILPCT: + mainParams.smsSendFailPct = atoi(optarg); + XP_ASSERT( mainParams.smsSendFailPct >= 0 && mainParams.smsSendFailPct <= 100 ); + break; case CMD_DROPRCVSMS: mainParams.commsDisableds[COMMS_CONN_SMS][0] = XP_TRUE; break; @@ -2488,6 +2512,10 @@ main( int argc, char** argv ) mainParams.askTimeout = atoi(optarg); break; #endif + case CMD_SMSTEST: + mainParams.runSMSTest = XP_TRUE; + break; + default: done = true; break; diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index a3622257e..e03fed979 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -64,9 +64,8 @@ void sendOnClose( XWStreamCtxt* stream, void* closure ); void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ); XP_Bool file_exists( const char* fileName ); -XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name, - void* closure ); -XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals, void* closure ); +XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name ); +XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals ); void writeToFile( XWStreamCtxt* stream, void* closure ); XP_Bool getDictPath( const LaunchParams *params, const char* name, char* result, int resultLen ); diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c index 3958a36c9..8d0693129 100644 --- a/xwords4/linux/linuxsms.c +++ b/xwords4/linux/linuxsms.c @@ -1,6 +1,6 @@ /* -*- compile-command: "make -j MEMDEBUG=TRUE";-*- */ /* - * Copyright 2006-2009 by Eric House (xwords@eehouse.org). All rights + * Copyright 2006 - 2018 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -20,15 +20,18 @@ #ifdef XWFEATURE_SMS +#include #include #include #include #include #include -#include #include "linuxsms.h" +#include "linuxutl.h" #include "strutils.h" +#include "smsproto.h" +#include "linuxmain.h" #define SMS_DIR "/tmp/xw_sms" #define LOCK_FILE ".lock" @@ -54,26 +57,34 @@ #define ADDR_FMT "from: %s %d\n" -static void -makeQueuePath( const XP_UCHAR* phone, XP_U16 port, - XP_UCHAR* path, XP_U16 pathlen ) -{ - XP_ASSERT( 0 != port ); - snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port ); -} - typedef struct _LinSMSData { - int fd, wd; XP_UCHAR myQueue[256]; XP_U16 myPort; FILE* lock; - XP_U16 count; const gchar* myPhone; const SMSProcs* procs; void* procClosure; + 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 gint check_for_files( gpointer data ); +static gint check_for_files_once( gpointer data ); + +static void +formatQueuePath( const XP_UCHAR* phone, XP_U16 port, XP_UCHAR* path, + XP_U16 pathlen ) +{ + XP_ASSERT( 0 != 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 @@ -103,58 +114,74 @@ unlock_queue( LinSMSData* storage ) } static XP_S16 -send_sms( LinSMSData* storage, XWStreamCtxt* stream, - const XP_UCHAR* phone, XP_U16 port ) +write_fake_sms( LaunchParams* params, XWStreamCtxt* stream, + const XP_UCHAR* phone, XP_U16 port ) { - const XP_U8* buf = stream_getPtr( stream ); - XP_U16 buflen = stream_getSize( stream ); + XP_S16 nSent; + XP_U16 pct = XP_RANDOM() % 100; + XP_Bool skipWrite = pct < params->smsSendFailPct; - XP_S16 nSent = -1; - XP_ASSERT( !!storage ); - char path[256]; + if ( skipWrite ) { + nSent = stream_getSize( stream ); + 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 ); - lock_queue( storage ); + XP_ASSERT( !!storage ); + char path[256]; + + lock_queue( storage ); #ifdef DEBUG - gchar* str64 = g_base64_encode( buf, buflen ); + gchar* str64 = g_base64_encode( buf, buflen ); #endif - XP_U16 count = ++storage->count; - makeQueuePath( phone, port, path, sizeof(path) ); - XP_LOGF( "%s: writing msg %d to %s", __func__, count, path ); - g_mkdir_with_parents( path, 0777 ); /* just in case */ - int len = strlen( path ); - snprintf( &path[len], sizeof(path)-len, "/%d", count ); + formatQueuePath( phone, port, path, sizeof(path) ); - XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */ - XP_U16 smslen = sizeof(sms); - binToSms( sms, &smslen, buf, buflen ); - XP_ASSERT( smslen == strlen(sms) ); + /* Random-number-based name is fine, as we pick based on age. */ + int rint = makeRandomInt(); + g_mkdir_with_parents( path, 0777 ); /* just in case */ + int len = strlen( path ); + snprintf( &path[len], sizeof(path)-len, "/%u", rint ); + + XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */ + XP_U16 smslen = sizeof(sms); + binToSms( sms, &smslen, buf, buflen ); + XP_ASSERT( smslen == strlen(sms) ); + XP_LOGF( "%s: writing msg to %s", __func__, path ); #ifdef DEBUG - XP_ASSERT( !strcmp( str64, sms ) ); - g_free( str64 ); + XP_ASSERT( !strcmp( str64, sms ) ); + g_free( str64 ); - XP_U8 testout[buflen]; - XP_U16 lenout = sizeof( testout ); - XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) ); - XP_ASSERT( lenout == buflen ); - XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) ); + XP_U8 testout[buflen]; + XP_U16 lenout = sizeof( testout ); + XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) ); + XP_ASSERT( lenout == buflen ); + // valgrind doesn't like this; punting on figuring out + // XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) ); #endif - FILE* fp = fopen( path, "w" ); - XP_ASSERT( !!fp ); - (void)fprintf( fp, ADDR_FMT, storage->myPhone, storage->myPort ); - (void)fprintf( fp, "%s\n", sms ); - fclose( fp ); - sync(); + FILE* fp = fopen( path, "w" ); + XP_ASSERT( !!fp ); + (void)fprintf( fp, ADDR_FMT, storage->myPhone, storage->myPort ); + (void)fprintf( fp, "%s\n", sms ); + fclose( fp ); + sync(); - unlock_queue( storage ); + unlock_queue( storage ); - nSent = buflen; + nSent = buflen; + LOG_RETURNF( "%d", nSent ); + } return nSent; -} /* linux_sms_send */ +} /* write_fake_sms */ static XP_S16 decodeAndDelete( LinSMSData* storage, const gchar* name, @@ -185,6 +212,7 @@ decodeAndDelete( LinSMSData* storage, const gchar* name, *strstr(eol, "\n" ) = '\0'; XP_U16 inlen = strlen(eol); /* skip \n */ + XP_LOGF( "%s(): decoding message from file %s", __func__, name ); XP_U8 out[inlen]; XP_U16 outlen = sizeof(out); XP_Bool valid = smsToBin( out, &outlen, eol, inlen ); @@ -194,7 +222,8 @@ decodeAndDelete( LinSMSData* storage, const gchar* name, nRead = outlen; addr_setType( addr, COMMS_CONN_SMS ); XP_STRNCPY( addr->u.sms.phone, phone, sizeof(addr->u.sms.phone) ); - XP_LOGF( "%s: message came from phone: %s, port: %d", __func__, phone, port ); + XP_LOGF( "%s: message came from phone: %s, port: %d", __func__, + phone, port ); addr->u.sms.port = port; } } else { @@ -226,7 +255,7 @@ dispatch_invite( LinSMSData* storage, XP_U16 XP_UNUSED(proto), addrFromStream( addr, stream ); (*storage->procs->inviteReceived)( storage->procClosure, gameName, - gameID, dictLang, dictName, nPlayers, + gameID, dictLang, dictName, nPlayers, nMissing, forceChannel, addr ); } @@ -234,13 +263,24 @@ 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 ); - - (*storage->procs->msgReceived)( storage->procClosure, addr, gameID, - 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 @@ -248,9 +288,8 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, CommsAddrRec* addr ) { LinSMSData* storage = getStorage( params ); - XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) - params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) + params->vtMgr ); stream_setVersion( stream, CUR_STREAM_VERS ); stream_putBytes( stream, buf, len ); @@ -274,66 +313,6 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, stream_destroy( stream ); } -static gboolean -sms_receive( GIOChannel *source, GIOCondition XP_UNUSED_DBG(condition), gpointer data ) -{ - LOG_FUNC(); - XP_ASSERT( 0 != (G_IO_IN & condition) ); - LaunchParams* params = (LaunchParams*)data; - XP_ASSERT( !!params->smsStorage ); - LinSMSData* storage = getStorage( params ); - - int socket = g_io_channel_unix_get_fd( source ); - XP_ASSERT( socket == storage->fd ); - - lock_queue( storage ); - - /* read required or we'll just get the event again. But we don't care - about the result or the buffer contents. */ - XP_U8 buffer[sizeof(struct inotify_event) + 16]; - if ( 0 > read( socket, buffer, sizeof(buffer) ) ) { - XP_LOGF( "%s: discarding inotify buffer", __func__ ); - } - for ( ; ; ) { - XP_S16 nRead = -1; - char shortest[256] = { '\0' }; - GDir* dir = g_dir_open( storage->myQueue, 0, NULL ); - XP_LOGF( "%s: opening queue %s", __func__, storage->myQueue ); - for ( ; ; ) { - const gchar* name = g_dir_read_name( dir ); - if ( NULL == name ) { - break; - } else if ( 0 == strcmp( name, LOCK_FILE ) ) { - continue; - } - if ( !shortest[0] || 0 < strcmp( shortest, name ) ) { - snprintf( shortest, sizeof(shortest), "%s", name ); - } - } - g_dir_close( dir ); - - uint8_t buf[256]; - CommsAddrRec fromAddr = {0}; - if ( !!shortest[0] ) { - XP_LOGF( "%s: decoding message %s", __func__, shortest ); - nRead = decodeAndDelete( storage, shortest, buf, - sizeof(buf), &fromAddr ); - } else { - XP_LOGF( "%s: never found shortest", __func__ ); - } - - unlock_queue( storage ); - - if ( 0 < nRead ) { - parseAndDispatch( params, buf, nRead, &fromAddr ); - lock_queue( storage ); - } else { - break; - } - } - return TRUE; -} /* sms_receive */ - void linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort, const SMSProcs* procs, void* procClosure ) @@ -346,18 +325,19 @@ linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort, storage->myPort = myPort; storage->procs = procs; storage->procClosure = procClosure; + storage->protoState = smsproto_init( MPPARM(params->mpool) params->dutil ); - makeQueuePath( myPhone, myPort, storage->myQueue, sizeof(storage->myQueue) ); + formatQueuePath( myPhone, myPort, storage->myQueue, sizeof(storage->myQueue) ); XP_LOGF( "%s: my queue: %s", __func__, storage->myQueue ); storage->myPort = params->connInfo.sms.port; (void)g_mkdir_with_parents( storage->myQueue, 0777 ); - int fd = inotify_init(); - storage->fd = fd; - storage->wd = inotify_add_watch( fd, storage->myQueue, IN_MODIFY ); - - (*procs->socketAdded)( params, fd, sms_receive ); + /* Look for preexisting or new files every half second. Easier than + inotify, especially when you add the need to handle files written while + not running. */ + (void)g_idle_add( check_for_files_once, params ); + (void)g_timeout_add( 500, check_for_files, params ); } /* linux_sms_init */ void @@ -368,8 +348,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi, { LOG_FUNC(); XWStreamCtxt* stream; - stream = mem_stream_make( MPPARM(params->mpool) params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + stream = mem_stream_make_raw( MPPARM(params->mpool) params->vtMgr ); writeHeader( stream, INVITE ); stream_putU32( stream, gi->gameID ); stringToStream( stream, gameName ); @@ -381,8 +360,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi, addrToStream( stream, addr ); - LinSMSData* storage = getStorage( params ); - send_sms( storage, stream, toPhone, toPort ); + write_fake_sms( params, stream, toPhone, toPort ); stream_destroy( stream ); } @@ -391,26 +369,161 @@ XP_S16 linux_sms_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port, XP_U32 gameID ) +{ + 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 ); + 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( MPPARM(params->mpool) params->vtMgr, - NULL, CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) + params->vtMgr ); writeHeader( stream, DATA ); stream_putU32( stream, gameID ); stream_putBytes( stream, buf, buflen ); - LinSMSData* storage = getStorage( params ); - if ( 0 >= send_sms( storage, stream, phone, port ) ) { - buflen = -1; - } + (void)write_fake_sms( params, stream, phone, port ); stream_destroy( stream ); - - return buflen; } +typedef struct _RetryClosure { + LaunchParams* params; + 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 ) +{ + 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 ); + } + + LinSMSData* storage = getStorage( params ); + smsproto_freeMsgArray( storage->protoState, arr ); + } else if ( waitSecs > 0 ) { + RetryClosure* closure = (RetryClosure*)XP_CALLOC( params->mpool, + sizeof(*closure) ); + closure->params = params; + XP_STRCAT( closure->phone, phone ); + closure->port = port; + closure->gameID = gameID; + g_timeout_add_seconds( 5, retrySend, closure ); + } +} + +static gboolean +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, + closure->port, closure->gameID ); + XP_FREEP( closure->params->mpool, &closure ); + return FALSE; +} + +static gint +check_for_files( gpointer data ) +{ + check_for_files_once( data ); + return TRUE; +} + +static gint +check_for_files_once( gpointer data ) +{ + LOG_FUNC(); + LaunchParams* params = (LaunchParams*)data; + LinSMSData* storage = getStorage( params ); + + for ( ; ; ) { + lock_queue( storage ); + + char oldestFile[256] = { '\0' }; + struct timespec oldestModTime; + + GDir* dir = g_dir_open( storage->myQueue, 0, NULL ); + XP_LOGF( "%s: opening queue %s", __func__, storage->myQueue ); + for ( ; ; ) { + const gchar* name = g_dir_read_name( dir ); + if ( NULL == name ) { + break; + } else if ( 0 == strcmp( name, LOCK_FILE ) ) { + continue; + } + + /* We want the oldest file first. Timestamp comes from stat(). */ + struct stat statbuf; + char fullPath[500]; + snprintf( fullPath, sizeof(fullPath), "%s/%s", storage->myQueue, name ); + int err = stat( fullPath, &statbuf ); + if ( err != 0 ) { + XP_LOGF( "%s(); %d from stat (error: %s)", __func__, + err, strerror(errno) ); + XP_ASSERT( 0 ); + } else { + XP_Bool replace = !oldestFile[0]; /* always replace empty/unset :-) */ + if ( !replace ) { + if (statbuf.st_mtim.tv_sec == oldestModTime.tv_sec ) { + replace = statbuf.st_mtim.tv_nsec < oldestModTime.tv_nsec; + } else { + replace = statbuf.st_mtim.tv_sec < oldestModTime.tv_sec; + } + } + + if ( replace ) { + oldestModTime = statbuf.st_mtim; + if ( !!oldestFile[0] ) { + XP_LOGF( "%s(): replacing %s with older %s", __func__, oldestFile, name ); + } + snprintf( oldestFile, sizeof(oldestFile), "%s", name ); + } + } + } + g_dir_close( dir ); + + uint8_t buf[256]; + CommsAddrRec fromAddr = {0}; + XP_S16 nRead = -1; + if ( !!oldestFile[0] ) { + nRead = decodeAndDelete( storage, oldestFile, buf, + sizeof(buf), &fromAddr ); + } else { + XP_LOGF( "%s: no file found", __func__ ); + } + + unlock_queue( storage ); + + if ( 0 >= nRead ) { + break; + } + + parseAndDispatch( params, buf, nRead, &fromAddr ); + } + return FALSE; +} /* check_for_files_once */ + void linux_sms_cleanup( LaunchParams* params ) { + LinSMSData* storage = getStorage( params ); + smsproto_free( storage->protoState ); XP_FREEP( params->mpool, ¶ms->smsStorage ); } diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index b596f9cdf..44504b614 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -266,151 +266,13 @@ linux_util_getSquareBonus( XW_UtilCtxt* uc, XP_U16 nCols, return result; } /* linux_util_getSquareBonus */ -static XP_U32 -linux_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) ) -{ - return (XP_U32)time(NULL);//tv.tv_sec; -} /* gtk_util_getCurSeconds */ - -static const XP_UCHAR* -linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code ) -{ - switch( code ) { - case STRD_REMAINING_TILES_ADD: - return (XP_UCHAR*)"+ %d [all remaining tiles]"; - case STRD_UNUSED_TILES_SUB: - return (XP_UCHAR*)"- %d [unused tiles]"; - case STR_COMMIT_CONFIRM: - return (XP_UCHAR*)"Are you sure you want to commit the current move?\n"; - case STRD_TURN_SCORE: - return (XP_UCHAR*)"Score for turn: %d\n"; - case STR_BONUS_ALL: - return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; - case STR_LOCAL_NAME: - return (XP_UCHAR*)"%s"; - case STR_NONLOCAL_NAME: - return (XP_UCHAR*)"%s (remote)"; - case STRD_TIME_PENALTY_SUB: - return (XP_UCHAR*)" - %d [time]"; - /* added.... */ - case STRD_CUMULATIVE_SCORE: - return (XP_UCHAR*)"Cumulative score: %d\n"; - case STRS_TRAY_AT_START: - return (XP_UCHAR*)"Tray at start: %s\n"; - case STRS_MOVE_DOWN: - return (XP_UCHAR*)"move (from %s down)\n"; - case STRS_MOVE_ACROSS: - return (XP_UCHAR*)"move (from %s across)\n"; - case STRS_NEW_TILES: - return (XP_UCHAR*)"New tiles: %s\n"; - case STRSS_TRADED_FOR: - return (XP_UCHAR*)"Traded %s for %s."; - case STR_PASS: - return (XP_UCHAR*)"pass\n"; - case STR_PHONY_REJECTED: - return (XP_UCHAR*)"Illegal word in move; turn lost!\n"; - - case STRD_ROBOT_TRADED: - return (XP_UCHAR*)"%d tiles traded this turn."; - case STR_ROBOT_MOVED: - return (XP_UCHAR*)"The robot \"%s\" moved:\n"; - case STRS_REMOTE_MOVED: - return (XP_UCHAR*)"Remote player \"%s\" moved:\n"; - -#ifndef XWFEATURE_STANDALONE_ONLY - case STR_LOCALPLAYERS: - return (XP_UCHAR*)"Local players"; - case STR_REMOTE: - return (XP_UCHAR*)"Remote"; -#endif - case STR_TOTALPLAYERS: - return (XP_UCHAR*)"Total players"; - - case STRS_VALUES_HEADER: - return (XP_UCHAR*)"%s counts/values:\n"; - - case STRD_REMAINS_HEADER: - return (XP_UCHAR*)"%d tiles left in pool."; - case STRD_REMAINS_EXPL: - return (XP_UCHAR*)"%d tiles left in pool and hidden trays:\n"; - - case STRSD_RESIGNED: - return "[Resigned] %s: %d"; - case STRSD_WINNER: - return "[Winner] %s: %d"; - case STRDSD_PLACER: - return "[#%d] %s: %d"; - - default: - return (XP_UCHAR*)"unknown code to linux_util_getUserString"; - } -} /* linux_util_getUserString */ - -static const XP_UCHAR* -linux_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 code, - XP_U16 XP_UNUSED(quantity) ) -{ - return linux_util_getUserString( uc, code ); -} - -#ifdef XWFEATURE_DEVID -static const XP_UCHAR* -linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) +static XW_DUtilCtxt* +linux_util_getDevUtilCtxt( XW_UtilCtxt* uc ) { CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - return linux_getDevID( cGlobals->params, typ ); + return cGlobals->params->dutil; } -static void -linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, - const XP_UCHAR* idRelay ) -{ - /* Script discon_ok2.sh is grepping for these strings in logs, so don't - change them! */ - CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - switch( typ ) { - case ID_TYPE_NONE: /* error case */ - XP_LOGF( "%s: id rejected", __func__ ); - cGlobals->params->lDevID = NULL; - break; - case ID_TYPE_RELAY: - if ( !!cGlobals->pDb && 0 < strlen( idRelay ) ) { - XP_LOGF( "%s: new id: %s", __func__, idRelay ); - db_store( cGlobals->pDb, KEY_RDEVID, idRelay ); - } - break; - default: - XP_ASSERT(0); - break; - } -} -#endif - -#ifdef COMMS_CHECKSUM -static XP_UCHAR* -linux_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len ) -{ - gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len ); - XP_U16 sumlen = 1 + strlen( sum ); - XP_UCHAR* result = XP_MALLOC( uc->mpool, sumlen ); - XP_MEMCPY( result, sum, sumlen ); - g_free( sum ); - return result; -} -#endif - -#ifdef XWFEATURE_SMS -static XP_Bool -linux_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1, - const XP_UCHAR* p2 ) -{ - XP_USE( uc ); - XP_Bool result = 0 == strcmp( p1, p2 ); - XP_LOGF( "%s(%s, %s) => %d", __func__, p1, p2, result ); - return result; -} -#endif - void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) { @@ -421,19 +283,8 @@ linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict; util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus; - util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds; - util->vtable->m_util_getUserString = linux_util_getUserString; - util->vtable->m_util_getUserQuantityString = linux_util_getUserQuantityString; -#ifdef XWFEATURE_DEVID - util->vtable->m_util_getDevID = linux_util_getDevID; - util->vtable->m_util_deviceRegistered = linux_util_deviceRegistered; -#endif -#ifdef COMMS_CHECKSUM - util->vtable->m_util_md5sum = linux_util_md5sum; -#endif -#ifdef XWFEATURE_SMS - util->vtable->m_util_phoneNumbersSame = linux_util_phoneNumbersSame; -#endif + + util->vtable->m_util_getDevUtilCtxt = linux_util_getDevUtilCtxt; } void @@ -666,16 +517,6 @@ storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 msglen, return inUse; } -XWStreamCtxt* -make_simple_stream( CommonGlobals* cGlobals ) -{ - XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->util->mpool) - cGlobals->params->vtMgr, - cGlobals, CHANNEL_NONE, NULL ); - return stream; -} - void writeNoConnMsgs( CommonGlobals* cGlobals, int fd ) { @@ -688,7 +529,8 @@ writeNoConnMsgs( CommonGlobals* cGlobals, int fd ) guint nMsgs = g_slist_length( list ); XP_ASSERT( 0 < nMsgs ); - XWStreamCtxt* stream = make_simple_stream( cGlobals ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); stream_putU16( stream, 1 ); /* number of relayIDs */ stream_catString( stream, relayID ); stream_putU8( stream, '\n' ); diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h index 28d961672..afbaab254 100644 --- a/xwords4/linux/linuxutl.h +++ b/xwords4/linux/linuxutl.h @@ -48,7 +48,6 @@ void initNoConnStorage( CommonGlobals* cGlobals ); XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID ); void writeNoConnMsgs( CommonGlobals* cGlobals, int fd ); -XWStreamCtxt* make_simple_stream( CommonGlobals* cGlobals ); #ifdef STREAM_VERS_BIGBOARD void setSquareBonuses( const CommonGlobals* cGlobals ); @@ -56,4 +55,6 @@ void setSquareBonuses( const CommonGlobals* cGlobals ); # define setSquareBonuses( cg ) #endif +XP_U32 linux_getCurSeconds(); + #endif diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 5f90e6999..0ed10b6cf 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -32,6 +32,7 @@ #include "game.h" #include "vtabmgr.h" #include "dictmgr.h" +#include "dutil.h" typedef struct ServerInfo { XP_U16 nRemotePlayers; @@ -58,6 +59,7 @@ typedef struct LaunchParams { char* dbName; sqlite3* pDb; /* null unless opened */ XP_U16 saveFailPct; + XP_U16 smsSendFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE char* dbFileName; @@ -77,6 +79,7 @@ typedef struct LaunchParams { #endif VTableMgr* vtMgr; DictMgrCtxt* dictMgr; + XW_DUtilCtxt* dutil; XP_U16 nLocalPlayers; XP_U16 nHidden; XP_U16 gameSeed; @@ -106,6 +109,7 @@ typedef struct LaunchParams { XP_Bool useCurses; XP_Bool useUdp; XP_Bool useHTTP; + XP_Bool runSMSTest; XP_Bool noHTTPAuto; XP_U16 splitPackets; XP_U16 chatsInterval; /* 0 means disabled */ @@ -150,7 +154,8 @@ typedef struct LaunchParams { #endif #ifdef XWFEATURE_SMS struct { - const char* phone; + const char* myPhone; + const char* serverPhone; int port; } sms; #endif @@ -197,7 +202,6 @@ struct CommonGlobals { XP_U16 lastStreamSize; XP_U16 nMissing; XP_Bool manualFinal; /* use asked for final scores */ - sqlite3* pDb; sqlite3_int64 selRow; SocketAddedFunc socketAdded; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index b156b268a..a1cdfb179 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -151,7 +151,7 @@ write_callback(void *contents, size_t size, size_t nmemb, void* data) ws->curSize = 1L; } - XP_LOGF( "%s(size=%ld, nmemb=%ld)", __func__, size, nmemb ); + XP_LOGF( "%s(size=%zd, nmemb=%zd)", __func__, size, nmemb ); size_t oldLen = ws->curSize; const size_t newLength = size * nmemb; XP_ASSERT( (oldLen + newLength) > 0 ); @@ -332,9 +332,8 @@ relaycon_invite( LaunchParams* params, XP_U32 destDevID, indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, destDevID ); } - XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) - params->vtMgr, params, - CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) + params->vtMgr ); nli_saveToStream( invit, stream ); XP_U16 len = stream_getSize( stream ); indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx, len ); @@ -679,9 +678,8 @@ process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead ) #endif getNetLong( &ptr ); XP_U16 len = getNetShort( &ptr ); - XWStreamCtxt* stream = mem_stream_make( MPPARM(storage->params->mpool) - storage->params->vtMgr, storage, - CHANNEL_NONE, NULL ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(storage->params->mpool) + storage->params->vtMgr ); stream_putBytes( stream, ptr, len ); NetLaunchInfo invit; XP_Bool success = nli_makeFromStream( &invit, stream ); diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 5acc8a5fe..92ef14af7 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -2,6 +2,7 @@ import re, os, sys, getopt, shutil, threading, requests, json, glob import argparse, datetime, random, signal, subprocess, time +from shutil import rmtree # LOGDIR=./$(basename $0)_logs # APP_NEW="" @@ -155,14 +156,14 @@ def player_params(args, NLOCALS, NPLAYERS, NAME_INDX): def logReaderStub(dev): dev.logReaderMain() class Device(): - sConnnameMap = {} sHasLDevIDMap = {} sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$') sGameOverPat = re.compile('.*\[unused tiles\].*') - sTilesLeftPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool') + sTilesLeftPoolPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool') + sTilesLeftTrayPat = re.compile('.*player \d+ now has (\d+) tiles') sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*') - def __init__(self, args, game, indx, app, params, room, db, log, nInGame): + def __init__(self, args, game, indx, app, params, room, peers, db, log, nInGame): self.game = game self.indx = indx self.args = args @@ -176,13 +177,15 @@ class Device(): self.nInGame = nInGame # runtime stuff; init now self.proc = None - self.connname = None + self.peers = peers self.devID = '' self.launchCount = 0 self.allDone = False # when true, can be killed - self.nTilesLeft = None + self.nTilesLeftPool = None + self.nTilesLeftTray = None self.relayID = None self.relaySeed = 0 + self.locked = False with open(self.logPath, "w") as log: log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params]))) @@ -198,23 +201,20 @@ class Device(): nLines += 1 log.write(line + os.linesep) - # check for connname - if not self.connname: - match = Device.sConnNamePat.match(line) - if match: - self.connname = match.group(1) - if not self.connname in Device.sConnnameMap: - Device.sConnnameMap[self.connname] = set() - Device.sConnnameMap[self.connname].add(self) + self.locked = True # check for game over if not self.gameOver: match = Device.sGameOverPat.match(line) if match: self.gameOver = True - # Check every line for tiles left - match = Device.sTilesLeftPat.match(line) - if match: self.nTilesLeft = int(match.group(1)) + # Check every line for tiles left in pool + match = Device.sTilesLeftPoolPat.match(line) + if match: self.nTilesLeftPool = int(match.group(1)) + + # Check every line for tiles left in tray + match = Device.sTilesLeftTrayPat.match(line) + if match: self.nTilesLeftTray = int(match.group(1)) if not self.relayID: match = Device.sRelayIDPat.match(line) @@ -222,6 +222,8 @@ class Device(): self.relaySeed = int(match.group(1)) self.relayID = match.group(2) + self.locked = False + # print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath); def launch(self): @@ -233,7 +235,6 @@ class Device(): args += [self.app] + [str(p) for p in self.params] if self.devID: args.extend( ' '.split(self.devID)) self.launchCount += 1 - # self.logStream = open(self.logPath, flag) self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL, stderr = subprocess.PIPE, universal_newlines = True) self.pid = self.proc.pid @@ -278,17 +279,22 @@ class Device(): shutil.move(self.db, self.args.LOGDIR + '/done') def send_dead(self): - JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}]) - url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST) - params = {'params' : JSON} - try: - req = requests.get(url, params = params) # failing - except requests.exceptions.ConnectionError: - print('got exception sending to', url, params, '; is relay.py running as apache module?') + if self.args.ADD_RELAY: + JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}]) + url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST) + params = {'params' : JSON} + try: + req = requests.get(url, params = params) # failing + except requests.exceptions.ConnectionError: + print('got exception sending to', url, params, '; is relay.py running as apache module?') def getTilesCount(self): - return {'index': self.indx, 'nTilesLeft': self.nTilesLeft, - 'launchCount': self.launchCount, 'game': self.game, + assert not self.locked + return {'index': self.indx, + 'nTilesLeftPool': self.nTilesLeftPool, + 'nTilesLeftTray': self.nTilesLeftTray, + 'launchCount': self.launchCount, + 'game': self.game, } def update_ldevid(self): @@ -313,19 +319,17 @@ class Device(): def check_game(self): if self.gameOver and not self.allDone: - allDone = False - if len(Device.sConnnameMap[self.connname]) == self.nInGame: - allDone = True - for dev in Device.sConnnameMap[self.connname]: - if dev == self: continue - if not dev.gameOver: - allDone = False - break + allDone = True + for dev in self.peers: + if dev == self: continue + if not dev.gameOver: + allDone = False + break - if allDone: - for dev in Device.sConnnameMap[self.connname]: - assert self.game == dev.game - dev.allDone = True + if allDone: + for dev in self.peers: + assert self.game == dev.game + dev.allDone = True # print('Closing', self.connname, datetime.datetime.now()) # for dev in Device.sConnnameMap[self.connname]: @@ -360,8 +364,10 @@ def build_cmds(args): if not args.USE_GTK: PLAT_PARMS += ['--curses', '--close-stdin'] - for GAME in range(1, args.NGAMES + 1): + for GAME in range(1, args.NGAMES + 1): + peers = set() ROOM = 'ROOM_%.3d' % (GAME % args.NROOMS) + PHONE_BASE = '%.4d' % (GAME % args.NROOMS) NDEVS = pick_ndevs(args) LOCALS = figure_locals(args, NDEVS) # as array NPLAYERS = sum(LOCALS) @@ -376,27 +382,27 @@ def build_cmds(args): DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV) LOG = '{}/{:02d}_{:02d}_LOG.txt'.format(args.LOGDIR, GAME, DEV) - BOARD_SIZE = ['--board-size', '15'] - # if [ 0 -lt ${#APPS_OLD[@]} ]; then - # # 50% chance of starting out with old app - # NAPPS=$((1+${#APPS_OLD[*]})) - # if [ 0 -lt $((RANDOM%$NAPPS)) ]; then - # APPS[$COUNTER]=${APPS_OLD[$((RANDOM%${#APPS_OLD[*]}))]} - # BOARD_SIZE="--board-size ${BOARD_SIZES_OLD[$((RANDOM%${#BOARD_SIZES_OLD[*]}))]}" - # NEW_ARGS[$COUNTER]="" - # fi - # fi - PARAMS = player_params(args, NLOCALS, NPLAYERS, DEV) PARAMS += PLAT_PARMS - PARAMS += BOARD_SIZE + ['--room', ROOM, '--trade-pct', args.TRADE_PCT, '--sort-tiles'] + PARAMS += ['--board-size', '15', '--trade-pct', args.TRADE_PCT, '--sort-tiles'] + + # We SHOULD support having both SMS and relay working... + if args.ADD_RELAY: + PARAMS += [ '--relay-port', args.PORT, '--room', ROOM, '--host', args.HOST] + if random.randint(0,100) % 100 < g_UDP_PCT_START: + PARAMS += ['--use-udp'] + if args.ADD_SMS: + PARAMS += [ '--sms-number', PHONE_BASE + str(DEV - 1) ] + if args.SMS_FAIL_PCT > 0: + PARAMS += [ '--sms-fail-pct', args.SMS_FAIL_PCT ] + if DEV > 1: + PARAMS += [ '--server-sms-number', PHONE_BASE + '0' ] + if args.UNDO_PCT > 0: PARAMS += ['--undo-pct', args.UNDO_PCT] - PARAMS += [ '--game-dict', DICT, '--relay-port', args.PORT, '--host', args.HOST] + PARAMS += [ '--game-dict', DICT] PARAMS += ['--slow-robot', '1:3', '--skip-confirm'] PARAMS += ['--db', DB] - if random.randint(0,100) % 100 < g_UDP_PCT_START: - PARAMS += ['--use-udp'] PARAMS += ['--drop-nth-packet', g_DROP_N] if random.randint(0, 100) < args.HTTP_PCT: @@ -417,231 +423,21 @@ def build_cmds(args): # it isn't a priority. # PARAMS += ['--seed', args.SEED] PARAMS += PUBLIC - if DEV > 1: + if DEV > 1: PARAMS += ['--force-channel', DEV - 1] else: PARAMS += ['--server'] # print('PARAMS:', PARAMS) - dev = Device(args, GAME, COUNTER, args.APP_NEW, PARAMS, ROOM, DB, LOG, len(LOCALS)) + dev = Device(args, GAME, COUNTER, args.APP_NEW, PARAMS, ROOM, peers, DB, LOG, len(LOCALS)) + peers.add(dev) dev.update_ldevid() devs.append(dev) COUNTER += 1 return devs -# read_resume_cmds() { -# COUNTER=0 -# for LOG in $(ls $LOGDIR/*.txt); do -# echo "need to parse cmd and deal with changes" -# exit 1 -# CMD=$(head -n 1 $LOG) - -# ARGS[$COUNTER]=$CMD -# LOGS[$COUNTER]=$LOG -# PIDS[$COUNTER]=0 - -# set $CMD -# while [ $# -gt 0 ]; do -# case $1 in -# --file) -# FILES[$COUNTER]=$2 -# shift -# ;; -# --room) -# ROOMS[$COUNTER]=$2 -# shift -# ;; -# esac -# shift -# done -# COUNTER=$((COUNTER+1)) -# done -# ROOM_PIDS[$ROOM]=0 -# } - -# launch() { -# KEY=$1 -# LOG=${LOGS[$KEY]} -# APP="${APPS[$KEY]}" -# if [ -z "$APP" ]; then -# echo "error: no app set" -# exit 1 -# fi -# PARAMS="${NEW_ARGS[$KEY]} ${ARGS[$KEY]} ${ARGS_DEVID[$KEY]}" -# exec $APP $PARAMS >/dev/null 2>>$LOG -# } - -# # launch_via_rq() { -# # KEY=$1 -# # RELAYID=$2 -# # PIPE=${PIPES[$KEY]} -# # ../relay/rq -f $RELAYID -o $PIPE & -# # CMD="${CMDS[$KEY]}" -# # exec $CMD >/dev/null 2>>$LOG -# # } - -# send_dead() { -# ID=$1 -# DB=${FILES[$ID]} -# while :; do -# [ -f $DB ] || break # it's gone -# RES=$(echo 'select relayid, seed from games limit 1;' | sqlite3 -separator ' ' $DB || /bin/true) -# [ -n "$RES" ] && break -# sleep 0.2 -# done -# RELAYID=$(echo $RES | awk '{print $1}') -# SEED=$(echo $RES | awk '{print $2}') -# JSON="[{\"relayID\":\"$RELAYID\", \"seed\":$SEED}]" -# curl -G --data-urlencode params="$JSON" http://$HOST/xw4/relay.py/kill >/dev/null 2>&1 -# } - -# close_device() { -# ID=$1 -# MVTO=$2 -# REASON="$3" -# PID=${PIDS[$ID]} -# if [ $PID -ne 0 ]; then -# kill ${PIDS[$ID]} 2>/dev/null -# wait ${PIDS[$ID]} -# ROOM=${ROOMS[$ID]} -# [ ${ROOM_PIDS[$ROOM]} -eq $PID ] && ROOM_PIDS[$ROOM]=0 -# fi -# unset PIDS[$ID] -# unset ARGS[$ID] -# echo "closing game: $REASON" >> ${LOGS[$ID]} -# if [ -n "$MVTO" ]; then -# [ -f "${FILES[$ID]}" ] && mv ${FILES[$ID]} $MVTO -# mv ${LOGS[$ID]} $MVTO -# else -# rm -f ${FILES[$ID]} -# rm -f ${LOGS[$ID]} -# fi -# unset FILES[$ID] -# unset LOGS[$ID] -# unset ROOMS[$ID] -# unset APPS[$ID] -# unset ARGS_DEVID[$ID] - -# COUNT=${#ARGS[*]} -# echo "$COUNT devices left playing..." -# } - -# OBITS="" - -# kill_from_log() { -# LOG=$1 -# RELAYID=$(./scripts/relayID.sh --long $LOG) -# if [ -n "$RELAYID" ]; then -# OBITS="$OBITS -d $RELAYID" -# if [ 0 -eq $(($RANDOM%2)) ]; then -# ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true -# OBITS="" -# fi -# return 0 # success -# fi -# echo "unable to send kill command for $LOG" -# return 1 -# } - -# maybe_resign() { -# if [ "$RESIGN_PCT" -gt 0 ]; then -# KEY=$1 -# LOG=${LOGS[$KEY]} -# if grep -aq XWRELAY_ALLHERE $LOG; then -# if [ $((${RANDOM}%100)) -lt $RESIGN_PCT ]; then -# echo "making $LOG $(connName $LOG) resign..." -# kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true -# fi -# fi -# fi -# } - -# try_upgrade() { -# KEY=$1 -# if [ 0 -lt ${#APPS_OLD[@]} ]; then -# if [ $APP_NEW != "${APPS[$KEY]}" ]; then -# # one in five chance of upgrading -# if [ 0 -eq $((RANDOM % UPGRADE_ODDS)) ]; then -# APPS[$KEY]=$APP_NEW -# NEW_ARGS[$KEY]="$APP_NEW_PARAMS" -# print_cmdline $KEY -# fi -# fi -# fi -# } - -# try_upgrade_upd() { -# KEY=$1 -# CMD=${ARGS[$KEY]} -# if [ "${CMD/--use-udp/}" = "${CMD}" ]; then -# if [ $((RANDOM % 100)) -lt $UDP_PCT_INCR ]; then -# ARGS[$KEY]="$CMD --use-udp" -# echo -n "$(date +%r): " -# echo "upgrading key $KEY to use UDP" -# fi -# fi -# } - -# check_game() { -# KEY=$1 -# LOG=${LOGS[$KEY]} -# CONNNAME="$(connName $LOG)" -# OTHERS="" -# if [ -n "$CONNNAME" ]; then -# if grep -aq '\[unused tiles\]' $LOG ; then -# for INDX in ${!LOGS[*]}; do -# [ $INDX -eq $KEY ] && continue -# ALOG=${LOGS[$INDX]} -# CONNNAME2="$(connName $ALOG)" -# if [ "$CONNNAME2" = "$CONNNAME" ]; then -# if ! grep -aq '\[unused tiles\]' $ALOG; then -# OTHERS="" -# break -# fi -# OTHERS="$OTHERS $INDX" -# fi -# done -# fi -# fi - -# if [ -n "$OTHERS" ]; then -# echo -n "Closing $CONNNAME [$(date)]: " -# # kill_from_logs $OTHERS $KEY -# for ID in $OTHERS $KEY; do -# echo -n "${ID}:${LOGS[$ID]}, " -# kill_from_log ${LOGS[$ID]} || /bin/true -# send_dead $ID -# close_device $ID $DONEDIR "game over" -# done -# echo "" -# # XWRELAY_ERROR_DELETED may be old -# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then -# echo "deleting $LOG $(connName $LOG) b/c another resigned" -# kill_from_log $LOG || /bin/true -# close_device $KEY $DEADDIR "other resigned" -# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then -# echo "deleting $LOG $(connName $LOG) b/c another resigned" -# kill_from_log $LOG || /bin/true -# close_device $KEY $DEADDIR "other resigned" -# else -# maybe_resign $KEY -# fi -# } - -# increment_drop() { -# KEY=$1 -# CMD=${ARGS[$KEY]} -# if [ "$CMD" != "${CMD/drop-nth-packet//}" ]; then -# DROP_N=$(echo $CMD | sed 's,^.*drop-nth-packet \(-*[0-9]*\) .*$,\1,') -# if [ $DROP_N -gt 0 ]; then -# NEXT_N=$((DROP_N+1)) -# ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*drop-nth-packet \)$DROP_N\(.*\)$,\1$NEXT_N\2,") -# fi -# fi -# } - def summarizeTileCounts(devs, endTime, state): global gDeadLaunches shouldGoOn = True @@ -675,13 +471,22 @@ def summarizeTileCounts(devs, endTime, state): nLaunches += launchCount fmtData[1]['data'].append('{:{width}d}'.format(launchCount, width=colWidth)) - nTiles = datum['nTilesLeft'] - fmtData[2]['data'].append(nTiles is None and ('-' * colWidth) or '{:{width}d}'.format(nTiles, width=colWidth)) - if not nTiles is None: totalTiles += int(nTiles) - + # Format tiles left. It's the number in the bag/pool until + # that drops to 0, then the number in the tray preceeded by + # '+'. Only the pool number is included in the totalTiles sum. + nTilesPool = datum['nTilesLeftPool'] + nTilesTray = datum['nTilesLeftTray'] + if nTilesPool is None and nTilesTray is None: + txt = ('-' * colWidth) + elif int(nTilesPool) == 0 and not nTilesTray is None: + txt = '{:+{width}d}'.format(nTilesTray, width=colWidth-1) + else: + txt = '{:{width}d}'.format(nTilesPool, width=colWidth) + totalTiles += int(nTilesPool) + fmtData[2]['data'].append(txt) print('') - print('devs left: {}; tiles left: {}; total launches: {}; {}/{}' + print('devs left: {}; bag tiles left: {}; total launches: {}; {}/{}' .format(nDevs, totalTiles, nLaunches, datetime.datetime.now(), endTime )) fmt = '{head:>%d} {data}' % headWidth for datum in fmtData: datum['data'] = ' '.join(datum['data']) @@ -708,19 +513,21 @@ gDone = False def run_cmds(args, devs): nCores = countCores() endTime = datetime.datetime.now() + datetime.timedelta(minutes = args.TIMEOUT_MINS) - LOOPCOUNT = 0 printState = {} + lastPrint = datetime.datetime.now() while len(devs) > 0 and not gDone: if countCores() > nCores: print('core file count increased; exiting') break - if datetime.datetime.now() > endTime: + now = datetime.datetime.now() + if now > endTime: print('outta time; outta here') break - LOOPCOUNT += 1 - if 0 == LOOPCOUNT % 20: + # print stats every 5 seconds + if now - lastPrint > datetime.timedelta(seconds = 5): + lastPrint = now if not summarizeTileCounts(devs, endTime, printState): print('no change in too long; exiting') break @@ -742,20 +549,16 @@ def run_cmds(args, devs): # PIDS[$KEY]=$PID # ROOM_PIDS[$ROOM]=$PID # MINEND[$KEY]=$(($NOW + $MINRUN)) - elif not dev.minTimeExpired(): - # print('sleeping...') - time.sleep(1.0) - else: + elif dev.minTimeExpired(): dev.kill() if dev.handleAllDone(): devs.remove(dev) - # if g_DROP_N >= 0: dev.increment_drop() - # update_ldevid $KEY - + else: + time.sleep(1.0) # if we get here via a break, kill any remaining games if devs: - print('stopping %d remaining games' % (len(devs))) + print('stopping {} remaining games'.format(len(devs))) for dev in devs: if dev.running(): dev.kill() @@ -828,6 +631,7 @@ def mkParser(): help = 'No game will have fewer devices than this') parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4, help = 'No game will have more devices than this') + parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2, help = 'Keep each run alive at least this many seconds') # # echo " [--new-app &2 @@ -852,6 +656,10 @@ def mkParser(): parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int) parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 0, type = int) + parser.add_argument('--add-sms', dest = 'ADD_SMS', default = False, action = 'store_true') + parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int) + parser.add_argument('--remove-relay', dest = 'ADD_RELAY', default = True, action = 'store_false') + parser.add_argument('--with-valgrind', dest = 'VALGRIND', default = False, action = 'store_true') @@ -1058,6 +866,10 @@ def main(): signal.signal(signal.SIGINT, termHandler) args = parseArgs() + # Hack: old files confuse things. Remove is simple fix good for now + if args.ADD_SMS: + try: rmtree('/tmp/xw_sms') + except: None devs = build_cmds(args) nDevs = len(devs) run_cmds(args, devs) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 99492a87a..aecdb29ec 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -1135,7 +1135,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid, void DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx, - int byteaIndex, uint8_t* buf, size_t* buflen ) + int byteaIndex, vector& buf ) { const char* from = NULL; if ( useB64 ) { @@ -1146,22 +1146,19 @@ DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx, from = PQgetvalue( result, rowIndx, byteaIndex ); } - size_t to_length; if ( useB64 ) { gsize out_len; guchar* txt = g_base64_decode( (const gchar*)from, &out_len ); - to_length = out_len; - assert( to_length <= *buflen ); - memcpy( buf, txt, to_length ); + buf.insert( buf.end(), txt, txt + out_len ); + assert( buf.size() == out_len ); g_free( txt ); } else { - uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from, - &to_length ); - assert( to_length <= *buflen ); - memcpy( buf, bytes, to_length ); + size_t to_length; + uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from, &to_length ); + buf.insert( buf.end(), bytes, bytes + to_length ); + assert( buf.size() == to_length ); PQfreemem( bytes ); } - *buflen = to_length; } void @@ -1193,12 +1190,9 @@ DBMgr::storedMessagesImpl( string test, vector& msgs, bool hasConnname = connname != NULL && '\0' != connname[0]; MsgInfo msg( id, token, hasConnname ); - uint8_t buf[1024]; - size_t buflen = sizeof(buf); - decodeMessage( result, m_useB64, ii, 1, 2, buf, &buflen ); + decodeMessage( result, m_useB64, ii, 1, 2, msg.msg ); size_t msglen = atoi( PQgetvalue( result, ii, 3 ) ); - assert( 0 == msglen || buflen == msglen ); - msg.msg.insert( msg.msg.end(), buf, &buf[buflen] ); + assert( 0 == msglen || msg.msg.size() == msglen ); msgs.push_back( msg ); } PQclear( result ); diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index d23622c7e..026d06851 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -166,7 +166,8 @@ class DBMgr { int getCountWhere( const char* table, string& test ); void RemoveStoredMessages( string& msgIDs ); void decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx, - int byteaIndex, uint8_t* buf, size_t* buflen ); + int byteaIndex, vector& buf ); + void storedMessagesImpl( string query, vector& msgs, bool nullConnnameOK ); int CountStoredMessages( const char* const connName, int hid ); diff --git a/xwords4/relay/devmgr.cpp b/xwords4/relay/devmgr.cpp index 86e682423..ef3163999 100644 --- a/xwords4/relay/devmgr.cpp +++ b/xwords4/relay/devmgr.cpp @@ -57,8 +57,7 @@ DevMgr::rememberDevice( DevIDRelay devid, const AddrInfo::AddrUnion* saddr ) pair::iterator, bool> result = m_devAddrMap.insert( pair( devid, rec ) ); if ( !result.second ) { - logf( XW_LOGINFO, "%s: replacing address for %d; was %s, now %s", - __func__, devid, result.first->second, rec ); + logf( XW_LOGINFO, "%s: replacing address for %d", __func__, devid ); result.first->second = rec; } diff --git a/xwords4/relay/udpack.cpp b/xwords4/relay/udpack.cpp index a62b24998..5e3e9e7fc 100644 --- a/xwords4/relay/udpack.cpp +++ b/xwords4/relay/udpack.cpp @@ -35,16 +35,16 @@ UDPAckTrack::nextPacketID( XWRelayReg cmd ) { uint32_t result = 0; if ( shouldAck( cmd ) ) { - result = get()->nextPacketIDImpl(); + result = get()->nextPacketIDImpl( cmd ); assert( PACKETID_NONE != result ); } return result; } -/* static*/ void +/* static*/ string UDPAckTrack::recordAck( uint32_t packetID ) { - get()->recordAckImpl( packetID ); + return get()->recordAckImpl( packetID ); } /* static */ bool @@ -97,33 +97,37 @@ UDPAckTrack::ackLimit() } uint32_t -UDPAckTrack::nextPacketIDImpl() +UDPAckTrack::nextPacketIDImpl( XWRelayReg cmd ) { MutexLock ml( &m_mutex ); uint32_t result = ++m_nextID; - AckRecord record; + AckRecord record( cmd , result ); m_pendings.insert( pair(result, record) ); return result; } -void +string UDPAckTrack::recordAckImpl( uint32_t packetID ) { + string str; map::iterator iter; MutexLock ml( &m_mutex ); iter = m_pendings.find( packetID ); if ( m_pendings.end() == iter ) { logf( XW_LOGERROR, "%s: packet ID %d not found", __func__, packetID ); } else { - time_t took = time( NULL ) - iter->second.m_createTime; + AckRecord& rec = iter->second; + str = rec.toStr(); + time_t took = time( NULL ) - rec.m_createTime; if ( 5 < took ) { - logf( XW_LOGERROR, "%s: packet ID %d took %d seconds to get acked", - __func__, packetID, took ); + logf( XW_LOGERROR, "%s: packet %s took %d seconds to get acked", + __func__, str.c_str(), took ); } callProc( iter, true ); m_pendings.erase( iter ); } + return str; } bool @@ -134,8 +138,8 @@ UDPAckTrack::setOnAckImpl( OnAckProc proc, uint32_t packetID, void* data ) MutexLock ml( &m_mutex ); map::iterator iter = m_pendings.find( packetID ); if ( m_pendings.end() != iter ) { - iter->second.proc = proc; - iter->second.data = data; + iter->second.m_proc = proc; + iter->second.m_data = data; } } return canAdd; @@ -180,12 +184,12 @@ void UDPAckTrack::callProc( const map::iterator iter, bool acked ) { const AckRecord* record = &(iter->second); - OnAckProc proc = record->proc; + OnAckProc proc = record->m_proc; if ( NULL != proc ) { uint32_t packetID = iter->first; logf( XW_LOGINFO, "%s(packetID=%d, acked=%d, proc=%p)", __func__, packetID, acked, proc ); - (*proc)( acked, packetID, record->data ); + (*proc)( acked, packetID, record->m_data ); } } @@ -195,15 +199,16 @@ UDPAckTrack::threadProc() for ( ; ; ) { time_t limit = ackLimit(); sleep( limit / 2 ); - vector older; + vector older; { MutexLock ml( &m_mutex ); time_t now = time( NULL ); map::iterator iter; for ( iter = m_pendings.begin(); m_pendings.end() != iter; ) { - time_t took = now - iter->second.m_createTime; + AckRecord& rec = iter->second; + time_t took = now - rec.m_createTime; if ( limit < took ) { - older.push_back( iter->first ); + older.push_back( rec.toStr() ); callProc( iter, false ); m_pendings.erase( iter++ ); } else { @@ -212,14 +217,14 @@ UDPAckTrack::threadProc() } } if ( 0 < older.size() ) { - StrWPF leaked; - vector::const_iterator iter = older.begin(); + string leaked; + vector::const_iterator iter = older.begin(); for ( ; ; ) { - leaked.catf( "%d", *iter ); + leaked += iter->c_str(); if ( ++iter == older.end() ) { break; } - leaked.catf( ", " ); + leaked += ", "; } logf( XW_LOGERROR, "%s: these packets leaked (were not ack'd " "within %d seconds): %s", __func__, diff --git a/xwords4/relay/udpack.h b/xwords4/relay/udpack.h index d7e6d453c..b11166eb8 100644 --- a/xwords4/relay/udpack.h +++ b/xwords4/relay/udpack.h @@ -1,6 +1,7 @@ -/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make -j3"; -*- */ /* - * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 2013 - 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 @@ -20,6 +21,8 @@ #ifndef _UDPACK_H_ #define _UDPACK_H_ +#include + #include "xwrelay_priv.h" #include "xwrelay.h" #include "strwpf.h" @@ -28,10 +31,26 @@ typedef void (*OnAckProc)( bool acked, uint32_t packetID, void* data ); class AckRecord { public: - AckRecord() { m_createTime = time( NULL ); proc = NULL; } + AckRecord( XWRelayReg cmd, uint32_t id ) { + m_createTime = time( NULL ); + m_proc = NULL; + m_cmd = cmd; + m_id = id; + } + + string toStr() + { + char buf[64]; + sprintf( buf, "%d/%s", m_id, msgToStr( m_cmd ) ); + string str(buf); + return str; + } time_t m_createTime; - OnAckProc proc; - void* data; + OnAckProc m_proc; + XWRelayReg m_cmd; + void* m_data; + private: + uint32_t m_id; }; class UDPAckTrack { @@ -39,7 +58,7 @@ class UDPAckTrack { static const uint32_t PACKETID_NONE = 0; static uint32_t nextPacketID( XWRelayReg cmd ); - static void recordAck( uint32_t packetID ); + static string recordAck( uint32_t packetID ); static bool setOnAck( OnAckProc proc, uint32_t packetID, void* data ); static bool shouldAck( XWRelayReg cmd ); /* called from ctrl port */ @@ -51,8 +70,8 @@ class UDPAckTrack { static void* thread_main( void* arg ); UDPAckTrack(); time_t ackLimit(); - uint32_t nextPacketIDImpl(); - void recordAckImpl( uint32_t packetID ); + uint32_t nextPacketIDImpl( XWRelayReg cmd ); + string recordAckImpl( uint32_t packetID ); bool setOnAckImpl( OnAckProc proc, uint32_t packetID, void* data ); void callProc( const map::iterator iter, bool acked ); void printAcksImpl( StrWPF& out ); diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 563a664c0..6ec028328 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1702,7 +1702,7 @@ retrieveMessages( DevID& devID, const AddrInfo* addr ) } } -static const char* +const char* msgToStr( XWRelayReg msg ) { const char* str; @@ -1840,7 +1840,7 @@ handle_udp_packet( PacketThreadClosure* ptc ) } case XWPDEV_KEEPALIVE: - case XWPDEV_RQSTMSGS: { // here + case XWPDEV_RQSTMSGS: { DevID devID( ID_TYPE_RELAY ); if ( getVLIString( &ptr, end, devID.m_devIDString ) ) { const AddrInfo* addr = ptc->addr(); @@ -1855,8 +1855,8 @@ handle_udp_packet( PacketThreadClosure* ptc ) case XWPDEV_ACK: { uint32_t packetID; if ( vli2un( &ptr, end, &packetID ) ) { - logf( XW_LOGINFO, "%s: got ack for packet %d", __func__, packetID ); - UDPAckTrack::recordAck( packetID ); + string str = UDPAckTrack::recordAck( packetID ); + logf( XW_LOGINFO, "%s: got ack for packet %s", __func__, str.c_str() ); } break; } diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h index ee85f98c7..357dfe54d 100644 --- a/xwords4/relay/xwrelay_priv.h +++ b/xwords4/relay/xwrelay_priv.h @@ -69,6 +69,7 @@ int read_packet( int sock, uint8_t* buf, int buflen ); void onMsgAcked( bool acked, uint32_t packetID, void* data ); const char* cmdToStr( XWRELAY_Cmd cmd ); +const char* msgToStr( XWRelayReg msg ); extern class ListenerMgr g_listeners;