kopia lustrzana https://github.com/ryukoposting/Signal-Android
Convert SenderKeyTable to kotlin.
rodzic
98980b8192
commit
c1cc2b064c
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,4 +105,16 @@ inline fun <T> Cursor.readToSet(predicate: (T) -> Boolean = { true }, mapper: (C
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> 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
|
fun Boolean.toInt(): Int = if (this) 1 else 0
|
||||||
|
|
Ładowanie…
Reference in New Issue