Signal-Android/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt

131 wiersze
4.5 KiB
Kotlin

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()
}
}