From c1cc2b064c4fdafd0699552637c96a2ed26263d2 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 7 Dec 2022 14:31:32 -0500 Subject: [PATCH] Convert SenderKeyTable to kotlin. --- .../securesms/database/SenderKeyTable.java | 147 ------------------ .../securesms/database/SenderKeyTable.kt | 130 ++++++++++++++++ .../org/signal/core/util/CursorExtensions.kt | 12 ++ 3 files changed, 142 insertions(+), 147 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.java deleted file mode 100644 index 3fa418bb5..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.thoughtcrime.securesms.database; - - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.signal.libsignal.protocol.groups.state.SenderKeyRecord; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.signal.core.util.CursorUtil; -import org.signal.core.util.SqlUtil; -import org.whispersystems.signalservice.api.push.DistributionId; - -/** - * Stores all of the sender keys -- both the ones we create, and the ones we're told about. - * - * When working with SenderKeys, keep this in mind: they're not *really* keys. They're sessions. - * The name is largely historical, and there's too much momentum to change it. - */ -public class SenderKeyTable extends DatabaseTable { - - private static final String TAG = Log.tag(SenderKeyTable.class); - - public static final String TABLE_NAME = "sender_keys"; - - private static final String ID = "_id"; - public static final String ADDRESS = "address"; - public static final String DEVICE = "device"; - public static final String DISTRIBUTION_ID = "distribution_id"; - public static final String RECORD = "record"; - public static final String CREATED_AT = "created_at"; - - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + - ADDRESS + " TEXT NOT NULL, " + - DEVICE + " INTEGER NOT NULL, " + - DISTRIBUTION_ID + " TEXT NOT NULL, " + - RECORD + " BLOB NOT NULL, " + - CREATED_AT + " INTEGER NOT NULL, " + - "UNIQUE(" + ADDRESS + "," + DEVICE + ", " + DISTRIBUTION_ID + ") ON CONFLICT REPLACE);"; - - SenderKeyTable(Context context, SignalDatabase databaseHelper) { - super(context, databaseHelper); - } - - public void store(@NonNull SignalProtocolAddress address, @NonNull DistributionId distributionId, @NonNull SenderKeyRecord record) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - - db.beginTransaction(); - try { - ContentValues updateValues = new ContentValues(); - updateValues.put(RECORD, record.serialize()); - - String query = ADDRESS + " = ? AND " + DEVICE + " = ? AND " + DISTRIBUTION_ID + " = ?"; - String[] args = SqlUtil.buildArgs(address.getName(), address.getDeviceId(), distributionId); - int updateCount = db.update(TABLE_NAME, updateValues, query, args); - - if (updateCount <= 0) { - Log.d(TAG, "New sender key " + distributionId + " from " + address); - - ContentValues insertValues = new ContentValues(); - insertValues.put(ADDRESS, address.getName()); - insertValues.put(DEVICE, address.getDeviceId()); - insertValues.put(DISTRIBUTION_ID, distributionId.toString()); - insertValues.put(RECORD, record.serialize()); - insertValues.put(CREATED_AT, System.currentTimeMillis()); - db.insertWithOnConflict(TABLE_NAME, null, insertValues, SQLiteDatabase.CONFLICT_REPLACE); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public @Nullable SenderKeyRecord load(@NonNull SignalProtocolAddress address, @NonNull DistributionId distributionId) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - - String query = ADDRESS + " = ? AND " + DEVICE + " = ? AND " + DISTRIBUTION_ID + " = ?"; - String[] args = SqlUtil.buildArgs(address.getName(), address.getDeviceId(), distributionId); - - try (Cursor cursor = db.query(TABLE_NAME, new String[]{ RECORD }, query, args, null, null, null)) { - if (cursor.moveToFirst()) { - try { - return new SenderKeyRecord(CursorUtil.requireBlob(cursor, RECORD)); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - } - } - - return null; - } - - /** - * Gets when the sender key session was created, or -1 if it doesn't exist. - */ - public long getCreatedTime(@NonNull SignalProtocolAddress address, @NonNull DistributionId distributionId) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - - String query = ADDRESS + " = ? AND " + DEVICE + " = ? AND " + DISTRIBUTION_ID + " = ?"; - String[] args = SqlUtil.buildArgs(address.getName(), address.getDeviceId(), distributionId); - - try (Cursor cursor = db.query(TABLE_NAME, new String[]{ CREATED_AT }, query, args, null, null, null)) { - if (cursor.moveToFirst()) { - return CursorUtil.requireLong(cursor, CREATED_AT); - } - } - - return -1; - } - - /** - * Removes all sender key session state for all devices for the provided recipient-distributionId pair. - */ - public void deleteAllFor(@NonNull String addressName, @NonNull DistributionId distributionId) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - String query = ADDRESS + " = ? AND " + DISTRIBUTION_ID + " = ?"; - String[] args = SqlUtil.buildArgs(addressName, distributionId); - - db.delete(TABLE_NAME, query, args); - } - - /** - * Get metadata for all sender keys created by the local user. Used for debugging. - */ - public Cursor getAllCreatedBySelf() { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - String query = ADDRESS + " = ?"; - String[] args = SqlUtil.buildArgs(SignalStore.account().requireAci()); - - return db.query(TABLE_NAME, new String[]{ ID, DISTRIBUTION_ID, CREATED_AT }, query, args, null, null, CREATED_AT + " DESC"); - } - - /** - * Deletes all database state. - */ - public void deleteAll() { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - db.delete(TABLE_NAME, null, null); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt new file mode 100644 index 000000000..0666e2a16 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt @@ -0,0 +1,130 @@ +package org.thoughtcrime.securesms.database + +import android.content.Context +import android.database.Cursor +import androidx.core.content.contentValuesOf +import org.signal.core.util.CursorUtil +import org.signal.core.util.delete +import org.signal.core.util.firstOrNull +import org.signal.core.util.logging.Log +import org.signal.core.util.requireLong +import org.signal.core.util.select +import org.signal.core.util.update +import org.signal.core.util.withinTransaction +import org.signal.libsignal.protocol.InvalidMessageException +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.signal.libsignal.protocol.groups.state.SenderKeyRecord +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.whispersystems.signalservice.api.push.DistributionId + +/** + * Stores all of the sender keys -- both the ones we create, and the ones we're told about. + * + * When working with SenderKeys, keep this in mind: they're not *really* keys. They're sessions. + * The name is largely historical, and there's too much momentum to change it. + */ +class SenderKeyTable internal constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper) { + companion object { + private val TAG = Log.tag(SenderKeyTable::class.java) + const val TABLE_NAME = "sender_keys" + private const val ID = "_id" + const val ADDRESS = "address" + const val DEVICE = "device" + const val DISTRIBUTION_ID = "distribution_id" + const val RECORD = "record" + const val CREATED_AT = "created_at" + const val CREATE_TABLE = """ + CREATE TABLE $TABLE_NAME ( + $ID INTEGER PRIMARY KEY AUTOINCREMENT, + $ADDRESS TEXT NOT NULL, + $DEVICE INTEGER NOT NULL, + $DISTRIBUTION_ID TEXT NOT NULL, + $RECORD BLOB NOT NULL, + $CREATED_AT INTEGER NOT NULL, + UNIQUE($ADDRESS,$DEVICE, $DISTRIBUTION_ID) ON CONFLICT REPLACE + ) + """ + } + + fun store(address: SignalProtocolAddress, distributionId: DistributionId, record: SenderKeyRecord) { + writableDatabase.withinTransaction { db -> + val updateCount = db.update(TABLE_NAME) + .values(RECORD to record.serialize()) + .where("$ADDRESS = ? AND $DEVICE = ? AND $DISTRIBUTION_ID = ?", address.name, address.deviceId, distributionId) + .run() + + if (updateCount <= 0) { + Log.d(TAG, "New sender key $distributionId from $address") + val insertValues = contentValuesOf( + ADDRESS to address.name, + DEVICE to address.deviceId, + DISTRIBUTION_ID to distributionId.toString(), + RECORD to record.serialize(), + CREATED_AT to System.currentTimeMillis(), + ) + db.insertWithOnConflict(TABLE_NAME, null, insertValues, SQLiteDatabase.CONFLICT_REPLACE) + } + } + } + + fun load(address: SignalProtocolAddress, distributionId: DistributionId): SenderKeyRecord? { + return readableDatabase + .select(RECORD) + .from(TABLE_NAME) + .where("$ADDRESS = ? AND $DEVICE = ? AND $DISTRIBUTION_ID = ?", address.name, address.deviceId, distributionId) + .run() + .firstOrNull { cursor -> + try { + SenderKeyRecord(CursorUtil.requireBlob(cursor, RECORD)) + } catch (e: InvalidMessageException) { + Log.w(TAG, e) + null + } + } + } + + /** + * Gets when the sender key session was created, or -1 if it doesn't exist. + */ + fun getCreatedTime(address: SignalProtocolAddress, distributionId: DistributionId): Long { + return readableDatabase + .select(CREATED_AT) + .from(TABLE_NAME) + .where("$ADDRESS = ? AND $DEVICE = ? AND $DISTRIBUTION_ID = ?", address.name, address.deviceId, distributionId) + .run() + .firstOrNull { cursor -> + cursor.requireLong(CREATED_AT) + } ?: -1 + } + + /** + * Removes all sender key session state for all devices for the provided recipient-distributionId pair. + */ + fun deleteAllFor(addressName: String, distributionId: DistributionId) { + writableDatabase + .delete(TABLE_NAME) + .where("$ADDRESS = ? AND $DISTRIBUTION_ID = ?", addressName, distributionId) + .run() + } + + /** + * Get metadata for all sender keys created by the local user. Used for debugging. + */ + fun getAllCreatedBySelf(): Cursor { + return readableDatabase + .select(ID, DISTRIBUTION_ID, CREATED_AT) + .from(TABLE_NAME) + .where("$ADDRESS = ?", SignalStore.account().requireAci()) + .orderBy("$CREATED_AT DESC") + .run() + } + + /** + * Deletes all database state. + */ + fun deleteAll() { + writableDatabase + .delete(TABLE_NAME) + .run() + } +} diff --git a/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt b/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt index b321dbdb0..c2cf2a25e 100644 --- a/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt @@ -105,4 +105,16 @@ inline fun Cursor.readToSet(predicate: (T) -> Boolean = { true }, mapper: (C return set } +inline fun Cursor.firstOrNull(predicate: (T) -> Boolean = { true }, mapper: (Cursor) -> T): T? { + use { + while (moveToNext()) { + val record = mapper(this) + if (predicate(record)) { + return record + } + } + } + return null +} + fun Boolean.toInt(): Int = if (this) 1 else 0