diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 4771324407..51950ccff7 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -594,13 +594,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc SignalProtocolAddress address = new SignalProtocolAddress(publicKey, SignalServiceAddress.DEFAULT_DEVICE_ID); boolean hasSession = new TextSecureSessionStore(this).containsSession(address); if (hasSession) { return; } - // Check that we didn't already send or process a session request + // Check that we didn't already send a session request LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); - boolean hasSentOrProcessedSessionRequest = (apiDB.getSessionRequestTimestamp(publicKey) != null); - if (hasSentOrProcessedSessionRequest) { return; } + boolean hasSentSessionRequest = (apiDB.getSessionRequestSentTimestamp(publicKey) != null); + if (hasSentSessionRequest) { return; } // Send the session request - DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestTimestamp(publicKey, new Date().getTime()); - PushSessionRequestMessageSendJob job = new PushSessionRequestMessageSendJob(publicKey); + long timestamp = new Date().getTime(); + DatabaseFactory.getLokiAPIDatabase(this).setSessionRequestSentTimestamp(publicKey, timestamp); + PushSessionRequestMessageSendJob job = new PushSessionRequestMessageSendJob(publicKey, timestamp); jobManager.add(job); } // endregion diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index a2482a5b17..86b4f9cd2d 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -83,8 +83,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV7 = 28; private static final int lokiV8 = 29; private static final int lokiV9 = 30; + private static final int lokiV10 = 31; - private static final int DATABASE_VERSION = lokiV9; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes + private static final int DATABASE_VERSION = lokiV10; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -143,6 +144,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand()); db.execSQL(LokiAPIDatabase.getCreateUserCountCacheCommand()); db.execSQL(LokiAPIDatabase.getCreateSessionRequestTimestampCacheCommand()); + db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand()); + db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); @@ -589,6 +592,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathCacheCommand()); } + if (oldVersion < lokiV10) { + db.execSQL(LokiAPIDatabase.getCreateSessionRequestSentTimestampCacheCommand()); + db.execSQL(LokiAPIDatabase.getCreateSessionRequestProcessedTimestampCacheCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index a3cbef083d..e203be2b29 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -70,11 +70,25 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( private val publicChatID = "public_chat_id" private val userCount = "user_count" @JvmStatic val createUserCountCacheCommand = "CREATE TABLE $userCountCache ($publicChatID STRING PRIMARY KEY, $userCount INTEGER DEFAULT 0);" - // Session request timestamp cache + // Session request sent timestamp cache + private val sessionRequestSentTimestampCache = "session_request_sent_timestamp_cache" + private val sessionRequestSentPublicKey = "public_key" + private val sessionRequestSentTimestamp = "timestamp" + @JvmStatic val createSessionRequestSentTimestampCacheCommand = "CREATE TABLE $sessionRequestSentTimestampCache ($sessionRequestSentPublicKey STRING PRIMARY KEY, $sessionRequestSentTimestamp INTEGER DEFAULT 0);" + // Session request processed timestamp cache + private val sessionRequestProcessedTimestampCache = "session_request_processed_timestamp_cache" + private val sessionRequestProcessedPublicKey = "public_key" + private val sessionRequestProcessedTimestamp = "timestamp" + @JvmStatic val createSessionRequestProcessedTimestampCacheCommand = "CREATE TABLE $sessionRequestProcessedTimestampCache ($sessionRequestProcessedPublicKey STRING PRIMARY KEY, $sessionRequestProcessedTimestamp INTEGER DEFAULT 0);" + + + + // region Deprecated private val sessionRequestTimestampCache = "session_request_timestamp_cache" private val sessionRequestPublicKey = "public_key" private val timestamp = "timestamp" @JvmStatic val createSessionRequestTimestampCacheCommand = "CREATE TABLE $sessionRequestTimestampCache ($sessionRequestPublicKey STRING PRIMARY KEY, $timestamp INTEGER DEFAULT 0);" + // endregion } override fun getSnodePool(): Set { @@ -323,17 +337,30 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(userCountCache, row, "$publicChatID = ?", wrap(index)) } - override fun getSessionRequestTimestamp(publicKey: String): Long? { + override fun getSessionRequestSentTimestamp(publicKey: String): Long? { val database = databaseHelper.readableDatabase - return database.get(sessionRequestTimestampCache, "$LokiAPIDatabase.publicKey = ?", wrap(publicKey)) { cursor -> - cursor.getInt(LokiAPIDatabase.timestamp) + return database.get(sessionRequestSentTimestampCache, "${LokiAPIDatabase.sessionRequestSentPublicKey} = ?", wrap(publicKey)) { cursor -> + cursor.getInt(LokiAPIDatabase.sessionRequestSentTimestamp) }?.toLong() } - override fun setSessionRequestTimestamp(publicKey: String, timestamp: Long) { + override fun setSessionRequestSentTimestamp(publicKey: String, timestamp: Long) { val database = databaseHelper.writableDatabase - val row = wrap(mapOf(LokiAPIDatabase.sessionRequestPublicKey to publicKey, LokiAPIDatabase.timestamp to timestamp.toString())) - database.insertOrUpdate(sessionRequestTimestampCache, row, "${LokiAPIDatabase.sessionRequestPublicKey} = ?", wrap(publicKey)) + val row = wrap(mapOf(LokiAPIDatabase.sessionRequestSentPublicKey to publicKey, LokiAPIDatabase.sessionRequestSentTimestamp to timestamp.toString())) + database.insertOrUpdate(sessionRequestSentTimestampCache, row, "${LokiAPIDatabase.sessionRequestSentPublicKey} = ?", wrap(publicKey)) + } + + override fun getSessionRequestProcessedTimestamp(publicKey: String): Long? { + val database = databaseHelper.readableDatabase + return database.get(sessionRequestProcessedTimestampCache, "${LokiAPIDatabase.sessionRequestProcessedPublicKey} = ?", wrap(publicKey)) { cursor -> + cursor.getInt(LokiAPIDatabase.sessionRequestProcessedTimestamp) + }?.toLong() + } + + override fun setSessionRequestProcessedTimestamp(publicKey: String, timestamp: Long) { + val database = databaseHelper.writableDatabase + val row = wrap(mapOf(LokiAPIDatabase.sessionRequestProcessedPublicKey to publicKey, LokiAPIDatabase.sessionRequestProcessedTimestamp to timestamp.toString())) + database.insertOrUpdate(sessionRequestProcessedTimestampCache, row, "${LokiAPIDatabase.sessionRequestProcessedPublicKey} = ?", wrap(publicKey)) } } diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index e2b6c0f5f5..771e6821b1 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -169,7 +169,7 @@ object MultiDeviceProtocol { TextSecurePreferences.setMasterHexEncodedPublicKey(context, deviceLink.masterPublicKey) TextSecurePreferences.setMultiDevice(context, true) FileServerAPI.shared.addDeviceLink(deviceLink) - org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.duplicate_handleProfileKey(context, content) + org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol.handleProfileKeyUpdate(context, content) } @JvmStatic diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt index b55e30aa65..f530468235 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt @@ -19,22 +19,23 @@ import java.security.SecureRandom import java.util.* import java.util.concurrent.TimeUnit -class PushSessionRequestMessageSendJob private constructor(parameters: Parameters, private val publicKey: String) : BaseJob(parameters) { +class PushSessionRequestMessageSendJob private constructor(parameters: Parameters, private val publicKey: String, private val timestamp: Long) : BaseJob(parameters) { companion object { const val KEY = "PushSessionRequestMessageSendJob" } - constructor(publicKey: String) : this(Parameters.Builder() + constructor(publicKey: String, timestamp: Long) : this(Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(1) .build(), - publicKey) + publicKey, + timestamp) override fun serialize(): Data { - return Data.Builder().putString("publicKey", publicKey).build() + return Data.Builder().putString("publicKey", publicKey).putLong("timestamp", timestamp).build() } override fun getFactoryKey(): String { return KEY } @@ -83,14 +84,21 @@ class PushSessionRequestMessageSendJob private constructor(parameters: Parameter return false } - override fun onCanceled() { } + override fun onCanceled() { + // Update the DB on fail if this is still the most recently sent session request (should always be true) + val apiDB = DatabaseFactory.getLokiAPIDatabase(context) + if (apiDB.getSessionRequestSentTimestamp(publicKey) == timestamp) { + apiDB.setSessionRequestSentTimestamp(publicKey, 0) + } + } class Factory : Job.Factory { override fun create(parameters: Parameters, data: Data): PushSessionRequestMessageSendJob { try { val publicKey = data.getString("publicKey") - return PushSessionRequestMessageSendJob(parameters, publicKey) + val timestamp = data.getLong("timestamp") + return PushSessionRequestMessageSendJob(parameters, publicKey, timestamp) } catch (e: IOException) { throw AssertionError(e) } diff --git a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt index bf24cc5d10..c442e060a7 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/SessionManagementProtocol.kt @@ -53,25 +53,30 @@ object SessionManagementProtocol { } } + @JvmStatic + fun shouldProcessSessionRequest(context: Context, publicKey: String, timestamp: Long): Boolean { + val apiDB = DatabaseFactory.getLokiAPIDatabase(context) + val sentTimestamp = apiDB.getSessionRequestSentTimestamp(publicKey) ?: 0 + val processedTimestamp = apiDB.getSessionRequestProcessedTimestamp(publicKey) ?: 0 + return timestamp > sentTimestamp && timestamp > processedTimestamp + } + @JvmStatic fun handlePreKeyBundleMessageIfNeeded(context: Context, content: SignalServiceContent) { - val publicKey = content.sender - val recipient = recipient(context, publicKey) - if (recipient.isGroupRecipient) { return } // Should never occur val preKeyBundleMessage = content.preKeyBundleMessage.orNull() ?: return - val registrationID = TextSecurePreferences.getLocalRegistrationId(context) // TODO: It seems wrong to use the local registration ID for this? - val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context) + val publicKey = content.sender + if (recipient(context, publicKey).isGroupRecipient) { return } // Should never occur Log.d("Loki", "Received a pre key bundle from: $publicKey.") - val sessionRequestTimestamp = DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestTimestamp(content.sender) - if (sessionRequestTimestamp != null && content.timestamp < sessionRequestTimestamp) { - // We sent or processed a session request after this one was sent - Log.d("Loki", "Ignoring session request from: ${content.sender}.") + if (!shouldProcessSessionRequest(context, publicKey, content.timestamp)) { + Log.d("Loki", "Ignoring session request from: $publicKey.") return } + val registrationID = TextSecurePreferences.getLocalRegistrationId(context) + val lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context) val preKeyBundle = preKeyBundleMessage.getPreKeyBundle(registrationID) - lokiPreKeyBundleDatabase.setPreKeyBundle(content.sender, preKeyBundle) - DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestTimestamp(content.sender, Date().time) - val job = PushNullMessageSendJob(content.sender) + lokiPreKeyBundleDatabase.setPreKeyBundle(publicKey, preKeyBundle) + DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestProcessedTimestamp(publicKey, Date().time) + val job = PushNullMessageSendJob(publicKey) ApplicationContext.getInstance(context).jobManager.add(job) }