mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 18:30:20 +01:00
Add call link support to storage service.
This commit is contained in:
committed by
Cody Henthorne
parent
1f2b5e90a3
commit
e247d311d8
@@ -10,7 +10,6 @@ import org.signal.core.util.delete
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.readToSingleInt
|
||||
import org.signal.core.util.readToSingleLong
|
||||
import org.signal.core.util.readToSingleObject
|
||||
@@ -18,7 +17,6 @@ import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullBlob
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
@@ -34,6 +32,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@@ -55,6 +54,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
const val REVOKED = "revoked"
|
||||
const val EXPIRATION = "expiration"
|
||||
const val RECIPIENT_ID = "recipient_id"
|
||||
const val DELETION_TIMESTAMP = "deletion_timestamp"
|
||||
|
||||
//language=sql
|
||||
const val CREATE_TABLE = """
|
||||
@@ -67,7 +67,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
$RESTRICTIONS INTEGER NOT NULL,
|
||||
$REVOKED INTEGER NOT NULL,
|
||||
$EXPIRATION INTEGER NOT NULL,
|
||||
$RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE
|
||||
$RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$DELETION_TIMESTAMP INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -90,14 +91,23 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
}
|
||||
|
||||
fun insertCallLink(
|
||||
callLink: CallLink
|
||||
callLink: CallLink,
|
||||
deletionTimestamp: Long = 0L,
|
||||
storageId: StorageId? = null
|
||||
): RecipientId {
|
||||
val recipientId: RecipientId = writableDatabase.withinTransaction { db ->
|
||||
val recipientId = SignalDatabase.recipients.getOrInsertFromCallLinkRoomId(callLink.roomId)
|
||||
val recipientId = SignalDatabase.recipients.getOrInsertFromCallLinkRoomId(callLink.roomId, storageId = storageId?.raw)
|
||||
|
||||
val contentValues = CallLinkSerializer.serialize(callLink.copy(recipientId = recipientId)).apply {
|
||||
put(DELETION_TIMESTAMP, deletionTimestamp)
|
||||
if (deletionTimestamp > 0) {
|
||||
put(REVOKED, true)
|
||||
}
|
||||
}
|
||||
|
||||
db
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(CallLinkSerializer.serialize(callLink.copy(recipientId = recipientId)))
|
||||
.values(contentValues)
|
||||
.run()
|
||||
|
||||
recipientId
|
||||
@@ -190,7 +200,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
linkKeyBytes = callLinkRootKey.keyBytes,
|
||||
adminPassBytes = null
|
||||
),
|
||||
state = SignalCallLinkState()
|
||||
state = SignalCallLinkState(),
|
||||
deletionTimestamp = 0L
|
||||
)
|
||||
|
||||
insertCallLink(link)
|
||||
@@ -200,6 +211,62 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
}
|
||||
}
|
||||
|
||||
fun insertOrUpdateCallLinkByRootKey(
|
||||
callLinkRootKey: CallLinkRootKey,
|
||||
adminPassKey: ByteArray?,
|
||||
deletionTimestamp: Long = 0L,
|
||||
storageId: StorageId? = null
|
||||
) {
|
||||
val roomId = CallLinkRoomId.fromBytes(callLinkRootKey.deriveRoomId())
|
||||
|
||||
writableDatabase.withinTransaction {
|
||||
val callLink = getCallLinkByRoomId(roomId)
|
||||
if (callLink == null) {
|
||||
val link = CallLink(
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = roomId,
|
||||
credentials = CallLinkCredentials(
|
||||
linkKeyBytes = callLinkRootKey.keyBytes,
|
||||
adminPassBytes = adminPassKey
|
||||
),
|
||||
state = SignalCallLinkState(),
|
||||
deletionTimestamp = 0L
|
||||
)
|
||||
|
||||
insertCallLink(link, deletionTimestamp, storageId)
|
||||
} else {
|
||||
if (storageId != null) {
|
||||
SignalDatabase.recipients.updateStorageId(callLink.recipientId, storageId.raw)
|
||||
}
|
||||
|
||||
if (deletionTimestamp != 0L) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(
|
||||
DELETION_TIMESTAMP to deletionTimestamp,
|
||||
ADMIN_KEY to null,
|
||||
REVOKED to true
|
||||
)
|
||||
.where("$ROOM_ID = ?", callLink.roomId.serialize())
|
||||
.run()
|
||||
} else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unix timestamp, or 0
|
||||
*/
|
||||
fun getDeletedTimestampByRoomId(
|
||||
roomId: CallLinkRoomId
|
||||
): Long {
|
||||
return readableDatabase
|
||||
.select(DELETION_TIMESTAMP)
|
||||
.from(TABLE_NAME)
|
||||
.where("$ROOM_ID = ?", roomId.serialize())
|
||||
.run()
|
||||
.readToSingleLong(defaultValue = 0)
|
||||
}
|
||||
|
||||
fun getOrCreateCallLinkByRoomId(
|
||||
callLinkRoomId: CallLinkRoomId
|
||||
): CallLink {
|
||||
@@ -209,7 +276,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
recipientId = RecipientId.UNKNOWN,
|
||||
roomId = callLinkRoomId,
|
||||
credentials = null,
|
||||
state = SignalCallLinkState()
|
||||
state = SignalCallLinkState(),
|
||||
deletionTimestamp = 0L
|
||||
)
|
||||
insertCallLink(link)
|
||||
return getCallLinkByRoomId(callLinkRoomId)!!
|
||||
@@ -235,6 +303,15 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll(): List<CallLink> {
|
||||
return readableDatabase.select()
|
||||
.from(TABLE_NAME)
|
||||
.run()
|
||||
.readToList {
|
||||
CallLinkDeserializer.deserialize(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the call link into the "revoked" state which will hide it from the UI and
|
||||
* delete it after a few days.
|
||||
@@ -244,25 +321,20 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(TABLE_NAME)
|
||||
.values(REVOKED to true)
|
||||
.values(
|
||||
REVOKED to true,
|
||||
DELETION_TIMESTAMP to System.currentTimeMillis(),
|
||||
ADMIN_KEY to null
|
||||
)
|
||||
.where("$ROOM_ID = ?", roomId.serialize())
|
||||
.run()
|
||||
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps()
|
||||
}
|
||||
}
|
||||
val recipient = SignalDatabase.recipients.getByCallLinkRoomId(roomId)
|
||||
|
||||
/**
|
||||
* Deletes the call link. This should only happen *after* we send out a sync message
|
||||
* or receive a sync message which deletes the corresponding link.
|
||||
*/
|
||||
fun deleteCallLink(
|
||||
roomId: CallLinkRoomId
|
||||
) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.delete(TABLE_NAME)
|
||||
.where("$ROOM_ID = ?", roomId.serialize())
|
||||
.run()
|
||||
if (recipient.isPresent) {
|
||||
SignalDatabase.recipients.markNeedsSync(recipient.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +351,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
fun deleteNonAdminCallLinksOnOrBefore(timestamp: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.delete(TABLE_NAME)
|
||||
.where("EXISTS (SELECT 1 FROM ${CallTable.TABLE_NAME} WHERE ${CallTable.TIMESTAMP} <= ? AND ${CallTable.PEER} = $RECIPIENT_ID)", timestamp)
|
||||
.where("$ADMIN_KEY IS NULL AND EXISTS (SELECT 1 FROM ${CallTable.TABLE_NAME} WHERE ${CallTable.TIMESTAMP} <= ? AND ${CallTable.PEER} = $RECIPIENT_ID)", timestamp)
|
||||
.run()
|
||||
|
||||
SignalDatabase.calls.updateAdHocCallEventDeletionTimestamps(skipSync = true)
|
||||
@@ -334,18 +406,6 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
}
|
||||
}
|
||||
|
||||
fun getAdminCallLinkCredentialsOnOrBefore(timestamp: Long): Set<CallLinkCredentials> {
|
||||
val query = """
|
||||
SELECT $ROOT_KEY, $ADMIN_KEY FROM $TABLE_NAME
|
||||
INNER JOIN ${CallTable.TABLE_NAME} ON ${CallTable.TABLE_NAME}.${CallTable.PEER} = $TABLE_NAME.$RECIPIENT_ID
|
||||
WHERE ${CallTable.TIMESTAMP} <= $timestamp AND $ADMIN_KEY IS NOT NULL AND $REVOKED = 0
|
||||
""".trimIndent()
|
||||
|
||||
return readableDatabase.query(query).readToSet {
|
||||
CallLinkCredentials(it.requireNonNullBlob(ROOT_KEY), it.requireNonNullBlob(ADMIN_KEY))
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryCallLinks(query: String?, offset: Int, limit: Int, asCount: Boolean): Cursor {
|
||||
//language=sql
|
||||
val noCallEvent = """
|
||||
@@ -381,7 +441,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
val statement = """
|
||||
SELECT $projection
|
||||
FROM $TABLE_NAME
|
||||
WHERE $noCallEvent AND NOT $REVOKED ${searchFilter?.where ?: ""} AND $ROOT_KEY IS NOT NULL
|
||||
WHERE $noCallEvent AND NOT $REVOKED ${searchFilter?.where ?: ""} AND $ROOT_KEY IS NOT NULL AND $DELETION_TIMESTAMP = 0
|
||||
ORDER BY $ID DESC
|
||||
$limitOffset
|
||||
""".trimIndent()
|
||||
@@ -432,7 +492,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
Instant.ofEpochMilli(it).truncatedTo(ChronoUnit.DAYS)
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
deletionTimestamp = data.requireLong(DELETION_TIMESTAMP)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -449,7 +510,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
val recipientId: RecipientId,
|
||||
val roomId: CallLinkRoomId,
|
||||
val credentials: CallLinkCredentials?,
|
||||
val state: SignalCallLinkState
|
||||
val state: SignalCallLinkState,
|
||||
val deletionTimestamp: Long
|
||||
) {
|
||||
val avatarColor: AvatarColor = credentials?.let { AvatarColorHash.forCallLink(it.linkKeyBytes) } ?: AvatarColor.UNKNOWN
|
||||
}
|
||||
|
||||
@@ -574,13 +574,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
).recipientId
|
||||
}
|
||||
|
||||
fun getOrInsertFromCallLinkRoomId(callLinkRoomId: CallLinkRoomId): RecipientId {
|
||||
fun getOrInsertFromCallLinkRoomId(callLinkRoomId: CallLinkRoomId, storageId: ByteArray? = null): RecipientId {
|
||||
return getOrInsertByColumn(
|
||||
CALL_LINK_ROOM_ID,
|
||||
callLinkRoomId.serialize(),
|
||||
contentValuesOf(
|
||||
TYPE to RecipientType.CALL_LINK.id,
|
||||
CALL_LINK_ROOM_ID to callLinkRoomId.serialize(),
|
||||
STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId ?: StorageSyncHelper.generateKey()),
|
||||
PROFILE_SHARING to 1
|
||||
)
|
||||
).recipientId
|
||||
@@ -1199,6 +1200,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
SELECT ${DistributionListTables.ListTable.ID}
|
||||
FROM ${DistributionListTables.ListTable.TABLE_NAME}
|
||||
)
|
||||
OR
|
||||
$CALL_LINK_ROOM_ID NOT NULL AND $CALL_LINK_ROOM_ID IN (
|
||||
SELECT ${CallLinkTable.ROOM_ID}
|
||||
FROM ${CallLinkTable.TABLE_NAME}
|
||||
WHERE (${CallLinkTable.ADMIN_KEY} NOT NULL OR ${CallLinkTable.DELETION_TIMESTAMP} > 0) AND ${CallLinkTable.ROOT_KEY} NOT NULL
|
||||
)
|
||||
)
|
||||
""",
|
||||
RecipientType.INDIVIDUAL.id,
|
||||
@@ -1217,6 +1224,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
RecipientType.INDIVIDUAL -> out[id] = StorageId.forContact(key)
|
||||
RecipientType.GV1 -> out[id] = StorageId.forGroupV1(key)
|
||||
RecipientType.DISTRIBUTION_LIST -> out[id] = StorageId.forStoryDistributionList(key)
|
||||
RecipientType.CALL_LINK -> out[id] = StorageId.forCallLink(key)
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
@@ -3870,8 +3878,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(StorageSyncHelper.generateKey()))
|
||||
}
|
||||
|
||||
val query = "$ID = ? AND ($TYPE IN (?, ?, ?) OR $REGISTERED = ? OR $ID = ?)"
|
||||
val args = SqlUtil.buildArgs(recipientId, RecipientType.GV1.id, RecipientType.GV2.id, RecipientType.DISTRIBUTION_LIST.id, RegisteredState.REGISTERED.id, selfId.toLong())
|
||||
val query = "$ID = ? AND ($TYPE IN (?, ?, ?, ?) OR $REGISTERED = ? OR $ID = ?)"
|
||||
val args = SqlUtil.buildArgs(recipientId, RecipientType.GV1.id, RecipientType.GV2.id, RecipientType.DISTRIBUTION_LIST.id, RecipientType.CALL_LINK.id, RegisteredState.REGISTERED.id, selfId.toLong())
|
||||
|
||||
writableDatabase.update(TABLE_NAME, values, query, args).also { updateCount ->
|
||||
Log.d(TAG, "[rotateStorageId] updateCount: $updateCount")
|
||||
|
||||
@@ -102,6 +102,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V241_ExpireTimerVer
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V242_MessageFullTextSearchEmojiSupportV2
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V243_MessageFullTextSearchDisableSecureDelete
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V244_AttachmentRemoteIv
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V245_DeletionTimestampOnCallLinks
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -206,10 +207,11 @@ object SignalDatabaseMigrations {
|
||||
241 to V241_ExpireTimerVersion,
|
||||
242 to V242_MessageFullTextSearchEmojiSupportV2,
|
||||
243 to V243_MessageFullTextSearchDisableSecureDelete,
|
||||
244 to V244_AttachmentRemoteIv
|
||||
244 to V244_AttachmentRemoteIv,
|
||||
245 to V245_DeletionTimestampOnCallLinks
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 244
|
||||
const val DATABASE_VERSION = 245
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds a deletion timestamp to the call links table, which is required for storage service syncing.
|
||||
*/
|
||||
object V245_DeletionTimestampOnCallLinks : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE call_link ADD COLUMN deletion_timestamp INTEGER DEFAULT 0 NOT NULL;")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user