Add support for PniSignatureMessages.
This commit is contained in:
parent
1e499fd12f
commit
61498037f3
29 changed files with 602 additions and 210 deletions
|
@ -60,13 +60,6 @@ class RecipientDatabaseTest_processPnpTuple {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun noMatch_pniOnly() {
|
||||
test {
|
||||
process(null, PNI_A, null)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun noMatch_noData() {
|
||||
test {
|
||||
|
|
|
@ -67,11 +67,6 @@ class RecipientDatabaseTest_processPnpTupleToChangeSet {
|
|||
)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun noMatch_pniOnly() {
|
||||
db.processPnpTupleToChangeSet(null, PNI_A, null, pniVerified = false)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun noMatch_noData() {
|
||||
db.processPnpTupleToChangeSet(null, null, null, pniVerified = false)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -144,7 +144,8 @@ object RecipientDatabaseTestUtils {
|
|||
syncExtras,
|
||||
extras,
|
||||
hasGroupsInCommon,
|
||||
badges
|
||||
badges,
|
||||
false
|
||||
),
|
||||
participants,
|
||||
isReleaseChannel
|
||||
|
|
|
@ -34,6 +34,15 @@ fun SupportSQLiteDatabase.getTableRowCount(table: String): Int {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a row exists that matches the query.
|
||||
*/
|
||||
fun SupportSQLiteDatabase.exists(table: String, query: String, vararg args: Any): Boolean {
|
||||
return this.query("SELECT EXISTS(SELECT 1 FROM $table WHERE $query)", SqlUtil.buildArgs(*args)).use { cursor ->
|
||||
cursor.moveToFirst() && cursor.getInt(0) == 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins a SELECT statement with a helpful builder pattern.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.signalservice.api;
|
|||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
|
@ -63,6 +64,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMes
|
|||
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
|
@ -81,6 +83,7 @@ import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
|||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
import org.whispersystems.signalservice.api.util.Uint64RangeException;
|
||||
import org.whispersystems.signalservice.api.util.Uint64Util;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
|
@ -96,6 +99,7 @@ import org.whispersystems.signalservice.internal.push.PushAttachmentData;
|
|||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.push.SendGroupMessageResponse;
|
||||
import org.whispersystems.signalservice.internal.push.SendMessageResponse;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
|
||||
|
@ -155,11 +159,13 @@ public class SignalServiceMessageSender {
|
|||
private static final int RETRY_COUNT = 4;
|
||||
|
||||
private final PushServiceSocket socket;
|
||||
private final SignalServiceAccountDataStore store;
|
||||
private final SignalServiceAccountDataStore aciStore;
|
||||
private final SignalSessionLock sessionLock;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final int localDeviceId;
|
||||
private final PNI localPni;
|
||||
private final Optional<EventListener> eventListener;
|
||||
private final IdentityKeyPair localPniIdentity;
|
||||
|
||||
private final AttachmentService attachmentService;
|
||||
private final MessagingService messagingService;
|
||||
|
@ -180,15 +186,17 @@ public class SignalServiceMessageSender {
|
|||
boolean automaticNetworkRetry)
|
||||
{
|
||||
this.socket = new PushServiceSocket(urls, credentialsProvider, signalAgent, clientZkProfileOperations, automaticNetworkRetry);
|
||||
this.store = store.aci();
|
||||
this.aciStore = store.aci();
|
||||
this.sessionLock = sessionLock;
|
||||
this.localAddress = new SignalServiceAddress(credentialsProvider.getAci(), credentialsProvider.getE164());
|
||||
this.localDeviceId = credentialsProvider.getDeviceId();
|
||||
this.localPni = credentialsProvider.getPni();
|
||||
this.attachmentService = new AttachmentService(signalWebSocket);
|
||||
this.messagingService = new MessagingService(signalWebSocket);
|
||||
this.eventListener = eventListener;
|
||||
this.executor = executor != null ? executor : Executors.newSingleThreadExecutor();
|
||||
this.maxEnvelopeSize = maxEnvelopeSize;
|
||||
this.localPniIdentity = store.pni().getIdentityKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,10 +207,18 @@ public class SignalServiceMessageSender {
|
|||
*/
|
||||
public SendMessageResult sendReceipt(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
SignalServiceReceiptMessage message)
|
||||
SignalServiceReceiptMessage message,
|
||||
boolean includePniSignature)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Content content = createReceiptContent(message);
|
||||
Content content = createReceiptContent(message);
|
||||
|
||||
if (includePniSignature) {
|
||||
content = content.toBuilder()
|
||||
.setPniSignatureMessage(createPniSignatureMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
return sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), envelopeContent, false, null, false);
|
||||
|
@ -264,7 +280,7 @@ public class SignalServiceMessageSender {
|
|||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
List<SendMessageResult> sendMessageResults = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, false);
|
||||
|
||||
if (store.isMultiDevice()) {
|
||||
if (aciStore.isMultiDevice()) {
|
||||
SignalServiceSyncMessage syncMessage = createSelfSendSyncMessageForStory(message, timestamp, isRecipientUpdate, manifest);
|
||||
sendSyncMessage(syncMessage, Optional.empty());
|
||||
}
|
||||
|
@ -288,7 +304,7 @@ public class SignalServiceMessageSender {
|
|||
Content content = createStoryContent(message);
|
||||
List<SendMessageResult> sendMessageResults = sendGroupMessage(distributionId, recipients, unidentifiedAccess, timestamp, content, ContentHint.IMPLICIT, groupId, false, SenderKeyGroupEvents.EMPTY, false);
|
||||
|
||||
if (store.isMultiDevice()) {
|
||||
if (aciStore.isMultiDevice()) {
|
||||
SignalServiceSyncMessage syncMessage = createSelfSendSyncMessageForStory(message, timestamp, isRecipientUpdate, manifest);
|
||||
sendSyncMessage(syncMessage, Optional.empty());
|
||||
}
|
||||
|
@ -363,13 +379,22 @@ public class SignalServiceMessageSender {
|
|||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
IndividualSendEvents sendEvents,
|
||||
boolean urgent)
|
||||
boolean urgent,
|
||||
boolean includePniSignature)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message.");
|
||||
|
||||
Content content = createMessageContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
Content content = createMessageContent(message);
|
||||
|
||||
if (includePniSignature) {
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Including PNI signature.");
|
||||
content = content.toBuilder()
|
||||
.setPniSignatureMessage(createPniSignatureMessage())
|
||||
.build();
|
||||
}
|
||||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
|
||||
sendEvents.onMessageEncrypted();
|
||||
|
||||
|
@ -396,7 +421,7 @@ public class SignalServiceMessageSender {
|
|||
*/
|
||||
public SenderKeyDistributionMessage getOrCreateNewGroupSession(DistributionId distributionId) {
|
||||
SignalProtocolAddress self = new SignalProtocolAddress(localAddress.getIdentifier(), localDeviceId);
|
||||
return new SignalGroupSessionBuilder(sessionLock, new GroupSessionBuilder(store)).create(self, distributionId.asUuid());
|
||||
return new SignalGroupSessionBuilder(sessionLock, new GroupSessionBuilder(aciStore)).create(self, distributionId.asUuid());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -423,7 +448,7 @@ public class SignalServiceMessageSender {
|
|||
* Processes an inbound {@link SenderKeyDistributionMessage}.
|
||||
*/
|
||||
public void processSenderKeyDistributionMessage(SignalProtocolAddress sender, SenderKeyDistributionMessage senderKeyDistributionMessage) {
|
||||
new SignalGroupSessionBuilder(sessionLock, new GroupSessionBuilder(store)).process(sender, senderKeyDistributionMessage);
|
||||
new SignalGroupSessionBuilder(sessionLock, new GroupSessionBuilder(aciStore)).process(sender, senderKeyDistributionMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -465,7 +490,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
sendEvents.onMessageSent();
|
||||
|
||||
if (store.isMultiDevice()) {
|
||||
if (aciStore.isMultiDevice()) {
|
||||
Content syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.empty(), message.getTimestamp(), results, isRecipientUpdate, Collections.emptySet());
|
||||
EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
|
@ -511,7 +536,7 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
}
|
||||
|
||||
if (needsSyncInResults || store.isMultiDevice()) {
|
||||
if (needsSyncInResults || aciStore.isMultiDevice()) {
|
||||
Optional<SignalServiceAddress> recipient = Optional.empty();
|
||||
if (!message.getGroupContext().isPresent() && recipients.size() == 1) {
|
||||
recipient = Optional.of(recipients.get(0));
|
||||
|
@ -771,6 +796,15 @@ public class SignalServiceMessageSender {
|
|||
return sendMessage(address, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, false);
|
||||
}
|
||||
|
||||
private SignalServiceProtos.PniSignatureMessage createPniSignatureMessage() {
|
||||
byte[] signature = localPniIdentity.signAlternateIdentity(aciStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
return SignalServiceProtos.PniSignatureMessage.newBuilder()
|
||||
.setPni(UuidUtil.toByteString(localPni.uuid()))
|
||||
.setSignature(ByteString.copyFrom(signature))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Content createTypingContent(SignalServiceTypingMessage message) {
|
||||
Content.Builder container = Content.newBuilder();
|
||||
TypingMessage.Builder builder = TypingMessage.newBuilder();
|
||||
|
@ -1755,7 +1789,7 @@ public class SignalServiceMessageSender {
|
|||
if (!unidentifiedAccess.isPresent()) {
|
||||
try {
|
||||
SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.empty()).blockingGet()).getResultOrThrow();
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
} catch (InvalidUnidentifiedAccessHeaderException | UnregisteredUserException | MismatchedDevicesException | StaleDevicesException e) {
|
||||
// Non-technical failures shouldn't be retried with socket
|
||||
throw e;
|
||||
|
@ -1768,7 +1802,7 @@ public class SignalServiceMessageSender {
|
|||
} else if (unidentifiedAccess.isPresent()) {
|
||||
try {
|
||||
SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess).blockingGet()).getResultOrThrow();
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
} catch (InvalidUnidentifiedAccessHeaderException | UnregisteredUserException | MismatchedDevicesException | StaleDevicesException e) {
|
||||
// Non-technical failures shouldn't be retried with socket
|
||||
throw e;
|
||||
|
@ -1789,7 +1823,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess);
|
||||
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w(TAG, ike);
|
||||
|
@ -1849,7 +1883,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
for (int i = 0; i < RETRY_COUNT; i++) {
|
||||
GroupTargetInfo targetInfo = buildGroupTargetInfo(recipients);
|
||||
Set<SignalProtocolAddress> sharedWith = store.getSenderKeySharedWith(distributionId);
|
||||
Set<SignalProtocolAddress> sharedWith = aciStore.getSenderKeySharedWith(distributionId);
|
||||
List<SignalServiceAddress> needsSenderKey = targetInfo.destinations.stream()
|
||||
.filter(a -> !sharedWith.contains(a))
|
||||
.map(a -> ServiceId.parseOrThrow(a.getName()))
|
||||
|
@ -1876,7 +1910,7 @@ public class SignalServiceMessageSender {
|
|||
Set<String> successSids = successes.stream().map(a -> a.getServiceId().toString()).collect(Collectors.toSet());
|
||||
Set<SignalProtocolAddress> successAddresses = targetInfo.destinations.stream().filter(a -> successSids.contains(a.getName())).collect(Collectors.toSet());
|
||||
|
||||
store.markSenderKeySharedWith(distributionId, successAddresses);
|
||||
aciStore.markSenderKeySharedWith(distributionId, successAddresses);
|
||||
|
||||
Log.i(TAG, "[sendGroupMessage][" + timestamp + "] Successfully sent sender keys to " + successes.size() + "/" + needsSenderKey.size() + " recipients.");
|
||||
|
||||
|
@ -1909,7 +1943,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
sendEvents.onSenderKeyShared();
|
||||
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, store, sessionLock, null);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, aciStore, sessionLock, null);
|
||||
SenderCertificate senderCertificate = unidentifiedAccess.get(0).getUnidentifiedCertificate();
|
||||
|
||||
byte[] ciphertext;
|
||||
|
@ -1963,7 +1997,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
private GroupTargetInfo buildGroupTargetInfo(List<SignalServiceAddress> recipients) {
|
||||
List<String> addressNames = recipients.stream().map(SignalServiceAddress::getIdentifier).collect(Collectors.toList());
|
||||
Set<SignalProtocolAddress> destinations = store.getAllAddressesWithActiveSessions(addressNames);
|
||||
Set<SignalProtocolAddress> destinations = aciStore.getAllAddressesWithActiveSessions(addressNames);
|
||||
Map<String, List<Integer>> devicesByAddressName = new HashMap<>();
|
||||
|
||||
destinations.addAll(recipients.stream()
|
||||
|
@ -2009,7 +2043,7 @@ public class SignalServiceMessageSender {
|
|||
List<SendMessageResult> success = recipients.keySet()
|
||||
.stream()
|
||||
.filter(r -> !unregistered.contains(r.getServiceId()))
|
||||
.map(a -> SendMessageResult.success(a, recipients.get(a), true, store.isMultiDevice(), -1, Optional.of(content)))
|
||||
.map(a -> SendMessageResult.success(a, recipients.get(a), true, aciStore.isMultiDevice(), -1, Optional.of(content)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<SendMessageResult> results = new ArrayList<>(success.size() + failures.size());
|
||||
|
@ -2109,7 +2143,7 @@ public class SignalServiceMessageSender {
|
|||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<>();
|
||||
|
||||
List<Integer> subDevices = store.getSubDeviceSessions(recipient.getIdentifier());
|
||||
List<Integer> subDevices = aciStore.getSubDeviceSessions(recipient.getIdentifier());
|
||||
|
||||
List<Integer> deviceIds = new ArrayList<>(subDevices.size() + 1);
|
||||
deviceIds.add(SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
|
@ -2120,7 +2154,7 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
for (int deviceId : deviceIds) {
|
||||
if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || store.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) {
|
||||
if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || aciStore.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) {
|
||||
messages.add(getEncryptedMessage(socket, recipient, unidentifiedAccess, deviceId, plaintext));
|
||||
}
|
||||
}
|
||||
|
@ -2136,16 +2170,16 @@ public class SignalServiceMessageSender {
|
|||
throws IOException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getIdentifier(), deviceId);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, store, sessionLock, null);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, aciStore, sessionLock, null);
|
||||
|
||||
if (!store.containsSession(signalProtocolAddress)) {
|
||||
if (!aciStore.containsSession(signalProtocolAddress)) {
|
||||
try {
|
||||
List<PreKeyBundle> preKeys = socket.getPreKeys(recipient, unidentifiedAccess, deviceId);
|
||||
|
||||
for (PreKeyBundle preKey : preKeys) {
|
||||
try {
|
||||
SignalProtocolAddress preKeyAddress = new SignalProtocolAddress(recipient.getIdentifier(), preKey.getDeviceId());
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, preKeyAddress));
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(aciStore, preKeyAddress));
|
||||
sessionBuilder.process(preKey);
|
||||
} catch (org.signal.libsignal.protocol.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey());
|
||||
|
@ -2179,7 +2213,7 @@ public class SignalServiceMessageSender {
|
|||
PreKeyBundle preKey = socket.getPreKey(recipient, missingDeviceId);
|
||||
|
||||
try {
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId)));
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(aciStore, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId)));
|
||||
sessionBuilder.process(preKey);
|
||||
} catch (org.signal.libsignal.protocol.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey());
|
||||
|
@ -2199,7 +2233,7 @@ public class SignalServiceMessageSender {
|
|||
List<SignalProtocolAddress> addressesToClear = convertToProtocolAddresses(recipient, devices);
|
||||
|
||||
for (SignalProtocolAddress address : addressesToClear) {
|
||||
store.archiveSession(address);
|
||||
aciStore.archiveSession(address);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMes
|
|||
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.payments.Money;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||
|
@ -82,17 +83,19 @@ public final class SignalServiceContent {
|
|||
private final Optional<byte[]> groupId;
|
||||
private final String destinationUuid;
|
||||
|
||||
private final Optional<SignalServiceDataMessage> message;
|
||||
private final Optional<SignalServiceSyncMessage> synchronizeMessage;
|
||||
private final Optional<SignalServiceCallMessage> callMessage;
|
||||
private final Optional<SignalServiceReceiptMessage> readMessage;
|
||||
private final Optional<SignalServiceTypingMessage> typingMessage;
|
||||
private final Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage;
|
||||
private final Optional<DecryptionErrorMessage> decryptionErrorMessage;
|
||||
private final Optional<SignalServiceStoryMessage> storyMessage;
|
||||
private final Optional<SignalServiceDataMessage> message;
|
||||
private final Optional<SignalServiceSyncMessage> synchronizeMessage;
|
||||
private final Optional<SignalServiceCallMessage> callMessage;
|
||||
private final Optional<SignalServiceReceiptMessage> readMessage;
|
||||
private final Optional<SignalServiceTypingMessage> typingMessage;
|
||||
private final Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage;
|
||||
private final Optional<DecryptionErrorMessage> decryptionErrorMessage;
|
||||
private final Optional<SignalServiceStoryMessage> storyMessage;
|
||||
private final Optional<SignalServicePniSignatureMessage> pniSignatureMessage;
|
||||
|
||||
private SignalServiceContent(SignalServiceDataMessage message,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -123,10 +126,12 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceSyncMessage synchronizeMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -157,10 +162,12 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceCallMessage callMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -191,10 +198,12 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceReceiptMessage receiptMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -225,10 +234,12 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(DecryptionErrorMessage errorMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -259,10 +270,12 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.of(errorMessage);
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceTypingMessage typingMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -293,9 +306,11 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SenderKeyDistributionMessage senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -326,9 +341,11 @@ public final class SignalServiceContent {
|
|||
this.senderKeyDistributionMessage = Optional.of(senderKeyDistributionMessage);
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceStoryMessage storyMessage,
|
||||
private SignalServiceContent(SignalServicePniSignatureMessage pniSignatureMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
|
@ -356,9 +373,46 @@ public final class SignalServiceContent {
|
|||
this.callMessage = Optional.empty();
|
||||
this.readMessage = Optional.empty();
|
||||
this.typingMessage = Optional.empty();
|
||||
this.senderKeyDistributionMessage = Optional.empty();
|
||||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.empty();
|
||||
this.pniSignatureMessage = Optional.of(pniSignatureMessage);
|
||||
}
|
||||
|
||||
private SignalServiceContent(SignalServiceStoryMessage storyMessage,
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage,
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage,
|
||||
SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
long timestamp,
|
||||
long serverReceivedTimestamp,
|
||||
long serverDeliveredTimestamp,
|
||||
boolean needsReceipt,
|
||||
String serverUuid,
|
||||
Optional<byte[]> groupId,
|
||||
String destinationUuid,
|
||||
SignalServiceContentProto serializedState)
|
||||
{
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.serverReceivedTimestamp = serverReceivedTimestamp;
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serverUuid = serverUuid;
|
||||
this.groupId = groupId;
|
||||
this.destinationUuid = destinationUuid;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.empty();
|
||||
this.synchronizeMessage = Optional.empty();
|
||||
this.callMessage = Optional.empty();
|
||||
this.readMessage = Optional.empty();
|
||||
this.typingMessage = Optional.empty();
|
||||
this.senderKeyDistributionMessage = senderKeyDistributionMessage;
|
||||
this.decryptionErrorMessage = Optional.empty();
|
||||
this.storyMessage = Optional.of(storyMessage);
|
||||
this.pniSignatureMessage = pniSignatureMessage;
|
||||
}
|
||||
|
||||
public Optional<SignalServiceDataMessage> getDataMessage() {
|
||||
|
@ -393,6 +447,10 @@ public final class SignalServiceContent {
|
|||
return decryptionErrorMessage;
|
||||
}
|
||||
|
||||
public Optional<SignalServicePniSignatureMessage> getPniSignatureMessage() {
|
||||
return pniSignatureMessage;
|
||||
}
|
||||
|
||||
public SignalServiceAddress getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
@ -456,20 +514,7 @@ public final class SignalServiceContent {
|
|||
SignalServiceAddress localAddress = SignalServiceAddressProtobufSerializer.fromProtobuf(serviceContentProto.getLocalAddress());
|
||||
|
||||
if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.LEGACYDATAMESSAGE) {
|
||||
SignalServiceProtos.DataMessage message = serviceContentProto.getLegacyDataMessage();
|
||||
|
||||
return new SignalServiceContent(createSignalServiceMessage(metadata, message),
|
||||
Optional.empty(),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.getServerReceivedTimestamp(),
|
||||
metadata.getServerDeliveredTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
metadata.getServerGuid(),
|
||||
metadata.getGroupId(),
|
||||
metadata.getDestinationUuid(),
|
||||
serviceContentProto);
|
||||
throw new InvalidMessageStructureException("Legacy message!");
|
||||
} else if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.CONTENT) {
|
||||
SignalServiceProtos.Content message = serviceContentProto.getContent();
|
||||
Optional<SenderKeyDistributionMessage> senderKeyDistributionMessage = Optional.empty();
|
||||
|
@ -482,9 +527,21 @@ public final class SignalServiceContent {
|
|||
}
|
||||
}
|
||||
|
||||
Optional<SignalServicePniSignatureMessage> pniSignatureMessage = Optional.empty();
|
||||
|
||||
if (message.hasPniSignatureMessage()) {
|
||||
PNI pni = PNI.parseOrNull(message.getPniSignatureMessage().getPni().toByteArray());
|
||||
if (pni != null) {
|
||||
pniSignatureMessage = Optional.of(new SignalServicePniSignatureMessage(pni, message.getPniSignatureMessage().getSignature().toByteArray()));
|
||||
} else {
|
||||
Log.w(TAG, "Invalid PNI on PNI signature message! Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
if (message.hasDataMessage()) {
|
||||
return new SignalServiceContent(createSignalServiceMessage(metadata, message.getDataMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -498,6 +555,7 @@ public final class SignalServiceContent {
|
|||
} else if (message.hasSyncMessage() && localAddress.matches(metadata.getSender())) {
|
||||
return new SignalServiceContent(createSynchronizeMessage(metadata, message.getSyncMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -511,6 +569,7 @@ public final class SignalServiceContent {
|
|||
} else if (message.hasCallMessage()) {
|
||||
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -524,6 +583,7 @@ public final class SignalServiceContent {
|
|||
} else if (message.hasReceiptMessage()) {
|
||||
return new SignalServiceContent(createReceiptMessage(metadata, message.getReceiptMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -537,6 +597,7 @@ public final class SignalServiceContent {
|
|||
} else if (message.hasTypingMessage()) {
|
||||
return new SignalServiceContent(createTypingMessage(metadata, message.getTypingMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -550,6 +611,7 @@ public final class SignalServiceContent {
|
|||
} else if (message.hasDecryptionErrorMessage()) {
|
||||
return new SignalServiceContent(createDecryptionErrorMessage(metadata, message.getDecryptionErrorMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -562,6 +624,21 @@ public final class SignalServiceContent {
|
|||
serviceContentProto);
|
||||
} else if (message.hasStoryMessage()) {
|
||||
return new SignalServiceContent(createStoryMessage(message.getStoryMessage()),
|
||||
senderKeyDistributionMessage,
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.getServerReceivedTimestamp(),
|
||||
metadata.getServerDeliveredTimestamp(),
|
||||
false,
|
||||
metadata.getServerGuid(),
|
||||
metadata.getGroupId(),
|
||||
metadata.getDestinationUuid(),
|
||||
serviceContentProto);
|
||||
} else if (pniSignatureMessage.isPresent()) {
|
||||
return new SignalServiceContent(pniSignatureMessage.get(),
|
||||
senderKeyDistributionMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
@ -575,6 +652,7 @@ public final class SignalServiceContent {
|
|||
} else if (senderKeyDistributionMessage.isPresent()) {
|
||||
// IMPORTANT: This block should always be last, since you can pair SKDM's with other content
|
||||
return new SignalServiceContent(senderKeyDistributionMessage.get(),
|
||||
pniSignatureMessage,
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
|
|
|
@ -9,10 +9,8 @@ package org.whispersystems.signalservice.api.messages;
|
|||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope;
|
||||
|
@ -21,7 +19,6 @@ import org.whispersystems.util.Base64;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class represents an encrypted Signal Service envelope.
|
||||
|
@ -162,7 +159,7 @@ public class SignalServiceEnvelope {
|
|||
* @return The envelope's sender as a SignalServiceAddress.
|
||||
*/
|
||||
public SignalServiceAddress getSourceAddress() {
|
||||
return new SignalServiceAddress(ACI.parseOrNull(envelope.getSourceUuid()));
|
||||
return new SignalServiceAddress(ServiceId.parseOrNull(envelope.getSourceUuid()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
|
||||
/**
|
||||
* When someone sends a message to your PNI, you need to attach one of these PNI signature messages,
|
||||
* proving that you own the PNI identity.
|
||||
*
|
||||
* The signature is generated by signing your ACI public key with your PNI identity.
|
||||
*/
|
||||
public class SignalServicePniSignatureMessage {
|
||||
|
||||
private final PNI pni;
|
||||
private final byte[] signature;
|
||||
|
||||
public SignalServicePniSignatureMessage(PNI pni, byte[] signature) {
|
||||
this.pni = pni;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public PNI getPni() {
|
||||
return pni;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,11 @@ public final class PNI extends ServiceId {
|
|||
return from(UUID.fromString(raw));
|
||||
}
|
||||
|
||||
public static PNI parseOrNull(byte[] raw) {
|
||||
UUID uuid = UuidUtil.parseOrNull(raw);
|
||||
return uuid != null ? from(uuid) : null;
|
||||
}
|
||||
|
||||
private PNI(UUID uuid) {
|
||||
super(uuid);
|
||||
}
|
||||
|
|
|
@ -39,15 +39,16 @@ message Envelope {
|
|||
}
|
||||
|
||||
message Content {
|
||||
optional DataMessage dataMessage = 1;
|
||||
optional SyncMessage syncMessage = 2;
|
||||
optional CallMessage callMessage = 3;
|
||||
optional NullMessage nullMessage = 4;
|
||||
optional ReceiptMessage receiptMessage = 5;
|
||||
optional TypingMessage typingMessage = 6;
|
||||
optional bytes senderKeyDistributionMessage = 7;
|
||||
optional bytes decryptionErrorMessage = 8;
|
||||
optional StoryMessage storyMessage = 9;
|
||||
optional DataMessage dataMessage = 1;
|
||||
optional SyncMessage syncMessage = 2;
|
||||
optional CallMessage callMessage = 3;
|
||||
optional NullMessage nullMessage = 4;
|
||||
optional ReceiptMessage receiptMessage = 5;
|
||||
optional TypingMessage typingMessage = 6;
|
||||
optional bytes senderKeyDistributionMessage = 7;
|
||||
optional bytes decryptionErrorMessage = 8;
|
||||
optional StoryMessage storyMessage = 9;
|
||||
optional PniSignatureMessage pniSignatureMessage = 10;
|
||||
}
|
||||
|
||||
message CallMessage {
|
||||
|
@ -711,3 +712,8 @@ message DecryptionErrorMessage {
|
|||
optional uint64 timestamp = 2;
|
||||
optional uint32 deviceId = 3;
|
||||
}
|
||||
|
||||
message PniSignatureMessage {
|
||||
optional bytes pni = 1;
|
||||
optional bytes signature = 2;
|
||||
}
|
Loading…
Add table
Reference in a new issue