Add support for PniSignatureMessages.

This commit is contained in:
Greyson Parrelli
2022-08-24 18:16:42 -04:00
committed by GitHub
parent 1e499fd12f
commit 61498037f3
29 changed files with 602 additions and 210 deletions

View File

@@ -0,0 +1,107 @@
package org.thoughtcrime.securesms.database
import android.content.Context
import androidx.core.content.contentValuesOf
import org.signal.core.util.delete
import org.signal.core.util.exists
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.messages.SendMessageResult
/**
* Contains records of messages that have been sent with PniSignatures on them.
* When we receive delivery receipts for these messages, we remove entries from the table and can clear
* the `needsPniSignature` flag on the recipient when all are delivered.
*/
class PendingPniSignatureMessageDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper) {
companion object {
private val TAG = Log.tag(PendingPniSignatureMessageDatabase::class.java)
const val TABLE_NAME = "pending_pni_signature_message"
private const val ID = "_id"
private const val RECIPIENT_ID = "recipient_id"
private const val SENT_TIMESTAMP = "sent_timestamp"
private const val DEVICE_ID = "device_id"
const val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientDatabase.TABLE_NAME} (${RecipientDatabase.ID}) ON DELETE CASCADE,
$SENT_TIMESTAMP INTEGER NOT NULL,
$DEVICE_ID INTEGER NOT NULL
)
"""
val CREATE_INDEXES = arrayOf(
"CREATE UNIQUE INDEX pending_pni_recipient_sent_device_index ON $TABLE_NAME ($RECIPIENT_ID, $SENT_TIMESTAMP, $DEVICE_ID)"
)
}
fun insertIfNecessary(recipientId: RecipientId, sentTimestamp: Long, result: SendMessageResult) {
if (!FeatureFlags.phoneNumberPrivacy()) return
if (!result.isSuccess) {
return
}
writableDatabase.withinTransaction { db ->
for (deviceId in result.success.devices) {
val values = contentValuesOf(
RECIPIENT_ID to recipientId.serialize(),
SENT_TIMESTAMP to sentTimestamp,
DEVICE_ID to deviceId
)
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE)
}
}
}
fun acknowledgeReceipts(recipientId: RecipientId, sentTimestamps: Collection<Long>, deviceId: Int) {
if (!FeatureFlags.phoneNumberPrivacy()) return
writableDatabase.withinTransaction { db ->
val count = db
.delete(TABLE_NAME)
.where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP IN (?) AND $DEVICE_ID = ?", recipientId, sentTimestamps.joinToString(separator = ","), deviceId)
.run()
if (count <= 0) {
return@withinTransaction
}
val stillPending: Boolean = db.exists(TABLE_NAME, "$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps)
if (!stillPending) {
Log.i(TAG, "All devices for ($recipientId, $sentTimestamps) have acked the PNI signature message. Clearing flag and removing any other pending receipts.")
SignalDatabase.recipients.clearNeedsPniSignature(recipientId)
db
.delete(TABLE_NAME)
.where("$RECIPIENT_ID = ?", recipientId)
.run()
}
}
}
/**
* Deletes all record of pending PNI verification messages. Should only be called after the user changes their number.
*/
fun deleteAll() {
if (!FeatureFlags.phoneNumberPrivacy()) return
writableDatabase.delete(TABLE_NAME).run()
}
fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
writableDatabase
.update(TABLE_NAME)
.values(RECIPIENT_ID to newId.serialize())
.where("$RECIPIENT_ID = ?", oldId)
.run()
}
}

View File

@@ -14,7 +14,7 @@ import net.zetetic.database.sqlcipher.SQLiteConstraintException
import org.signal.core.util.Bitmask
import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.exists
import org.signal.core.util.logging.Log
import org.signal.core.util.optionalBlob
import org.signal.core.util.optionalBoolean
@@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.identities
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.messageLog
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.notificationProfiles
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.pendingPniSignatureMessages
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.reactions
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.runPostSuccessfulTransaction
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.sessions
@@ -180,6 +181,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
private const val SORT_NAME = "sort_name"
private const val IDENTITY_STATUS = "identity_status"
private const val IDENTITY_KEY = "identity_key"
private const val NEEDS_PNI_SIGNATURE = "needs_pni_signature"
@JvmField
val CREATE_TABLE =
@@ -237,7 +239,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
$CUSTOM_CHAT_COLORS_ID INTEGER DEFAULT 0,
$BADGES BLOB DEFAULT NULL,
$PNI_COLUMN TEXT DEFAULT NULL,
$DISTRIBUTION_LIST_ID INTEGER DEFAULT NULL
$DISTRIBUTION_LIST_ID INTEGER DEFAULT NULL,
$NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0
)
""".trimIndent()
@@ -297,7 +300,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
CHAT_COLORS,
CUSTOM_CHAT_COLORS_ID,
BADGES,
DISTRIBUTION_LIST_ID
DISTRIBUTION_LIST_ID,
NEEDS_PNI_SIGNATURE
)
private val ID_PROJECTION = arrayOf(ID)
@@ -418,9 +422,13 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
return getByColumn(USERNAME, username)
}
fun isAssociated(serviceId: ServiceId, pni: PNI): Boolean {
return readableDatabase.exists(TABLE_NAME, "$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString())
}
@JvmOverloads
fun getAndPossiblyMerge(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId {
return if (FeatureFlags.recipientMergeV2()) {
return if (FeatureFlags.recipientMergeV2() || FeatureFlags.phoneNumberPrivacy()) {
getAndPossiblyMergePnp(serviceId, e164, changeSelf)
} else {
getAndPossiblyMergeLegacy(serviceId, e164, changeSelf)
@@ -562,7 +570,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
if (result.operations.isNotEmpty()) {
Log.i(TAG, "[getAndPossiblyMergePnp] BreadCrumbs: ${result.breadCrumbs}, Operations: ${result.operations}")
Log.i(TAG, "[getAndPossiblyMergePnp] ($serviceId, $pni, $e164) BreadCrumbs: ${result.breadCrumbs}, Operations: ${result.operations}")
}
db.setTransactionSuccessful()
@@ -2038,6 +2046,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
}
/**
* Does *not* handle clearing the recipient cache. It is assumed the caller handles this.
*/
fun updateSelfPhone(e164: String) {
val db = writableDatabase
@@ -2052,6 +2063,13 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
throw AssertionError("[updateSelfPhone] Self recipient id changed when updating phone. old: $id new: $newId")
}
db
.update(TABLE_NAME)
.values(NEEDS_PNI_SIGNATURE to 0)
.run()
SignalDatabase.pendingPniSignatureMessages.deleteAll()
db.setTransactionSuccessful()
} finally {
db.endTransaction()
@@ -2303,7 +2321,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
}
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pnpEnabled)
val finalId: RecipientId = writePnpChangeSetToDisk(changeSet, pnpEnabled, pni)
return ProcessPnpTupleResult(
finalId = finalId,
@@ -2316,7 +2334,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
@VisibleForTesting
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, pnpEnabled: Boolean): RecipientId {
fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, pnpEnabled: Boolean, inputPni: PNI?): RecipientId {
for (operation in changeSet.operations) {
@Exhaustive
when (operation) {
@@ -2378,29 +2396,13 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
val secondary = getRecord(operation.secondaryId)
if (primary.serviceId != null && !primary.sidIsPni() && secondary.e164 != null) {
merge(operation.primaryId, operation.secondaryId)
merge(operation.primaryId, operation.secondaryId, inputPni)
} else {
if (!pnpEnabled) {
throw AssertionError("This type of merge is not supported in production!")
}
Log.w(TAG, "WARNING: Performing an unfinished PNP merge! This operation currently only has a basic implementation only suitable for basic testing!")
writableDatabase
.delete(TABLE_NAME)
.where("$ID = ?", operation.secondaryId)
.run()
writableDatabase
.update(TABLE_NAME)
.values(
PHONE to (primary.e164 ?: secondary.e164),
PNI_COLUMN to (primary.pni ?: secondary.pni)?.toString(),
SERVICE_ID to (primary.serviceId ?: secondary.serviceId)?.toString(),
REGISTERED to RegisteredState.REGISTERED.id
)
.where("$ID = ?", operation.primaryId)
.run()
merge(operation.primaryId, operation.secondaryId, inputPni)
}
}
is PnpOperation.SessionSwitchoverInsert -> {
@@ -2435,7 +2437,6 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
@VisibleForTesting
fun processPnpTupleToChangeSet(e164: String?, pni: PNI?, aci: ACI?, pniVerified: Boolean, changeSelf: Boolean = false): PnpChangeSet {
check(e164 != null || pni != null || aci != null) { "Must provide at least one field!" }
check(pni == null || e164 != null) { "If a PNI is provided, you must also provide an E164!" }
val breadCrumbs: MutableList<String> = mutableListOf()
@@ -3238,6 +3239,25 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
}
}
/**
* Indicates that the recipient knows our PNI, and therefore needs to be sent PNI signature messages until we know that they have our PNI-ACI association.
*/
fun markNeedsPniSignature(recipientId: RecipientId) {
if (update(recipientId, contentValuesOf(NEEDS_PNI_SIGNATURE to 1))) {
Log.i(TAG, "Marked $recipientId as needing a PNI signature message.")
Recipient.live(recipientId).refresh()
}
}
/**
* Indicates that we successfully told all of this recipient's devices our PNI-ACI association, and therefore no longer needs us to send it to them.
*/
fun clearNeedsPniSignature(recipientId: RecipientId) {
if (update(recipientId, contentValuesOf(NEEDS_PNI_SIGNATURE to 0))) {
Recipient.live(recipientId).refresh()
}
}
fun setHasGroupsInCommon(recipientIds: List<RecipientId?>) {
if (recipientIds.isEmpty()) {
return
@@ -3401,26 +3421,28 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
* Merges one ACI recipient with an E164 recipient. It is assumed that the E164 recipient does
* *not* have an ACI.
*/
private fun merge(byAci: RecipientId, byE164: RecipientId): RecipientId {
private fun merge(primaryId: RecipientId, secondaryId: RecipientId, newPni: PNI? = null): RecipientId {
ensureInTransaction()
val db = writableDatabase
val aciRecord = getRecord(byAci)
val e164Record = getRecord(byE164)
val primaryRecord = getRecord(primaryId)
val secondaryRecord = getRecord(secondaryId)
// Identities
ApplicationDependencies.getProtocolStore().aci().identities().delete(e164Record.e164!!)
// Clean up any E164-based identities (legacy stuff)
if (secondaryRecord.e164 != null) {
ApplicationDependencies.getProtocolStore().aci().identities().delete(secondaryRecord.e164)
}
// Group Receipts
val groupReceiptValues = ContentValues()
groupReceiptValues.put(GroupReceiptDatabase.RECIPIENT_ID, byAci.serialize())
db.update(GroupReceiptDatabase.TABLE_NAME, groupReceiptValues, GroupReceiptDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(byE164))
groupReceiptValues.put(GroupReceiptDatabase.RECIPIENT_ID, primaryId.serialize())
db.update(GroupReceiptDatabase.TABLE_NAME, groupReceiptValues, GroupReceiptDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
// Groups
val groupDatabase = groups
for (group in groupDatabase.getGroupsContainingMember(byE164, false, true)) {
for (group in groupDatabase.getGroupsContainingMember(secondaryId, false, true)) {
val newMembers = LinkedHashSet(group.members).apply {
remove(byE164)
add(byAci)
remove(secondaryId)
add(primaryId)
}
val groupValues = ContentValues().apply {
@@ -3429,18 +3451,18 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
db.update(GroupDatabase.TABLE_NAME, groupValues, GroupDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(group.recipientId))
if (group.isV2Group) {
groupDatabase.removeUnmigratedV1Members(group.id.requireV2(), listOf(byE164))
groupDatabase.removeUnmigratedV1Members(group.id.requireV2(), listOf(secondaryId))
}
}
// Threads
val threadMerge = threads.merge(byAci, byE164)
val threadMerge = threads.merge(primaryId, secondaryId)
// SMS Messages
val smsValues = ContentValues().apply {
put(SmsDatabase.RECIPIENT_ID, byAci.serialize())
put(SmsDatabase.RECIPIENT_ID, primaryId.serialize())
}
db.update(SmsDatabase.TABLE_NAME, smsValues, SmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(byE164))
db.update(SmsDatabase.TABLE_NAME, smsValues, SmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
if (threadMerge.neededMerge) {
val values = ContentValues().apply {
@@ -3451,9 +3473,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
// MMS Messages
val mmsValues = ContentValues().apply {
put(MmsDatabase.RECIPIENT_ID, byAci.serialize())
put(MmsDatabase.RECIPIENT_ID, primaryId.serialize())
}
db.update(MmsDatabase.TABLE_NAME, mmsValues, MmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(byE164))
db.update(MmsDatabase.TABLE_NAME, mmsValues, MmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
if (threadMerge.neededMerge) {
val values = ContentValues()
@@ -3461,35 +3483,14 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
db.update(MmsDatabase.TABLE_NAME, values, MmsDatabase.THREAD_ID + " = ?", SqlUtil.buildArgs(threadMerge.previousThreadId))
}
// Sessions
val localAci: ACI = SignalStore.account().requireAci()
val sessionDatabase = sessions
val hasE164Session = sessionDatabase.getAllFor(localAci, e164Record.e164).isNotEmpty()
val hasAciSession = sessionDatabase.getAllFor(localAci, aciRecord.serviceId.toString()).isNotEmpty()
if (hasE164Session && hasAciSession) {
Log.w(TAG, "Had a session for both users. Deleting the E164.", true)
sessionDatabase.deleteAllFor(localAci, e164Record.e164)
} else if (hasE164Session && !hasAciSession) {
Log.w(TAG, "Had a session for E164, but not ACI. Re-assigning to the ACI.", true)
val values = ContentValues().apply {
put(SessionDatabase.ADDRESS, aciRecord.serviceId.toString())
}
db.update(SessionDatabase.TABLE_NAME, values, "${SessionDatabase.ACCOUNT_ID} = ? AND ${SessionDatabase.ADDRESS} = ?", SqlUtil.buildArgs(localAci, e164Record.e164))
} else if (!hasE164Session && hasAciSession) {
Log.w(TAG, "Had a session for ACI, but not E164. No action necessary.", true)
} else {
Log.w(TAG, "Had no sessions. No action necessary.", true)
}
// MSL
messageLog.remapRecipient(byE164, byAci)
messageLog.remapRecipient(secondaryId, primaryId)
// Mentions
val mentionRecipientValues = ContentValues().apply {
put(MentionDatabase.RECIPIENT_ID, byAci.serialize())
put(MentionDatabase.RECIPIENT_ID, primaryId.serialize())
}
db.update(MentionDatabase.TABLE_NAME, mentionRecipientValues, MentionDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(byE164))
db.update(MentionDatabase.TABLE_NAME, mentionRecipientValues, MentionDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
if (threadMerge.neededMerge) {
val mentionThreadValues = ContentValues().apply {
@@ -3501,59 +3502,62 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
threads.update(threadMerge.threadId, false, false)
// Reactions
reactions.remapRecipient(byE164, byAci)
reactions.remapRecipient(secondaryId, primaryId)
// Notification Profiles
notificationProfiles.remapRecipient(byE164, byAci)
notificationProfiles.remapRecipient(secondaryId, primaryId)
// DistributionLists
distributionLists.remapRecipient(byE164, byAci)
distributionLists.remapRecipient(secondaryId, primaryId)
// Story Sends
storySends.remapRecipient(byE164, byAci)
storySends.remapRecipient(secondaryId, primaryId)
// PendingPniSignatureMessage
pendingPniSignatureMessages.remapRecipient(secondaryId, primaryId)
// Recipient
Log.w(TAG, "Deleting recipient $byE164", true)
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(byE164))
RemappedRecords.getInstance().addRecipient(byE164, byAci)
Log.w(TAG, "Deleting recipient $secondaryId", true)
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(secondaryId))
RemappedRecords.getInstance().addRecipient(secondaryId, primaryId)
// TODO [pnp] We should pass in the PNI involved in the merge and prefer that over either of the ones in the records
val uuidValues = contentValuesOf(
PHONE to e164Record.e164,
PNI_COLUMN to (e164Record.pni ?: aciRecord.pni)?.toString(),
BLOCKED to (e164Record.isBlocked || aciRecord.isBlocked),
MESSAGE_RINGTONE to Optional.ofNullable(aciRecord.messageRingtone).or(Optional.ofNullable(e164Record.messageRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
MESSAGE_VIBRATE to if (aciRecord.messageVibrateState != VibrateState.DEFAULT) aciRecord.messageVibrateState.id else e164Record.messageVibrateState.id,
CALL_RINGTONE to Optional.ofNullable(aciRecord.callRingtone).or(Optional.ofNullable(e164Record.callRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
CALL_VIBRATE to if (aciRecord.callVibrateState != VibrateState.DEFAULT) aciRecord.callVibrateState.id else e164Record.callVibrateState.id,
NOTIFICATION_CHANNEL to (aciRecord.notificationChannel ?: e164Record.notificationChannel),
MUTE_UNTIL to if (aciRecord.muteUntil > 0) aciRecord.muteUntil else e164Record.muteUntil,
CHAT_COLORS to Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null),
AVATAR_COLOR to aciRecord.avatarColor.serialize(),
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(aciRecord.chatColors).or(Optional.ofNullable(e164Record.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
SEEN_INVITE_REMINDER to e164Record.insightsBannerTier.id,
DEFAULT_SUBSCRIPTION_ID to e164Record.getDefaultSubscriptionId().orElse(-1),
MESSAGE_EXPIRATION_TIME to if (aciRecord.expireMessages > 0) aciRecord.expireMessages else e164Record.expireMessages,
PHONE to (secondaryRecord.e164 ?: primaryRecord.e164),
SERVICE_ID to (primaryRecord.serviceId ?: secondaryRecord.serviceId)?.toString(),
PNI_COLUMN to (newPni ?: secondaryRecord.pni ?: primaryRecord.pni)?.toString(),
BLOCKED to (secondaryRecord.isBlocked || primaryRecord.isBlocked),
MESSAGE_RINGTONE to Optional.ofNullable(primaryRecord.messageRingtone).or(Optional.ofNullable(secondaryRecord.messageRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
MESSAGE_VIBRATE to if (primaryRecord.messageVibrateState != VibrateState.DEFAULT) primaryRecord.messageVibrateState.id else secondaryRecord.messageVibrateState.id,
CALL_RINGTONE to Optional.ofNullable(primaryRecord.callRingtone).or(Optional.ofNullable(secondaryRecord.callRingtone)).map { obj: Uri? -> obj.toString() }.orElse(null),
CALL_VIBRATE to if (primaryRecord.callVibrateState != VibrateState.DEFAULT) primaryRecord.callVibrateState.id else secondaryRecord.callVibrateState.id,
NOTIFICATION_CHANNEL to (primaryRecord.notificationChannel ?: secondaryRecord.notificationChannel),
MUTE_UNTIL to if (primaryRecord.muteUntil > 0) primaryRecord.muteUntil else secondaryRecord.muteUntil,
CHAT_COLORS to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null),
AVATAR_COLOR to primaryRecord.avatarColor.serialize(),
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
SEEN_INVITE_REMINDER to secondaryRecord.insightsBannerTier.id,
DEFAULT_SUBSCRIPTION_ID to secondaryRecord.getDefaultSubscriptionId().orElse(-1),
MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages,
REGISTERED to RegisteredState.REGISTERED.id,
SYSTEM_GIVEN_NAME to e164Record.systemProfileName.givenName,
SYSTEM_FAMILY_NAME to e164Record.systemProfileName.familyName,
SYSTEM_JOINED_NAME to e164Record.systemProfileName.toString(),
SYSTEM_PHOTO_URI to e164Record.systemContactPhotoUri,
SYSTEM_PHONE_LABEL to e164Record.systemPhoneLabel,
SYSTEM_CONTACT_URI to e164Record.systemContactUri,
PROFILE_SHARING to (aciRecord.profileSharing || e164Record.profileSharing),
CAPABILITIES to max(aciRecord.rawCapabilities, e164Record.rawCapabilities),
MENTION_SETTING to if (aciRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) aciRecord.mentionSetting.id else e164Record.mentionSetting.id
SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName,
SYSTEM_FAMILY_NAME to secondaryRecord.systemProfileName.familyName,
SYSTEM_JOINED_NAME to secondaryRecord.systemProfileName.toString(),
SYSTEM_PHOTO_URI to secondaryRecord.systemContactPhotoUri,
SYSTEM_PHONE_LABEL to secondaryRecord.systemPhoneLabel,
SYSTEM_CONTACT_URI to secondaryRecord.systemContactUri,
PROFILE_SHARING to (primaryRecord.profileSharing || secondaryRecord.profileSharing),
CAPABILITIES to max(primaryRecord.rawCapabilities, secondaryRecord.rawCapabilities),
MENTION_SETTING to if (primaryRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id
)
if (aciRecord.profileKey != null) {
updateProfileValuesForMerge(uuidValues, aciRecord)
} else if (e164Record.profileKey != null) {
updateProfileValuesForMerge(uuidValues, e164Record)
if (primaryRecord.profileKey != null) {
updateProfileValuesForMerge(uuidValues, primaryRecord)
} else if (secondaryRecord.profileKey != null) {
updateProfileValuesForMerge(uuidValues, secondaryRecord)
}
db.update(TABLE_NAME, uuidValues, ID_WHERE, SqlUtil.buildArgs(byAci))
return byAci
db.update(TABLE_NAME, uuidValues, ID_WHERE, SqlUtil.buildArgs(primaryId))
return primaryId
}
private fun ensureInTransaction() {
@@ -3834,7 +3838,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
syncExtras = getSyncExtras(cursor),
extras = getExtras(cursor),
hasGroupsInCommon = cursor.requireBoolean(GROUPS_IN_COMMON),
badges = parseBadgeList(cursor.requireBlob(BADGES))
badges = parseBadgeList(cursor.requireBlob(BADGES)),
needsPniSignature = cursor.requireBoolean(NEEDS_PNI_SIGNATURE)
)
}

View File

@@ -72,6 +72,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
val storySendsDatabase: StorySendsDatabase = StorySendsDatabase(context, this)
val cdsDatabase: CdsDatabase = CdsDatabase(context, this)
val remoteMegaphoneDatabase: RemoteMegaphoneDatabase = RemoteMegaphoneDatabase(context, this)
val pendingPniSignatureMessageDatabase: PendingPniSignatureMessageDatabase = PendingPniSignatureMessageDatabase(context, this)
override fun onOpen(db: net.zetetic.database.sqlcipher.SQLiteDatabase) {
db.setForeignKeyConstraintsEnabled(true)
@@ -107,6 +108,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
db.execSQL(StorySendsDatabase.CREATE_TABLE)
db.execSQL(CdsDatabase.CREATE_TABLE)
db.execSQL(RemoteMegaphoneDatabase.CREATE_TABLE)
db.execSQL(PendingPniSignatureMessageDatabase.CREATE_TABLE)
executeStatements(db, SearchDatabase.CREATE_TABLE)
executeStatements(db, RemappedRecordsDatabase.CREATE_TABLE)
executeStatements(db, MessageSendLogDatabase.CREATE_TABLE)
@@ -131,6 +133,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
executeStatements(db, DonationReceiptDatabase.CREATE_INDEXS)
db.execSQL(StorySendsDatabase.CREATE_INDEX)
executeStatements(db, DistributionListDatabase.CREATE_INDEXES)
executeStatements(db, PendingPniSignatureMessageDatabase.CREATE_INDEXES)
executeStatements(db, MessageSendLogDatabase.CREATE_TRIGGERS)
executeStatements(db, ReactionDatabase.CREATE_TRIGGERS)
@@ -502,5 +505,10 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
@get:JvmName("remoteMegaphones")
val remoteMegaphones: RemoteMegaphoneDatabase
get() = instance!!.remoteMegaphoneDatabase
@get:JvmStatic
@get:JvmName("pendingPniSignatureMessages")
val pendingPniSignatureMessages: PendingPniSignatureMessageDatabase
get() = instance!!.pendingPniSignatureMessageDatabase
}
}

View File

@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper.entrySet
import org.thoughtcrime.securesms.database.KeyValueDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.helpers.migration.MyStoryMigration
import org.thoughtcrime.securesms.database.helpers.migration.PniSignaturesMigration
import org.thoughtcrime.securesms.database.helpers.migration.UrgentMslFlagMigration
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList
import org.thoughtcrime.securesms.groups.GroupId
@@ -208,8 +209,9 @@ object SignalDatabaseMigrations {
private const val MY_STORY_MIGRATION = 151
private const val STORY_GROUP_TYPES = 152
private const val MY_STORY_MIGRATION_2 = 153
private const val PNI_SIGNATURES = 154
const val DATABASE_VERSION = 153
const val DATABASE_VERSION = 154
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -2690,6 +2692,10 @@ object SignalDatabaseMigrations {
if (oldVersion < MY_STORY_MIGRATION_2) {
MyStoryMigration.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < PNI_SIGNATURES) {
PniSignaturesMigration.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Introduces the tables and fields required to keep track of whether we need to send a PNI signature message and if the ones we've sent out have been received.
*/
object PniSignaturesMigration : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE recipient ADD COLUMN needs_pni_signature")
db.execSQL(
"""
CREATE TABLE pending_pni_signature_message (
_id INTEGER PRIMARY KEY,
recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
sent_timestamp INTEGER NOT NULL,
device_id INTEGER NOT NULL
)
""".trimIndent()
)
db.execSQL("CREATE UNIQUE INDEX pending_pni_recipient_sent_device_index ON pending_pni_signature_message (recipient_id, sent_timestamp, device_id)")
}
}

View File

@@ -82,7 +82,9 @@ data class RecipientRecord(
val extras: Recipient.Extras?,
@get:JvmName("hasGroupsInCommon")
val hasGroupsInCommon: Boolean,
val badges: List<Badge>
val badges: List<Badge>,
@get:JvmName("needsPniSignature")
val needsPniSignature: Boolean
) {
fun getDefaultSubscriptionId(): Optional<Int> {