From 7b3bd2fbf76df306a6cc44837ae1de4e17b1fce9 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 21 Apr 2015 13:04:00 -0700 Subject: [PATCH] Replace rather than insert into push db on duplicate incoming. Combined with the switch to server acked messages, this will prevent the race condition that occurred when an incoming message showed up at exactly the moment the app updated. It'd be great if we could just do REPLACE INTO, but it's too late to add a UNIQUE() constraint. =( Fixes #2287 Closes #3029 // FREEBIE --- .../securesms/DatabaseUpgradeActivity.java | 2 +- .../securesms/database/PushDatabase.java | 50 ++++++++++++++++--- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java index 42aa764548..fa59aa224f 100644 --- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java +++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java @@ -58,7 +58,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73; public static final int NO_V1_VERSION = 83; public static final int SIGNED_PREKEY_VERSION = 83; - public static final int NO_DECRYPT_QUEUE_VERSION = 84; + public static final int NO_DECRYPT_QUEUE_VERSION = 113; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index cdda7a6164..2e6854f05a 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -3,10 +3,13 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; import android.util.Log; import org.thoughtcrime.securesms.util.Base64; +import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import java.io.IOException; @@ -30,15 +33,21 @@ public class PushDatabase extends Database { super(context, databaseHelper); } - public long insert(TextSecureEnvelope envelope) { - ContentValues values = new ContentValues(); - values.put(TYPE, envelope.getType()); - values.put(SOURCE, envelope.getSource()); - values.put(DEVICE_ID, envelope.getSourceDevice()); - values.put(BODY, Base64.encodeBytes(envelope.getMessage())); - values.put(TIMESTAMP, envelope.getTimestamp()); + public long insert(@NonNull TextSecureEnvelope envelope) { + Optional messageId = find(envelope); - return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); + if (messageId.isPresent()) { + return messageId.get(); + } else { + ContentValues values = new ContentValues(); + values.put(TYPE, envelope.getType()); + values.put(SOURCE, envelope.getSource()); + values.put(DEVICE_ID, envelope.getSourceDevice()); + values.put(BODY, Base64.encodeBytes(envelope.getMessage())); + values.put(TIMESTAMP, envelope.getTimestamp()); + + return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); + } } public TextSecureEnvelope get(long id) throws NoSuchMessageException { @@ -80,6 +89,31 @@ public class PushDatabase extends Database { return new Reader(cursor); } + private Optional find(TextSecureEnvelope envelope) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = null; + + try { + cursor = database.query(TABLE_NAME, null, TYPE + " = ? AND " + SOURCE + " = ? AND " + + DEVICE_ID + " = ? AND " + BODY + " = ? AND " + + TIMESTAMP + " = ?" , + new String[] {String.valueOf(envelope.getType()), + envelope.getSource(), + String.valueOf(envelope.getSourceDevice()), + Base64.encodeBytes(envelope.getMessage()), + String.valueOf(envelope.getTimestamp())}, + null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return Optional.of(cursor.getLong(cursor.getColumnIndexOrThrow(ID))); + } else { + return Optional.absent(); + } + } finally { + if (cursor != null) cursor.close(); + } + } + public static class Reader { private final Cursor cursor;