mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add support for PniSignatureMessages.
This commit is contained in:
@@ -110,7 +110,6 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||
if (MessageDigest.isEqual(oldStorageId, newStorageId)) {
|
||||
Log.w(TAG, "Self storage id was not rotated, attempting to rotate again")
|
||||
SignalDatabase.recipients.rotateStorageId(Recipient.self().id)
|
||||
Recipient.self().live().refresh()
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
val secondAttemptStorageId: ByteArray? = Recipient.self().storageServiceId
|
||||
if (MessageDigest.isEqual(oldStorageId, secondAttemptStorageId)) {
|
||||
@@ -119,6 +118,7 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.setPni(Recipient.self().id, pni)
|
||||
ApplicationDependencies.getRecipientCache().clear()
|
||||
|
||||
SignalStore.account().setE164(e164)
|
||||
SignalStore.account().setPni(pni)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -110,7 +110,11 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
||||
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote())))
|
||||
.build();
|
||||
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false);
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false, recipient.needsPniSignature());
|
||||
|
||||
if (recipient.needsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(recipientId, dataMessage.getTimestamp(), sendMessageResult);
|
||||
}
|
||||
|
||||
if (sendMessageResult.getIdentityFailure() != null) {
|
||||
Log.w(TAG, "Identity failure for " + recipient.getId());
|
||||
|
||||
@@ -8,10 +8,13 @@ import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
@@ -24,6 +27,8 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServicePniSignatureMessage;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@@ -100,6 +105,10 @@ public final class PushDecryptMessageJob extends BaseJob {
|
||||
handleSenderKeyDistributionMessage(result.getContent().getSender(), result.getContent().getSenderDevice(), result.getContent().getSenderKeyDistributionMessage().get());
|
||||
}
|
||||
|
||||
if (result.getContent().getPniSignatureMessage().isPresent()) {
|
||||
handlePniSignatureMessage(result.getContent().getSender(), result.getContent().getSenderDevice(), result.getContent().getPniSignatureMessage().get());
|
||||
}
|
||||
|
||||
jobs.add(new PushProcessMessageJob(result.getContent(), smsMessageId, envelope.getTimestamp()));
|
||||
} else if (result.getException() != null && result.getState() != MessageState.NOOP) {
|
||||
jobs.add(new PushProcessMessageJob(result.getState(), result.getException(), smsMessageId, envelope.getTimestamp()));
|
||||
@@ -122,11 +131,45 @@ public final class PushDecryptMessageJob extends BaseJob {
|
||||
}
|
||||
|
||||
private void handleSenderKeyDistributionMessage(@NonNull SignalServiceAddress address, int deviceId, @NonNull SenderKeyDistributionMessage message) {
|
||||
Log.i(TAG, "Processing SenderKeyDistributionMessage.");
|
||||
Log.i(TAG, "Processing SenderKeyDistributionMessage from " + address.getServiceId() + "." + deviceId);
|
||||
SignalServiceMessageSender sender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
sender.processSenderKeyDistributionMessage(new SignalProtocolAddress(address.getIdentifier(), deviceId), message);
|
||||
}
|
||||
|
||||
private void handlePniSignatureMessage(@NonNull SignalServiceAddress address, int deviceId, @NonNull SignalServicePniSignatureMessage pniSignatureMessage) {
|
||||
Log.i(TAG, "Processing PniSignatureMessage from " + address.getServiceId() + "." + deviceId);
|
||||
|
||||
PNI pni = pniSignatureMessage.getPni();
|
||||
|
||||
if (SignalDatabase.recipients().isAssociated(address.getServiceId(), pni)) {
|
||||
Log.i(TAG, "[handlePniSignatureMessage] ACI (" + address.getServiceId() + ") and PNI (" + pni + ") are already associated.");
|
||||
return;
|
||||
}
|
||||
|
||||
SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
SignalProtocolAddress aciAddress = new SignalProtocolAddress(address.getIdentifier(), deviceId);
|
||||
SignalProtocolAddress pniAddress = new SignalProtocolAddress(pni.toString(), deviceId);
|
||||
IdentityKey aciIdentity = identityStore.getIdentity(aciAddress);
|
||||
IdentityKey pniIdentity = identityStore.getIdentity(pniAddress);
|
||||
|
||||
if (aciIdentity == null) {
|
||||
Log.w(TAG, "[validatePniSignature] No identity found for ACI address " + aciAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pniIdentity == null) {
|
||||
Log.w(TAG, "[validatePniSignature] No identity found for PNI address " + pniAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.getSignature())) {
|
||||
Log.i(TAG, "[validatePniSignature] PNI signature is valid. Associating ACI (" + address.getServiceId() + ") with PNI (" + pni + ")");
|
||||
SignalDatabase.recipients().getAndPossiblyMergePnpVerified(address.getServiceId(), pni, address.getNumber().orElse(null));
|
||||
} else {
|
||||
Log.w(TAG, "[validatePniSignature] Invalid PNI signature! Cannot associate ACI (" + address.getServiceId() + ") with PNI (" + pni + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsMigration() {
|
||||
return TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
}
|
||||
|
||||
@@ -257,8 +257,14 @@ public class PushMediaSendJob extends PushSendJob {
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId, true), false);
|
||||
return syncAccess.isPresent();
|
||||
} else {
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY, message.isUrgent());
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY, message.isUrgent(), messageRecipient.needsPniSignature());
|
||||
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId, true), message.isUrgent());
|
||||
|
||||
if (messageRecipient.needsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(messageRecipient.getId(), message.getSentTimeMillis(), result);
|
||||
}
|
||||
|
||||
return result.getSuccess().isUnidentified();
|
||||
}
|
||||
} catch (UnregisteredUserException e) {
|
||||
|
||||
@@ -198,9 +198,14 @@ public class PushTextSendJob extends PushSendJob {
|
||||
return syncAccess.isPresent();
|
||||
} else {
|
||||
SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId);
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage, new MetricEventListener(messageId), true);
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage, new MetricEventListener(messageId), true, messageRecipient.needsPniSignature());
|
||||
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false), true);
|
||||
|
||||
if (messageRecipient.needsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(messageRecipient.getId(), message.getDateSent(), result);
|
||||
}
|
||||
|
||||
return result.getSuccess().isUnidentified();
|
||||
}
|
||||
} catch (UnregisteredUserException e) {
|
||||
|
||||
@@ -119,7 +119,8 @@ public class SendDeliveryReceiptJob extends BaseJob {
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
receiptMessage);
|
||||
receiptMessage,
|
||||
recipient.needsPniSignature());
|
||||
|
||||
if (messageId != null) {
|
||||
SignalDatabase.messageLog().insertIfPossible(recipientId, timestamp, result, ContentHint.IMPLICIT, messageId, false);
|
||||
|
||||
@@ -183,7 +183,8 @@ public class SendReadReceiptJob extends BaseJob {
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
receiptMessage);
|
||||
receiptMessage,
|
||||
recipient.needsPniSignature());
|
||||
|
||||
if (Util.hasItems(messageIds)) {
|
||||
SignalDatabase.messageLog().insertIfPossible(recipientId, timestamp, result, ContentHint.IMPLICIT, messageIds, false);
|
||||
|
||||
@@ -179,7 +179,8 @@ public class SendViewedReceiptJob extends BaseJob {
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
receiptMessage);
|
||||
receiptMessage,
|
||||
recipient.needsPniSignature());
|
||||
|
||||
if (Util.hasItems(messageIds)) {
|
||||
SignalDatabase.messageLog().insertIfPossible(recipientId, timestamp, result, ContentHint.IMPLICIT, messageIds, false);
|
||||
|
||||
@@ -77,6 +77,8 @@ public final class GroupSendUtil {
|
||||
* {@link SendMessageResult}s just like we're used to.
|
||||
*
|
||||
* Messages sent this way, if failed to be decrypted by the receiving party, can be requested to be resent.
|
||||
* Note that the ContentHint <em>may not</em> be {@link ContentHint#RESENDABLE} -- it just means that we have an actual record of the message
|
||||
* and we <em>could</em> resend it if asked.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
@@ -348,7 +350,7 @@ public final class GroupSendUtil {
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, result -> {
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, legacyTargets, access, recipientUpdate, result -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
@@ -416,6 +418,7 @@ public final class GroupSendUtil {
|
||||
|
||||
@NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@@ -471,14 +474,26 @@ public final class GroupSendUtil {
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
LegacyGroupEvents listener = relatedMessageId != null ? new LegacyMetricEventListener(relatedMessageId.getId()) : LegacyGroupEvents.EMPTY;
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
if (targets.size() == 1 && relatedMessageId == null) {
|
||||
Recipient targetRecipient = targetRecipients.get(0);
|
||||
SendMessageResult result = messageSender.sendDataMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.needsPniSignature());
|
||||
|
||||
if (targetRecipient.needsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(targetRecipients.get(0).getId(), getSentTimestamp(), result);
|
||||
}
|
||||
|
||||
return Collections.singletonList(result);
|
||||
} else {
|
||||
LegacyGroupEvents listener = relatedMessageId != null ? new LegacyMetricEventListener(relatedMessageId.getId()) : LegacyGroupEvents.EMPTY;
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -534,6 +549,7 @@ public final class GroupSendUtil {
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@@ -592,6 +608,7 @@ public final class GroupSendUtil {
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@@ -662,6 +679,7 @@ public final class GroupSendUtil {
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
|
||||
@@ -383,6 +383,8 @@ public final class MessageContentProcessor {
|
||||
handleRetryReceipt(content, content.getDecryptionErrorMessage().get(), senderRecipient);
|
||||
} else if (content.getSenderKeyDistributionMessage().isPresent()) {
|
||||
// Already handled, here in order to prevent unrecognized message log
|
||||
} else if (content.getPniSignatureMessage().isPresent()) {
|
||||
// Already handled, here in order to prevent unrecognized message log
|
||||
} else {
|
||||
warn(String.valueOf(content.getTimestamp()), "Got unrecognized message!");
|
||||
}
|
||||
@@ -2559,6 +2561,7 @@ public final class MessageContentProcessor {
|
||||
PushProcessEarlyMessagesJob.enqueue();
|
||||
}
|
||||
|
||||
SignalDatabase.pendingPniSignatureMessages().acknowledgeReceipts(senderRecipient.getId(), message.getTimestamps(), content.getSenderDevice());
|
||||
SignalDatabase.messageLog().deleteEntriesForRecipient(message.getTimestamps(), senderRecipient.getId(), content.getSenderDevice());
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.signalservice.api.InvalidMessageStructureException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
|
||||
@@ -54,6 +55,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -89,6 +91,16 @@ public final class MessageDecryptionUtil {
|
||||
destination = aci;
|
||||
}
|
||||
|
||||
if (destination.equals(pni)) {
|
||||
if (envelope.hasSourceUuid()) {
|
||||
RecipientId sender = RecipientId.from(envelope.getSourceAddress());
|
||||
SignalDatabase.recipients().markNeedsPniSignature(sender);
|
||||
} else {
|
||||
Log.w(TAG, "[" + envelope.getTimestamp() + "] Got a sealed sender message to our PNI? Invalid message, ignoring.");
|
||||
return DecryptionResult.forNoop(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
if (!destination.equals(aci) && !destination.equals(pni)) {
|
||||
Log.w(TAG, "Destination of " + destination + " does not match our ACI (" + aci + ") or PNI (" + pni + ")! Defaulting to ACI.");
|
||||
destination = aci;
|
||||
|
||||
@@ -138,6 +138,7 @@ public class Recipient {
|
||||
private final boolean hasGroupsInCommon;
|
||||
private final List<Badge> badges;
|
||||
private final boolean isReleaseNotesRecipient;
|
||||
private final boolean needsPniSignature;
|
||||
|
||||
/**
|
||||
* Returns a {@link LiveRecipient}, which contains a {@link Recipient} that may or may not be
|
||||
@@ -215,20 +216,6 @@ public class Recipient {
|
||||
return externalPush(signalServiceAddress.getServiceId(), signalServiceAddress.getNumber().orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fully-populated {@link Recipient} based off of a {@link SignalServiceAddress},
|
||||
* creating one in the database if necessary. We special-case GV1 members because we want to
|
||||
* prioritize E164 addresses and not use the UUIDs if possible.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull Recipient externalGV1Member(@NonNull SignalServiceAddress address) {
|
||||
if (address.getNumber().isPresent()) {
|
||||
return externalPush(null, address.getNumber().get());
|
||||
} else {
|
||||
return externalPush(address.getServiceId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fully-populated {@link Recipient} based off of a ServiceId, creating one
|
||||
* in the database if necessary.
|
||||
@@ -452,6 +439,7 @@ public class Recipient {
|
||||
this.hasGroupsInCommon = false;
|
||||
this.badges = Collections.emptyList();
|
||||
this.isReleaseNotesRecipient = false;
|
||||
this.needsPniSignature = false;
|
||||
}
|
||||
|
||||
public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
|
||||
@@ -510,6 +498,7 @@ public class Recipient {
|
||||
this.hasGroupsInCommon = details.hasGroupsInCommon;
|
||||
this.badges = details.badges;
|
||||
this.isReleaseNotesRecipient = details.isReleaseChannel;
|
||||
this.needsPniSignature = details.needsPniSignature;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getId() {
|
||||
@@ -1221,6 +1210,10 @@ public class Recipient {
|
||||
return isReleaseNotesRecipient || isSelf;
|
||||
}
|
||||
|
||||
public boolean needsPniSignature() {
|
||||
return FeatureFlags.phoneNumberPrivacy() && needsPniSignature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -88,6 +88,7 @@ public class RecipientDetails {
|
||||
final boolean hasGroupsInCommon;
|
||||
final List<Badge> badges;
|
||||
final boolean isReleaseChannel;
|
||||
final boolean needsPniSignature;
|
||||
|
||||
public RecipientDetails(@Nullable String groupName,
|
||||
@Nullable String systemContactName,
|
||||
@@ -153,6 +154,7 @@ public class RecipientDetails {
|
||||
this.hasGroupsInCommon = record.hasGroupsInCommon();
|
||||
this.badges = record.getBadges();
|
||||
this.isReleaseChannel = isReleaseChannel;
|
||||
this.needsPniSignature = record.needsPniSignature();
|
||||
}
|
||||
|
||||
private RecipientDetails() {
|
||||
@@ -210,6 +212,7 @@ public class RecipientDetails {
|
||||
this.hasGroupsInCommon = false;
|
||||
this.badges = Collections.emptyList();
|
||||
this.isReleaseChannel = false;
|
||||
this.needsPniSignature = false;
|
||||
}
|
||||
|
||||
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientRecord settings) {
|
||||
|
||||
Reference in New Issue
Block a user