diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt index 0aa395e925..45bb24cc24 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt @@ -14,6 +14,7 @@ import org.whispersystems.signalservice.api.crypto.EnvelopeContent import org.whispersystems.signalservice.api.crypto.SealedSenderAccess import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.util.toByteArray import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.DataMessage import org.whispersystems.signalservice.internal.push.Envelope @@ -52,13 +53,16 @@ object FakeClientHelpers { } fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope { + val serverGuid = UUID.randomUUID() return Envelope.Builder() .type(Envelope.Type.fromValue(this.type)) .sourceDevice(1) .timestamp(timestamp) .serverTimestamp(timestamp + 1) .destinationServiceId(destination.toString()) - .serverGuid(UUID.randomUUID().toString()) + .destinationServiceIdBinary(destination.toByteString()) + .serverGuid(serverGuid.toString()) + .serverGuidBinary(serverGuid.toByteArray().toByteString()) .content(Base64.decode(this.content).toByteString()) .urgent(true) .story(false) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt index c5b6c4dda5..65471e300e 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MessageContentFuzzer.kt @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata import org.whispersystems.signalservice.api.util.UuidUtil +import org.whispersystems.signalservice.api.util.toByteArray import org.whispersystems.signalservice.internal.push.AddressableMessage import org.whispersystems.signalservice.internal.push.AttachmentPointer import org.whispersystems.signalservice.internal.push.BodyRange @@ -43,7 +44,7 @@ object MessageContentFuzzer { return Envelope.Builder() .timestamp(timestamp) .serverTimestamp(timestamp + 5) - .serverGuid(serverGuid.toString()) + .serverGuidBinary(serverGuid.toByteArray().toByteString()) .build() } @@ -127,7 +128,7 @@ object MessageContentFuzzer { unidentifiedStatus( deliveredTo.map { SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith { - destinationServiceId = Recipient.resolved(it).requireServiceId().toString() + destinationServiceIdBinary = Recipient.resolved(it).requireServiceId().toByteString() unidentified = true } } @@ -147,7 +148,7 @@ object MessageContentFuzzer { SyncMessage.Builder().buildWith { read = timestamps.map { (senderId, timestamp) -> SyncMessage.Read.Builder().buildWith { - this.senderAci = Recipient.resolved(senderId).requireAci().toString() + this.senderAciBinary = Recipient.resolved(senderId).requireAci().toByteString() this.timestamp = timestamp } } @@ -167,12 +168,12 @@ object MessageContentFuzzer { conversation = if (conversation.isGroup) { ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString()) } else { - ConversationIdentifier(threadServiceId = conversation.requireAci().toString()) + ConversationIdentifier(threadServiceIdBinary = conversation.requireAci().toByteString()) }, messages = conversationDeletes.map { (author, timestamp) -> AddressableMessage( - authorServiceId = Recipient.resolved(author).requireAci().toString(), + authorServiceIdBinary = Recipient.resolved(author).requireAci().toByteString(), sentTimestamp = timestamp ) } @@ -195,19 +196,19 @@ object MessageContentFuzzer { conversation = if (conversation.isGroup) { ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString()) } else { - ConversationIdentifier(threadServiceId = conversation.requireAci().toString()) + ConversationIdentifier(threadServiceIdBinary = conversation.requireAci().toByteString()) }, mostRecentMessages = delete.messages.map { (author, timestamp) -> AddressableMessage( - authorServiceId = Recipient.resolved(author).requireAci().toString(), + authorServiceIdBinary = Recipient.resolved(author).requireAci().toByteString(), sentTimestamp = timestamp ) }, mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) -> AddressableMessage( - authorServiceId = Recipient.resolved(author).requireAci().toString(), + authorServiceIdBinary = Recipient.resolved(author).requireAci().toByteString(), sentTimestamp = timestamp ) }, @@ -232,7 +233,7 @@ object MessageContentFuzzer { conversation = if (conversation.isGroup) { ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString()) } else { - ConversationIdentifier(threadServiceId = conversation.requireAci().toString()) + ConversationIdentifier(threadServiceIdBinary = conversation.requireAci().toByteString()) } ) } @@ -254,10 +255,10 @@ object MessageContentFuzzer { conversation = if (conversation.isGroup) { ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString()) } else { - ConversationIdentifier(threadServiceId = conversation.requireAci().toString()) + ConversationIdentifier(threadServiceIdBinary = conversation.requireAci().toByteString()) }, targetMessage = AddressableMessage( - authorServiceId = Recipient.resolved(message.first).requireAci().toString(), + authorServiceIdBinary = Recipient.resolved(message.first).requireAci().toByteString(), sentTimestamp = message.second ), clientUuid = uuid?.let { UuidUtil.toByteString(it) }, @@ -290,7 +291,7 @@ object MessageContentFuzzer { val quoted = quoteAble.random(random) quote = DataMessage.Quote.Builder().buildWith { id = quoted.envelope.timestamp - authorAci = quoted.metadata.sourceServiceId.toString() + authorAciBinary = quoted.metadata.sourceServiceId.toByteString() text = quoted.content.dataMessage?.body attachments(quoted.content.dataMessage?.attachments ?: emptyList()) bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList()) @@ -302,7 +303,7 @@ object MessageContentFuzzer { val quoted = quoteAble.random(random) quote = DataMessage.Quote.Builder().buildWith { id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!) - authorAci = quoted.metadata.sourceServiceId.toString() + authorAciBinary = quoted.metadata.sourceServiceId.toByteString() text = quoted.content.dataMessage?.body } } @@ -329,7 +330,7 @@ object MessageContentFuzzer { reaction = DataMessage.Reaction.Builder().buildWith { emoji = emojis.random(random) remove = false - targetAuthorAci = reactTo.metadata.sourceServiceId.toString() + targetAuthorAciBinary = reactTo.metadata.sourceServiceId.toByteString() targetSentTimestamp = reactTo.envelope.timestamp } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt index 0707def510..427ccdd565 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt @@ -620,7 +620,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign } val privacyMode: DistributionListPrivacyMode = when { - insert.proto.isBlockList && insert.proto.recipientServiceIds.isEmpty() -> DistributionListPrivacyMode.ALL + insert.proto.isBlockList && insert.proto.recipientServiceIds.isEmpty() && insert.proto.recipientServiceIdsBinary.isEmpty() -> DistributionListPrivacyMode.ALL insert.proto.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT else -> DistributionListPrivacyMode.ONLY_WITH } @@ -666,7 +666,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign } val privacyMode: DistributionListPrivacyMode = when { - update.new.proto.isBlockList && update.new.proto.recipientServiceIds.isEmpty() -> DistributionListPrivacyMode.ALL + update.new.proto.isBlockList && update.new.proto.recipientServiceIds.isEmpty() && update.new.proto.recipientServiceIdsBinary.isEmpty() -> DistributionListPrivacyMode.ALL update.new.proto.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT else -> DistributionListPrivacyMode.ONLY_WITH } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index f478ef13c2..b2e0ce4d1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -4888,7 +4888,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat writableDatabase.withinTransaction { for (readMessage in readMessages) { - val authorId: RecipientId = recipients.getOrInsertFromServiceId(ServiceId.parseOrThrow(readMessage.senderAci!!)) + val authorId: RecipientId = recipients.getOrInsertFromServiceId(ServiceId.ACI.parseOrThrow(readMessage.senderAci, readMessage.senderAciBinary)) val result: TimestampReadResult = setTimestampReadFromSyncMessageInternal( messageId = SyncMessageId(authorId, readMessage.timestamp!!), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index ebbddc9b95..3382d4a919 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -869,7 +869,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val recipientId: RecipientId if (id < 0) { Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.") - recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(insert.proto.aci), pni = PNI.parseOrNull(insert.proto.pni), e164 = insert.proto.e164.nullIfBlank(), pniVerified = insert.proto.pniSignatureVerified) + recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(insert.proto.aci, insert.proto.aciBinary), pni = PNI.parseOrNull(insert.proto.pni, insert.proto.pniBinary), e164 = insert.proto.e164.nullIfBlank(), pniVerified = insert.proto.pniSignatureVerified) resolvePotentialUsernameConflicts(values.getAsString(USERNAME), recipientId) db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId)) @@ -917,7 +917,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da var recipientId = getByColumn(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.old.id.raw)).get() Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.") - recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(update.new.proto.aci), pni = PNI.parseOrNull(update.new.proto.pni), e164 = update.new.proto.e164.nullIfBlank(), pniVerified = update.new.proto.pniSignatureVerified) + recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(update.new.proto.aci, update.new.proto.aciBinary), pni = PNI.parseOrNull(update.new.proto.pni, update.new.proto.pniBinary), e164 = update.new.proto.e164.nullIfBlank(), pniVerified = update.new.proto.pniSignatureVerified) Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId") resolvePotentialUsernameConflicts(values.getAsString(USERNAME), recipientId) @@ -943,7 +943,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val oldIdentityRecord = identityStore.getIdentityRecord(recipientId) if (update.new.proto.identityKey.isNotEmpty() && update.new.proto.signalAci != null) { val identityKey = IdentityKey(update.new.proto.identityKey.toByteArray(), 0) - identities.updateIdentityAfterSync(update.new.proto.aci, recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.proto.identityState)) + identities.updateIdentityAfterSync(update.new.proto.signalAci!!.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.proto.identityState)) } val newIdentityRecord = identityStore.getIdentityRecord(recipientId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SentStorySyncManifest.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SentStorySyncManifest.kt index 159b7816ff..6aefdf7f3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SentStorySyncManifest.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SentStorySyncManifest.kt @@ -8,6 +8,7 @@ import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.push.SyncMessage +import org.whispersystems.signalservice.internal.util.Util /** * Represents a list of, or update to a list of, who can access a story through what @@ -88,9 +89,9 @@ data class SentStorySyncManifest( } fun fromRecipientsSet(recipients: List): SentStorySyncManifest { - val entries = recipients.toSet().filter { it.destinationServiceId != null }.map { recipient -> + val entries = recipients.toSet().filter { Util.anyNotNull(it.destinationServiceId, it.destinationServiceIdBinary) }.map { recipient -> Entry( - recipientId = RecipientId.from(ServiceId.parseOrThrow(recipient.destinationServiceId!!)), + recipientId = RecipientId.from(ServiceId.parseOrThrow(recipient.destinationServiceId, recipient.destinationServiceIdBinary)), allowedToReply = recipient.isAllowedToReply!!, distributionLists = recipient.distributionListIds.map { DistributionId.from(it) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index 4a568d87d5..26961efd4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -1605,7 +1605,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa for (pinned: AccountRecord.PinnedConversation in record.proto.pinnedConversations) { val pinnedRecipient: Recipient? = if (pinned.contact != null) { - if (ServiceId.parseOrNull(pinned.contact!!.serviceId) != null) { + if (ServiceId.parseOrNull(pinned.contact!!.serviceId, pinned.contact!!.serviceIdBinary) != null) { Recipient.externalPush(pinned.contact!!.toSignalServiceAddress()) } else { Log.w(TAG, "Failed to parse serviceId!") diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index b0b9866a07..58a7d4dd16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -174,7 +174,8 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { Optional.of(new SecurityEventListener(context)), SignalExecutors.newCachedBoundedExecutor("signal-messages", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD, 1, 16, 30), RemoteConfig.maxEnvelopeSizeBytes(), - RemoteConfig::useMessageSendRestFallback); + RemoteConfig::useMessageSendRestFallback, + RemoteConfig.useBinaryId()); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 2715a1d5cd..c2a07b5e7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.AppForegroundObserver; +import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -144,7 +145,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { Uri updateUri = null; try { - DeviceContactsOutputStream out = new DeviceContactsOutputStream(writeDetails.outputStream); + DeviceContactsOutputStream out = new DeviceContactsOutputStream(writeDetails.outputStream, RemoteConfig.useBinaryId()); Recipient recipient = Recipient.resolved(recipientId); if (recipient.getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) { @@ -209,7 +210,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { Uri updateUri = null; try { - DeviceContactsOutputStream out = new DeviceContactsOutputStream(writeDetails.outputStream); + DeviceContactsOutputStream out = new DeviceContactsOutputStream(writeDetails.outputStream, RemoteConfig.useBinaryId()); List recipients = SignalDatabase.recipients().getRecipientsForMultiDeviceSync(); Map inboxPositions = SignalDatabase.threads().getInboxPositions(); Set archived = SignalDatabase.threads().getArchivedRecipients(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java index 1b5a1d8839..a51af58e1d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.RemoteConfig; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -72,7 +72,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DeviceContactsOutputStream out = new DeviceContactsOutputStream(baos); + DeviceContactsOutputStream out = new DeviceContactsOutputStream(baos, RemoteConfig.useBinaryId()); out.write(new DeviceContact(Optional.ofNullable(SignalStore.account().getAci()), Optional.ofNullable(SignalStore.account().getE164()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt index 7acf736236..f3ec4081ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt @@ -22,6 +22,7 @@ import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.Envelope +import org.whispersystems.signalservice.internal.util.Util import java.io.IOException import java.util.concurrent.TimeUnit import org.whispersystems.signalservice.api.crypto.protos.EnvelopeMetadata as EnvelopeMetadataProto @@ -127,8 +128,11 @@ class PushProcessMessageJob private constructor( } } getQueueName(RecipientId.from(groupId)) - } else if (result.content.syncMessage != null && result.content.syncMessage!!.sent != null && result.content.syncMessage!!.sent!!.destinationServiceId != null) { - getQueueName(RecipientId.from(ServiceId.parseOrThrow(result.content.syncMessage!!.sent!!.destinationServiceId!!))) + } else if (result.content.syncMessage != null && + result.content.syncMessage!!.sent != null && + Util.anyNotNull(result.content.syncMessage!!.sent!!.destinationServiceId, result.content.syncMessage!!.sent!!.destinationServiceIdBinary) + ) { + getQueueName(RecipientId.from(ServiceId.parseOrThrow(result.content.syncMessage!!.sent!!.destinationServiceId, result.content.syncMessage!!.sent!!.destinationServiceIdBinary))) } else { getQueueName(RecipientId.from(result.metadata.sourceServiceId)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt index 1a6e7982c8..cac927e098 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt @@ -104,12 +104,14 @@ import org.whispersystems.signalservice.api.payments.Money import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.util.Preconditions +import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.BodyRange import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.DataMessage import org.whispersystems.signalservice.internal.push.Envelope import org.whispersystems.signalservice.internal.push.GroupContextV2 import org.whispersystems.signalservice.internal.push.Preview +import org.whispersystems.signalservice.internal.util.Util import java.util.Optional import java.util.UUID import kotlin.time.Duration @@ -145,7 +147,7 @@ object DataMessageProcessor { groupV2 = message.groupV2!!, senderRecipient = senderRecipient, groupSecretParams = groupSecretParams, - serverGuid = envelope.serverGuid + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary) ) if (groupProcessResult == MessageContentProcessor.Gv2PreProcessResult.IGNORE) { @@ -304,7 +306,7 @@ object DataMessageProcessor { serverTimeMillis = envelope.serverTimestamp!!, receivedTimeMillis = System.currentTimeMillis(), isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), type = MessageType.END_SESSION ) @@ -362,7 +364,7 @@ object DataMessageProcessor { receivedTimeMillis = receivedTime, expiresIn = expiresIn.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary) ) val insertResult: InsertResult? = SignalDatabase.messages.insertMessageInbox(mediaMessage, -1).orNull() @@ -423,7 +425,7 @@ object DataMessageProcessor { return null } - val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!) + val authorServiceId: ServiceId = ACI.parseOrThrow(storyContext.authorAci, storyContext.authorAciBinary) val sentTimestamp = storyContext.sentTimestamp!! SignalDatabase.messages.beginTransaction() @@ -473,7 +475,7 @@ object DataMessageProcessor { body = emoji, groupId = groupId, quote = quoteModel, - serverGuid = envelope.serverGuid + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary) ) val insertResult: InsertResult? = SignalDatabase.messages.insertMessageInbox(mediaMessage, -1).orNull() @@ -517,7 +519,7 @@ object DataMessageProcessor { val emoji: String? = reaction.emoji val isRemove: Boolean = reaction.remove ?: false - val targetAuthorServiceId: ServiceId = ServiceId.parseOrThrow(reaction.targetAuthorAci!!) + val targetAuthorServiceId: ServiceId = ACI.parseOrThrow(reaction.targetAuthorAci, reaction.targetAuthorAciBinary) val targetSentTimestamp: Long = reaction.targetSentTimestamp!! if (targetAuthorServiceId.isUnknown) { @@ -635,7 +637,7 @@ object DataMessageProcessor { receivedTimeMillis = receivedTime, expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), type = if (isActivatePaymentsRequest) MessageType.ACTIVATE_PAYMENTS_REQUEST else MessageType.PAYMENTS_ACTIVATED ) @@ -686,7 +688,7 @@ object DataMessageProcessor { receivedTimeMillis = receivedTime, expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), type = MessageType.PAYMENTS_NOTIFICATION ) @@ -726,7 +728,7 @@ object DataMessageProcessor { log(envelope.timestamp!!, "Story reply.") val storyContext: DataMessage.StoryContext = message.storyContext!! - val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!) + val authorServiceId: ServiceId = ACI.parseOrThrow(storyContext.authorAci, storyContext.authorAciBinary) val sentTimestamp: Long = if (storyContext.sentTimestamp != null) { storyContext.sentTimestamp!! } else { @@ -786,7 +788,7 @@ object DataMessageProcessor { return null } - val bodyRanges: BodyRangeList? = message.bodyRanges.filter { it.mentionAci == null }.toList().toBodyRangeList() + val bodyRanges: BodyRangeList? = message.bodyRanges.filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toList().toBodyRangeList() val mediaMessage = IncomingMessage( type = MessageType.NORMAL, @@ -801,7 +803,7 @@ object DataMessageProcessor { groupId = groupId, quote = quoteModel, mentions = getMentions(message.bodyRanges), - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), messageRanges = bodyRanges ) @@ -866,7 +868,7 @@ object DataMessageProcessor { expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, body = Base64.encodeWithPadding(dbGiftBadge.encode()), - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), giftBadge = dbGiftBadge ) @@ -910,7 +912,7 @@ object DataMessageProcessor { val mentions: List = getMentions(message.bodyRanges.take(BODY_RANGE_PROCESSING_LIMIT)) val sticker: Attachment? = getStickerAttachment(envelope.timestamp!!, message) val attachments: List = message.attachments.toPointersWithinLimit() - val messageRanges: BodyRangeList? = if (message.bodyRanges.isNotEmpty()) message.bodyRanges.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { it.mentionAci == null }.toList().toBodyRangeList() else null + val messageRanges: BodyRangeList? = if (message.bodyRanges.isNotEmpty()) message.bodyRanges.asSequence().take(BODY_RANGE_PROCESSING_LIMIT).filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toList().toBodyRangeList() else null handlePossibleExpirationUpdate(envelope, metadata, senderRecipient, threadRecipient, groupId, message.expireTimerDuration, message.expireTimerVersion, receivedTime) @@ -930,7 +932,7 @@ object DataMessageProcessor { sharedContacts = contacts, linkPreviews = linkPreviews, mentions = mentions, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), messageRanges = messageRanges ) @@ -1006,7 +1008,7 @@ object DataMessageProcessor { groupId = groupId, expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary) ) val insertResult: InsertResult? = SignalDatabase.messages.insertMessageInbox(textMessage).orNull() @@ -1086,7 +1088,7 @@ object DataMessageProcessor { groupId = groupId, expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), poll = Poll( question = poll.question!!, allowMultipleVotes = poll.allowMultiple!!, @@ -1148,7 +1150,7 @@ object DataMessageProcessor { groupId = groupId, expiresIn = message.expireTimerDuration.inWholeMilliseconds, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), messageExtras = MessageExtras(pollTerminate = PollTerminate(poll.question, poll.messageId, targetSentTimestamp)) ) @@ -1244,9 +1246,9 @@ object DataMessageProcessor { fun getMentions(mentionBodyRanges: List): List { return mentionBodyRanges - .filter { it.mentionAci != null && it.start != null && it.length != null } + .filter { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && it.start != null && it.length != null } .mapNotNull { - val aci = ACI.parseOrNull(it.mentionAci) + val aci = ACI.parseOrNull(it.mentionAci, it.mentionAciBinary) if (aci != null && !aci.isUnknown) { val id = Recipient.externalPush(aci).id @@ -1279,7 +1281,7 @@ object DataMessageProcessor { return null } - val authorId = Recipient.externalPush(ServiceId.parseOrThrow(quote.authorAci!!)).id + val authorId = Recipient.externalPush(ACI.parseOrThrow(quote.authorAci, quote.authorAciBinary)).id var quotedMessage = SignalDatabase.messages.getMessageFor(quote.id!!, authorId) as? MmsMessageRecord if (quotedMessage != null && isSenderValid(quotedMessage, timestamp, senderRecipient, threadRecipient) && !quotedMessage.isRemoteDelete) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/EditMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/EditMessageProcessor.kt index bd93c3379d..173e8b80c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/EditMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/EditMessageProcessor.kt @@ -31,9 +31,11 @@ import org.thoughtcrime.securesms.util.MessageConstraintsUtil import org.thoughtcrime.securesms.util.hasAudio import org.thoughtcrime.securesms.util.hasSharedContact import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata +import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.DataMessage import org.whispersystems.signalservice.internal.push.Envelope +import org.whispersystems.signalservice.internal.util.Util object EditMessageProcessor { fun process( @@ -120,7 +122,7 @@ object EditMessageProcessor { message: DataMessage, targetMessage: MmsMessageRecord ): InsertResult? { - val messageRanges: BodyRangeList? = message.bodyRanges.filter { it.mentionAci == null }.toList().toBodyRangeList() + val messageRanges: BodyRangeList? = message.bodyRanges.filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toList().toBodyRangeList() val targetQuote = targetMessage.quote val quote: QuoteModel? = if (targetQuote != null && (message.quote != null || (targetMessage.parentStoryId != null && message.storyContext != null))) { QuoteModel( @@ -156,7 +158,7 @@ object EditMessageProcessor { sharedContacts = emptyList(), linkPreviews = DataMessageProcessor.getLinkPreviews(message.preview, message.body ?: "", false), mentions = DataMessageProcessor.getMentions(message.bodyRanges), - serverGuid = envelope.serverGuid, + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), messageRanges = messageRanges ) @@ -191,7 +193,7 @@ object EditMessageProcessor { parentStoryId = targetMessage.parentStoryId, expiresIn = targetMessage.expiresIn, isUnidentified = metadata.sealedSender, - serverGuid = envelope.serverGuid + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary) ) return SignalDatabase.messages.insertEditMessageInbox(textMessage, targetMessage).orNull() diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt index 6a6d7ba250..e9dc78a62f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -334,7 +334,7 @@ class IncomingMessageObserver( } private fun processReceipt(envelope: Envelope) { - val serviceId = ServiceId.parseOrNull(envelope.sourceServiceId) + val serviceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary) if (serviceId == null) { Log.w(TAG, "Invalid envelope sourceServiceId!") return diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt index 75e19e1ed2..968b5b5177 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.kt @@ -659,7 +659,7 @@ open class MessageContentProcessor(private val context: Context) { private fun handleIndividualRetryReceipt(requester: Recipient, messageLogEntry: MessageLogEntry?, envelope: Envelope, metadata: EnvelopeMetadata, decryptionErrorMessage: DecryptionErrorMessage) { var archivedSession = false - if (ServiceId.parseOrNull(envelope.destinationServiceId) is ServiceId.PNI) { + if (ServiceId.parseOrNull(envelope.destinationServiceId, envelope.destinationServiceIdBinary) is ServiceId.PNI) { warn(envelope.timestamp!!, "[RetryReceipt-I] Destination is our PNI. Ignoring.") return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index f3e1481ee1..00c778e737 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -67,9 +67,11 @@ import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.Envelope import org.whispersystems.signalservice.internal.push.PniSignatureMessage +import org.whispersystems.signalservice.internal.util.Util import java.util.Optional import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.DurationUnit @@ -100,7 +102,7 @@ object MessageDecryptor { val selfAci: ACI = SignalStore.account.requireAci() val selfPni: PNI = SignalStore.account.requirePni() - val destination: ServiceId? = ServiceId.parseOrNull(envelope.destinationServiceId) + val destination: ServiceId? = ServiceId.parseOrNull(envelope.destinationServiceId, envelope.destinationServiceIdBinary) if (destination == null) { Log.w(TAG, "${logPrefix(envelope)} Missing destination address! Invalid message, ignoring.") @@ -112,10 +114,10 @@ object MessageDecryptor { return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList()) } - if (destination == selfPni && envelope.sourceServiceId != null) { + if (destination == selfPni && Util.anyNotNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)) { Log.i(TAG, "${logPrefix(envelope)} Received a message at our PNI. Marking as needing a PNI signature.") - val sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId) + val sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary) if (sourceServiceId != null) { val sender = RecipientId.from(sourceServiceId) @@ -125,7 +127,7 @@ object MessageDecryptor { } } - if (destination == selfPni && envelope.sourceServiceId == null) { + if (destination == selfPni && Util.allAreNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)) { Log.w(TAG, "${logPrefix(envelope)} Got a sealed sender message to our PNI? Invalid message, ignoring.") return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList()) } @@ -164,7 +166,7 @@ object MessageDecryptor { return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) } - Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope in ${(endTimeNanos - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)} ms (GUID ${envelope.serverGuid}). Delivery latency: ${serverDeliveredTimestamp - envelope.serverTimestamp!!} ms, Urgent: ${envelope.urgent}") + Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope in ${(endTimeNanos - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)} ms (GUID ${UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary)}). Delivery latency: ${serverDeliveredTimestamp - envelope.serverTimestamp!!} ms, Urgent: ${envelope.urgent}") val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content, SignalStore.account.aci!!) @@ -289,7 +291,7 @@ object MessageDecryptor { followUpOperations: MutableList, protocolException: ProtocolException ): Result { - if (ServiceId.parseOrNull(envelope.destinationServiceId) == SignalStore.account.pni) { + if (ServiceId.parseOrNull(envelope.destinationServiceId, envelope.destinationServiceIdBinary) == SignalStore.account.pni) { Log.w(TAG, "${logPrefix(envelope)} Decryption error for message sent to our PNI! Ignoring.") return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) } @@ -475,7 +477,7 @@ object MessageDecryptor { } private fun logPrefix(envelope: Envelope): String { - return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId)?.logString() ?: "", envelope.sourceDevice) + return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)?.logString() ?: "", envelope.sourceDevice) } private fun logPrefix(envelope: Envelope, sender: ServiceId?): String { @@ -494,7 +496,7 @@ object MessageDecryptor { return if (exception.sender != null) { logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(exception.sender)?.logString() ?: "?", exception.senderDevice) } else { - logPrefix(envelope.timestamp!!, envelope.sourceServiceId, envelope.sourceDevice) + logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary).toString(), envelope.sourceDevice) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt index 665b1de48d..b41cc32c44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SignalServiceProtoUtil.kt @@ -145,14 +145,14 @@ object SignalServiceProtoUtil { } fun Sent.isUnidentified(serviceId: ServiceId?): Boolean { - return serviceId != null && unidentifiedStatus.firstOrNull { ServiceId.parseOrNull(it.destinationServiceId) == serviceId }?.unidentified ?: false + return serviceId != null && unidentifiedStatus.firstOrNull { ServiceId.parseOrNull(it.destinationServiceId, it.destinationServiceIdBinary) == serviceId }?.unidentified ?: false } val Sent.serviceIdsToUnidentifiedStatus: Map get() { return unidentifiedStatus .mapNotNull { status -> - val serviceId = ServiceId.parseOrNull(status.destinationServiceId) + val serviceId = ServiceId.parseOrNull(status.destinationServiceId, status.destinationServiceIdBinary) if (serviceId != null) { serviceId to (status.unidentified ?: false) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/StoryMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/StoryMessageProcessor.kt index 1dc4e45eb3..566bce0bf9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/StoryMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/StoryMessageProcessor.kt @@ -21,10 +21,12 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata +import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.Envelope import org.whispersystems.signalservice.internal.push.StoryMessage import org.whispersystems.signalservice.internal.push.TextAttachment +import org.whispersystems.signalservice.internal.util.Util object StoryMessageProcessor { @@ -75,8 +77,8 @@ object StoryMessageProcessor { body = "", isStoryEmbed = true ), - serverGuid = envelope.serverGuid, - messageRanges = storyMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList() + serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary), + messageRanges = storyMessage.bodyRanges.filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toBodyRangeList() ) insertResult = SignalDatabase.messages.insertMessageInbox(mediaMessage, -1).orNull() diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt index ac45fee863..2f60deb9d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt @@ -143,6 +143,7 @@ import java.util.concurrent.TimeUnit import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.seconds +import org.whispersystems.signalservice.internal.util.Util as Utils object SyncMessageProcessor { @@ -287,7 +288,7 @@ object SyncMessageProcessor { continue } - val pni = PNI.parsePrefixedOrNull(status.destinationServiceId) + val pni = PNI.parsePrefixedOrNull(status.destinationServiceId, status.destinationServiceIdBinary) if (pni == null) { continue } @@ -313,7 +314,7 @@ object SyncMessageProcessor { return if (message.message.hasGroupContext) { Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.message!!.groupV2!!.groupMasterKey)) } else { - Recipient.externalPush(SignalServiceAddress(ServiceId.parseOrThrow(message.destinationServiceId!!), message.destinationE164)) + Recipient.externalPush(SignalServiceAddress(ServiceId.parseOrThrow(message.destinationServiceId, message.destinationServiceIdBinary), message.destinationE164)) } } @@ -341,7 +342,7 @@ object SyncMessageProcessor { val toRecipient: Recipient = if (message.hasGroupContext) { Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.groupV2!!.groupMasterKey)) } else { - Recipient.externalPush(ServiceId.parseOrThrow(sent.destinationServiceId!!)) + Recipient.externalPush(ServiceId.parseOrThrow(sent.destinationServiceId, sent.destinationServiceIdBinary)) } if (message.isMediaMessage) { @@ -364,7 +365,7 @@ object SyncMessageProcessor { log(envelopeTimestamp, "Synchronize sent edit text message for message: ${targetMessage.id}") val body = message.body ?: "" - val bodyRanges = message.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList() + val bodyRanges = message.bodyRanges.filter { Utils.allAreNull(it.mentionAci, it.mentionAciBinary) }.toBodyRangeList() val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient) val isGroup = toRecipient.isGroup @@ -740,7 +741,7 @@ object SyncMessageProcessor { val reaction: DataMessage.Reaction? = dataMessage.reaction val parentStoryId: ParentStoryId - val authorServiceId: ServiceId = ServiceId.parseOrThrow(storyContext.authorAci!!) + val authorServiceId: ServiceId = ACI.parseOrThrow(storyContext.authorAci, storyContext.authorAciBinary) val sentTimestamp: Long = storyContext.sentTimestamp!! val recipient: Recipient = getSyncMessageDestination(sent) var quoteModel: QuoteModel? = null @@ -903,7 +904,7 @@ object SyncMessageProcessor { val dataMessage: DataMessage = sent.message!! val body = dataMessage.body ?: "" val expiresInMillis = dataMessage.expireTimerDuration.inWholeMilliseconds - val bodyRanges = dataMessage.bodyRanges.filter { it.mentionAci == null }.toBodyRangeList() + val bodyRanges = dataMessage.bodyRanges.filter { Utils.allAreNull(it.mentionAci, it.mentionAciBinary) }.toBodyRangeList() if (recipient.expiresInSeconds != dataMessage.expireTimerDuration.inWholeSeconds.toInt() || ((dataMessage.expireTimerVersion ?: -1) > recipient.expireTimerVersion)) { handleSynchronizeSentExpirationUpdate(sent, sideEffect = true) @@ -1017,7 +1018,7 @@ object SyncMessageProcessor { val records = viewedMessages .mapNotNull { message -> - val author = Recipient.externalPush(ServiceId.parseOrThrow(message.senderAci!!)).id + val author = Recipient.externalPush(ACI.parseOrThrow(message.senderAci, message.senderAciBinary)).id if (message.timestamp != null) { SignalDatabase.messages.getMessageFor(message.timestamp!!, author) } else { @@ -1049,7 +1050,7 @@ object SyncMessageProcessor { private fun handleSynchronizeViewOnceOpenMessage(context: Context, openMessage: ViewOnceOpen, envelopeTimestamp: Long, earlyMessageCacheEntry: EarlyMessageCacheEntry?) { log(envelopeTimestamp, "Handling a view-once open for message: " + openMessage.timestamp) - val author: RecipientId = Recipient.externalPush(ServiceId.parseOrThrow(openMessage.senderAci!!)).id + val author: RecipientId = Recipient.externalPush(ACI.parseOrThrow(openMessage.senderAci, openMessage.senderAciBinary)).id val timestamp: Long = if (openMessage.timestamp != null) { openMessage.timestamp!! } else { @@ -1122,7 +1123,7 @@ object SyncMessageProcessor { } private fun handleSynchronizeBlockedListMessage(blockMessage: Blocked, envelopeTimestamp: Long) { - val blockedAcis = blockMessage.acis.mapNotNull { ACI.parseOrNull(it) } + val blockedAcis = if (blockMessage.acisBinary.isNotEmpty()) { blockMessage.acisBinary.mapNotNull { ACI.parseOrNull(it) } } else blockMessage.acis.mapNotNull { ACI.parseOrNull(it) } val blockedE164s = blockMessage.numbers val blockedGroupIds = blockMessage.groupIds.map { it.toByteArray() } log(envelopeTimestamp, "Synchronize block message. Counts: (ACI: ${blockedAcis.size}, E164: ${blockedE164s.size}, Group: ${blockedGroupIds.size})") @@ -1144,8 +1145,8 @@ object SyncMessageProcessor { private fun handleSynchronizeMessageRequestResponse(response: MessageRequestResponse, envelopeTimestamp: Long) { log(envelopeTimestamp, "Synchronize message request response.") - val recipient: Recipient = if (response.threadAci != null) { - Recipient.externalPush(ServiceId.parseOrThrow(response.threadAci!!)) + val recipient: Recipient = if (Utils.anyNotNull(response.threadAci, response.threadAciBinary)) { + Recipient.externalPush(ACI.parseOrThrow(response.threadAci, response.threadAciBinary)) } else if (response.groupId != null) { val groupId: GroupId = GroupId.push(response.groupId!!) Recipient.externalPossiblyMigratedGroup(groupId) @@ -1858,6 +1859,7 @@ object SyncMessageProcessor { } private fun ConversationIdentifier.toRecipientId(): RecipientId? { + val threadServiceId = ServiceId.parseOrNull(this.threadServiceId, this.threadServiceIdBinary) return when { threadGroupId != null -> { try { @@ -1869,9 +1871,7 @@ object SyncMessageProcessor { } threadServiceId != null -> { - ServiceId.parseOrNull(threadServiceId)?.let { - SignalDatabase.recipients.getOrInsertFromServiceId(it) - } + SignalDatabase.recipients.getOrInsertFromServiceId(threadServiceId) } threadE164 != null -> { @@ -1885,8 +1885,8 @@ object SyncMessageProcessor { } private fun AddressableMessage.toSyncMessageId(envelopeTimestamp: Long): MessageTable.SyncMessageId? { - return if (this.sentTimestamp != null && (this.authorServiceId != null || this.authorE164 != null)) { - val serviceId = ServiceId.parseOrNull(this.authorServiceId) + return if (this.sentTimestamp != null && Utils.anyNotNull(this.authorServiceId, this.authorServiceIdBinary) || this.authorE164 != null) { + val serviceId = ServiceId.parseOrNull(this.authorServiceId, this.authorServiceIdBinary) val id = if (serviceId != null) { SignalDatabase.recipients.getOrInsertFromServiceId(serviceId) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/ChatFolderRecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/ChatFolderRecordProcessor.kt index b376ec66e4..53ae9503c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/ChatFolderRecordProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/ChatFolderRecordProcessor.kt @@ -99,7 +99,7 @@ class ChatFolderRecordProcessor : DefaultStorageRecordProcessor): Boolean { return recipients.any { recipient -> - recipient.contact != null && ServiceId.parseOrNull(recipient.contact!!.serviceId) == null + recipient.contact != null && ServiceId.parseOrNull(recipient.contact!!.serviceId, recipient.contact!!.serviceIdBinary) == null } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.kt index 686b7964d6..f56dfe36a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.kt @@ -235,6 +235,8 @@ class ContactRecordProcessor( pniSignatureVerified = remote.proto.pniSignatureVerified || local.proto.pniSignatureVerified note = remote.proto.note.nullIfBlank() ?: "" avatarColor = if (SignalStore.account.isPrimaryDevice) local.proto.avatarColor else remote.proto.avatarColor + aciBinary = local.proto.aciBinary.nullIfEmpty() ?: remote.proto.aciBinary + pniBinary = mergedPni?.toByteStringWithoutPrefix() ?: byteArrayOf().toByteString() }.build().toSignalContactRecord(StorageId.forContact(keyGenerator.generate())) val matchesRemote = doParamsMatch(remote, merged) @@ -265,9 +267,9 @@ class ContactRecordProcessor( override fun compare(lhs: SignalContactRecord, rhs: SignalContactRecord): Int { return if ( - (lhs.proto.signalAci != null && lhs.proto.aci == rhs.proto.aci) || + (lhs.proto.signalAci != null && lhs.proto.aci == rhs.proto.aci && lhs.proto.aciBinary == rhs.proto.aciBinary) || (lhs.proto.e164.isNotBlank() && lhs.proto.e164 == rhs.proto.e164) || - (lhs.proto.signalPni != null && lhs.proto.pni == rhs.proto.pni) + (lhs.proto.signalPni != null && lhs.proto.pni == rhs.proto.pni && lhs.proto.pniBinary == rhs.proto.pniBinary) ) { 0 } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/NotificationProfileRecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/NotificationProfileRecordProcessor.kt index e2190ae0a0..9cb64292d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/NotificationProfileRecordProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/NotificationProfileRecordProcessor.kt @@ -89,7 +89,7 @@ class NotificationProfileRecordProcessor : DefaultStorageRecordProcessor): Boolean { return recipients.any { recipient -> - recipient.contact != null && ServiceId.parseOrNull(recipient.contact!!.serviceId) == null + recipient.contact != null && ServiceId.parseOrNull(recipient.contact!!.serviceId, recipient.contact!!.serviceIdBinary) == null } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.kt index 1a0a45d25a..f180838efd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.kt @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.storage.IAPSubscriptionId @@ -133,7 +134,8 @@ object StorageSyncModels { AccountRecord.PinnedConversation( contact = AccountRecord.PinnedConversation.Contact( serviceId = settings.serviceId?.toString() ?: "", - e164 = settings.e164 ?: "" + e164 = settings.e164 ?: "", + serviceIdBinary = settings.serviceId?.toByteString().takeIf { RemoteConfig.useBinaryId } ?: ByteString.EMPTY ) ) } @@ -210,6 +212,8 @@ object StorageSyncModels { nickname = recipient.nickname.takeUnless { it.isEmpty }?.let { ContactRecord.Name(given = it.givenName, family = it.familyName) } note = recipient.note ?: "" avatarColor = localToRemoteAvatarColor(recipient.avatarColor) + aciBinary = recipient.aci?.toByteString()?.takeIf { RemoteConfig.useBinaryId } ?: ByteString.EMPTY + pniBinary = recipient.pni?.toByteStringWithoutPrefix()?.takeIf { RemoteConfig.useBinaryId } ?: ByteString.EMPTY }.build().toSignalContactRecord(StorageId.forContact(rawStorageId)) } @@ -294,6 +298,14 @@ object StorageSyncModels { .map { Recipient.resolved(it) } .filter { it.hasServiceId } .map { it.requireServiceId().toString() } + recipientServiceIdsBinary = if (RemoteConfig.useBinaryId) { + record.getMembersToSync() + .map { Recipient.resolved(it) } + .filter { it.hasServiceId } + .map { it.requireServiceId().toByteString() } + } else { + emptyList() + } allowsReplies = record.allowsReplies isBlockList = record.privacyMode.isBlockList }.build().toSignalStoryDistributionListRecord(StorageId.forStoryDistributionList(rawStorageId)) @@ -493,7 +505,13 @@ object StorageSyncModels { } else { when (recipient.recipientType) { RecipientType.INDIVIDUAL -> { - RemoteRecipient(contact = RemoteRecipient.Contact(serviceId = recipient.serviceId?.toString() ?: "", e164 = recipient.e164 ?: "")) + RemoteRecipient( + contact = RemoteRecipient.Contact( + serviceId = recipient.serviceId?.toString() ?: "", + e164 = recipient.e164 ?: "", + serviceIdBinary = recipient.serviceId?.toByteString().takeIf { RemoteConfig.useBinaryId } ?: ByteString.EMPTY + ) + ) } RecipientType.GV1 -> { RemoteRecipient(legacyGroupId = recipient.groupId!!.requireV1().decodedId.toByteString()) @@ -509,7 +527,7 @@ object StorageSyncModels { fun remoteToLocalRecipient(remoteRecipient: RemoteRecipient): Recipient? { return if (remoteRecipient.contact != null) { - val serviceId = ServiceId.parseOrNull(remoteRecipient.contact!!.serviceId) + val serviceId = ServiceId.parseOrNull(remoteRecipient.contact!!.serviceId, remoteRecipient.contact!!.serviceIdBinary) val e164 = remoteRecipient.contact!!.e164 Recipient.externalPush(SignalServiceAddress(serviceId, e164)) } else if (remoteRecipient.legacyGroupId != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java index 5844fb97ae..ca994abd29 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java @@ -14,6 +14,7 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import org.whispersystems.signalservice.api.storage.StorageId; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.storage.protos.ContactRecord; import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; @@ -181,8 +182,8 @@ public final class StorageSyncValidations { if (insert.getProto().contact != null) { ContactRecord contact = insert.getProto().contact; - if (self.requireAci().equals(ServiceId.ACI.parseOrNull(contact.aci)) || - (self.getPni().isPresent() && self.requirePni().equals(ServiceId.PNI.parseOrNull(contact.pni))) || + if (self.requireAci().equals(ServiceId.ACI.parseOrNull(contact.aci, contact.aciBinary)) || + (self.getPni().isPresent() && self.requirePni().equals(ServiceId.PNI.parseOrNull(contact.pni, contact.pniBinary))) || (self.getE164().isPresent() && self.requireE164().equals(contact.e164))) { throw new SelfAddedAsContactError(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StoryDistributionListRecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/StoryDistributionListRecordProcessor.kt index 52988e1d6e..774335d5bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StoryDistributionListRecordProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StoryDistributionListRecordProcessor.kt @@ -99,6 +99,7 @@ class StoryDistributionListRecordProcessor : DefaultStorageRecordProcessor eventListener, ExecutorService executor, long maxEnvelopeSize, - BooleanSupplier useRestFallback) + BooleanSupplier useRestFallback, + boolean useBinaryId) { CredentialsProvider credentialsProvider = pushServiceSocket.getCredentialsProvider(); @@ -212,6 +214,7 @@ public class SignalServiceMessageSender { this.scheduler = Schedulers.from(executor, false, false); this.keysApi = keysApi; this.useRestFallback = useRestFallback; + this.useBinaryId = useBinaryId; } /** @@ -1057,6 +1060,7 @@ public class SignalServiceMessageSender { .id(message.getQuote().get().getId()) .text(message.getQuote().get().getText()) .authorAci(message.getQuote().get().getAuthor().toString()) + .authorAciBinary(useBinaryId ? message.getQuote().get().getAuthor().toByteString() : null) .type(message.getQuote().get().getType().getProtoType()); List mentions = message.getQuote().get().getMentions(); @@ -1164,7 +1168,8 @@ public class SignalServiceMessageSender { .emoji(message.getReaction().get().getEmoji()) .remove(message.getReaction().get().isRemove()) .targetSentTimestamp(message.getReaction().get().getTargetSentTimestamp()) - .targetAuthorAci(message.getReaction().get().getTargetAuthor().toString()); + .targetAuthorAci(message.getReaction().get().getTargetAuthor().toString()) + .targetAuthorAciBinary(useBinaryId ? message.getReaction().get().getTargetAuthor().toByteString() : null); builder.reaction(reactionBuilder.build()); builder.requiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.REACTIONS.getValue(), builder.requiredProtocolVersion)); @@ -1209,6 +1214,7 @@ public class SignalServiceMessageSender { builder.storyContext(new DataMessage.StoryContext.Builder() .authorAci(storyContext.getAuthorServiceId().toString()) + .authorAciBinary(useBinaryId ? storyContext.getAuthorServiceId().toByteString() : null) .sentTimestamp(storyContext.getSentTimestamp()) .build()); } @@ -1399,6 +1405,7 @@ public class SignalServiceMessageSender { unidentifiedDeliveryStatuses.add(new SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder() .destinationServiceId(result.getAddress().getServiceId().toString()) + .destinationServiceIdBinary(useBinaryId ? result.getAddress().getServiceId().toByteString() : null) .unidentified(false) .destinationPniIdentityKey(identity) .build()); @@ -1408,6 +1415,7 @@ public class SignalServiceMessageSender { if (recipient.isPresent()) { sentMessage.destinationServiceId(recipient.get().getServiceId().toString()); + sentMessage.destinationServiceIdBinary(useBinaryId ? recipient.get().getServiceId().toByteString() : null); if (recipient.get().getNumber().isPresent()) { sentMessage.destinationE164(recipient.get().getNumber().get()); } @@ -1447,6 +1455,7 @@ public class SignalServiceMessageSender { return new SyncMessage.Sent.StoryMessageRecipient.Builder() .distributionListIds(storyMessageRecipient.getDistributionListIds()) .destinationServiceId(storyMessageRecipient.getSignalServiceAddress().getIdentifier()) + .destinationServiceIdBinary(useBinaryId ? storyMessageRecipient.getSignalServiceAddress().getServiceId().toByteString() : null) .isAllowedToReply(storyMessageRecipient.isAllowedToReply()) .build(); } @@ -1460,6 +1469,7 @@ public class SignalServiceMessageSender { .map(readMessage -> new SyncMessage.Read.Builder() .timestamp(readMessage.getTimestamp()) .senderAci(readMessage.getSenderAci().toString()) + .senderAciBinary(useBinaryId ? readMessage.getSenderAci().toByteString() : null) .build()) .collect(Collectors.toList()) ); @@ -1476,6 +1486,7 @@ public class SignalServiceMessageSender { .map(readMessage -> new SyncMessage.Viewed.Builder() .timestamp(readMessage.getTimestamp()) .senderAci(readMessage.getSender().toString()) + .senderAciBinary(useBinaryId ? readMessage.getSender().toByteString() : null) .build()) .collect(Collectors.toList()) ); @@ -1490,6 +1501,7 @@ public class SignalServiceMessageSender { builder.viewOnceOpen(new SyncMessage.ViewOnceOpen.Builder() .timestamp(readMessage.getTimestamp()) .senderAci(readMessage.getSender().toString()) + .senderAciBinary(useBinaryId ? readMessage.getSender().toByteString() : null) .build()); return container.syncMessage(builder.build()).build(); @@ -1501,6 +1513,7 @@ public class SignalServiceMessageSender { SyncMessage.Blocked.Builder blockedMessage = new SyncMessage.Blocked.Builder(); blockedMessage.acis(blocked.individuals.stream().filter(a -> a.getAci() != null).map(a -> a.getAci().toString()).collect(Collectors.toList())); + blockedMessage.acisBinary(useBinaryId ? blocked.individuals.stream().filter(a -> a.getAci() != null).map(a -> a.getAci().toByteString()).collect(Collectors.toList()) : Collections.emptyList()); blockedMessage.numbers(blocked.individuals.stream().filter(a -> a.getE164() != null).map(a -> a.getE164()).collect(Collectors.toList())); blockedMessage.groupIds(blocked.groupIds.stream().map(ByteString::of).collect(Collectors.toList())); @@ -1600,6 +1613,7 @@ public class SignalServiceMessageSender { if (message.getPerson().isPresent()) { responseMessage.threadAci(message.getPerson().get().toString()); + responseMessage.threadAciBinary(useBinaryId ? message.getPerson().get().toByteString() : null); } switch (message.getType()) { @@ -1697,6 +1711,7 @@ public class SignalServiceMessageSender { verifiedMessageBuilder.nullMessage(ByteString.of(nullMessage)); verifiedMessageBuilder.identityKey(ByteString.of(verifiedMessage.getIdentityKey().serialize())); verifiedMessageBuilder.destinationAci(verifiedMessage.getDestination().getServiceId().toString()); + verifiedMessageBuilder.destinationAciBinary(useBinaryId ? verifiedMessage.getDestination().getServiceId().toByteString() : null); switch (verifiedMessage.getVerified()) { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index f24a057201..819198bc83 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -49,6 +49,7 @@ import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.Content; import org.whispersystems.signalservice.internal.push.Envelope; import org.whispersystems.signalservice.internal.push.OutgoingPushMessage; @@ -171,32 +172,36 @@ public class SignalServiceCipher { ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException, InvalidMessageStructureException { + ServiceId sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary); try { + ServiceId destinationServiceId = ServiceId.parseOrNull(envelope.destinationServiceId, envelope.destinationServiceIdBinary); + String destinationStr = (destinationServiceId != null) ? destinationServiceId.toString() : ""; + String serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary); byte[] paddedMessage; SignalServiceMetadata metadata; - if (envelope.sourceServiceId == null && envelope.type != Envelope.Type.UNIDENTIFIED_SENDER) { + if (sourceServiceId == null && envelope.type != Envelope.Type.UNIDENTIFIED_SENDER) { throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!"); } if (envelope.type == Envelope.Type.PREKEY_BUNDLE) { - SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.sourceServiceId, envelope.sourceDevice); + SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice); SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.content.toByteArray())); - metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr); signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress)); } else if (envelope.type == Envelope.Type.CIPHERTEXT) { - SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.sourceServiceId, envelope.sourceDevice); + SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice); SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.content.toByteArray())); - metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr); } else if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) { paddedMessage = new PlaintextContent(envelope.content.toByteArray()).getBody(); - metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr); } else if (envelope.type == Envelope.Type.UNIDENTIFIED_SENDER) { SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId)); DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.content.toByteArray(), envelope.serverTimestamp); @@ -204,7 +209,7 @@ public class SignalServiceCipher { Optional groupId = result.getGroupId(); boolean needsReceipt = true; - if (envelope.sourceServiceId != null) { + if (sourceServiceId != null) { Log.w(TAG, "[" + envelope.timestamp + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false"); needsReceipt = false; } @@ -214,7 +219,7 @@ public class SignalServiceCipher { } paddedMessage = result.getPaddedMessage(); - metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, envelope.serverGuid, groupId, envelope.destinationServiceId); + metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, serverGuid, groupId, destinationStr); } else { throw new InvalidMetadataMessageException("Unknown type: " + envelope.type); } @@ -224,26 +229,26 @@ public class SignalServiceCipher { return new Plaintext(metadata, data); } catch (DuplicateMessageException e) { - throw new ProtocolDuplicateMessageException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolDuplicateMessageException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (LegacyMessageException e) { - throw new ProtocolLegacyMessageException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolLegacyMessageException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (InvalidMessageException e) { - throw new ProtocolInvalidMessageException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolInvalidMessageException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (InvalidKeyIdException e) { - throw new ProtocolInvalidKeyIdException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolInvalidKeyIdException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (InvalidKeyException e) { - throw new ProtocolInvalidKeyException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolInvalidKeyException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (UntrustedIdentityException e) { - throw new ProtocolUntrustedIdentityException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolUntrustedIdentityException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (InvalidVersionException e) { - throw new ProtocolInvalidVersionException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolInvalidVersionException(e, sourceServiceId.toString(), envelope.sourceDevice); } catch (NoSessionException e) { - throw new ProtocolNoSessionException(e, envelope.sourceServiceId, envelope.sourceDevice); + throw new ProtocolNoSessionException(e, sourceServiceId.toString(), envelope.sourceDevice); } } private static SignalServiceAddress getSourceAddress(Envelope envelope) { - return new SignalServiceAddress(ServiceId.parseOrNull(envelope.sourceServiceId)); + return new SignalServiceAddress(ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)); } private static class Plaintext { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt index 0bf15ae046..c479591c0d 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -19,6 +19,7 @@ import org.whispersystems.signalservice.internal.push.ReceiptMessage import org.whispersystems.signalservice.internal.push.StoryMessage import org.whispersystems.signalservice.internal.push.SyncMessage import org.whispersystems.signalservice.internal.push.TypingMessage +import org.whispersystems.signalservice.internal.util.Util /** * Validates an [Envelope] and its decrypted [Content] so that we know the message can be processed safely @@ -36,7 +37,8 @@ object EnvelopeContentValidator { validatePlaintextContent(content)?.let { return it } } - if (envelope.sourceServiceId != null && envelope.sourceServiceId.isInvalidServiceId()) { + val sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary) + if (Util.anyNotNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary) && sourceServiceId.isNullOrInvalidServiceId()) { return Result.Invalid("Envelope had an invalid sourceServiceId!") } @@ -83,7 +85,7 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}") } - if (dataMessage.quote != null && dataMessage.quote.authorAci.isNullOrInvalidAci()) { + if (dataMessage.quote != null && ACI.parseOrNull(dataMessage.quote.authorAci, dataMessage.quote.authorAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[DataMessage] Invalid ACI on quote!") } @@ -95,7 +97,7 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!") } - if (dataMessage.bodyRanges.any { it.mentionAci != null && it.mentionAci.isNullOrInvalidAci() }) { + if (dataMessage.bodyRanges.any { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && ACI.parseOrNull(it.mentionAci, it.mentionAciBinary).isNullOrInvalidServiceId() }) { return Result.Invalid("[DataMessage] Invalid ACI on body range!") } @@ -108,7 +110,7 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!") } - if (dataMessage.reaction.targetAuthorAci.isNullOrInvalidAci()) { + if (ACI.parseOrNull(dataMessage.reaction.targetAuthorAci, dataMessage.reaction.targetAuthorAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.reaction!") } } @@ -117,7 +119,7 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!") } - if (dataMessage.storyContext != null && dataMessage.storyContext.authorAci.isNullOrInvalidAci()) { + if (dataMessage.storyContext != null && ACI.parseOrNull(dataMessage.storyContext.authorAci, dataMessage.storyContext.authorAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.storyContext!") } @@ -166,14 +168,14 @@ object EnvelopeContentValidator { private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage, localAci: ACI): Result { // Source serviceId was already determined to be a valid serviceId in general - val sourceServiceId = ServiceId.parseOrThrow(envelope.sourceServiceId!!) + val sourceServiceId = ServiceId.parseOrThrow(envelope.sourceServiceId, envelope.sourceServiceIdBinary) if (sourceServiceId != localAci) { return Result.Invalid("[SyncMessage] Source was not our own account!") } if (syncMessage.sent != null) { - val validAddress = syncMessage.sent.destinationServiceId.isValidServiceId() + val validAddress = ServiceId.parseOrNull(syncMessage.sent.destinationServiceId, syncMessage.sent.destinationServiceIdBinary) != null val hasDataGroup = syncMessage.sent.message?.groupV2 != null val hasStoryGroup = syncMessage.sent.storyMessage?.group != null val hasStoryManifest = syncMessage.sent.storyMessageRecipients.isNotEmpty() @@ -196,7 +198,7 @@ object EnvelopeContentValidator { } for (status in syncMessage.sent.unidentifiedStatus) { - if (status.destinationServiceId.isNullOrInvalidServiceId()) { + if (ServiceId.parseOrNull(status.destinationServiceId, status.destinationServiceIdBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[SyncMessage] Invalid ServiceId in SyncMessage.sent.unidentifiedStatusList!") } } @@ -214,19 +216,19 @@ object EnvelopeContentValidator { } } - if (syncMessage.read.any { it.senderAci.isNullOrInvalidAci() }) { + if (syncMessage.read.any { ACI.parseOrNull(it.senderAci, it.senderAciBinary).isNullOrInvalidServiceId() }) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.readList!") } - if (syncMessage.viewed.any { it.senderAci.isNullOrInvalidAci() }) { + if (syncMessage.viewed.any { ACI.parseOrNull(it.senderAci, it.senderAciBinary).isNullOrInvalidServiceId() }) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewList!") } - if (syncMessage.viewOnceOpen != null && syncMessage.viewOnceOpen.senderAci.isNullOrInvalidAci()) { + if (syncMessage.viewOnceOpen != null && ACI.parseOrNull(syncMessage.viewOnceOpen.senderAci, syncMessage.viewOnceOpen.senderAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewOnceOpen!") } - if (syncMessage.verified != null && syncMessage.verified.destinationAci.isNullOrInvalidAci()) { + if (syncMessage.verified != null && ACI.parseOrNull(syncMessage.verified.destinationAci, syncMessage.verified.destinationAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.verified!") } @@ -234,11 +236,11 @@ object EnvelopeContentValidator { return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!") } - if (syncMessage.blocked != null && syncMessage.blocked.acis.any { it.isNullOrInvalidAci() }) { + if (syncMessage.blocked != null && syncMessage.blocked.acis.any { it.isNullOrInvalidAci() } && syncMessage.blocked.acisBinary.any { it.isNullOrInvalidAci() }) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.blocked!") } - if (syncMessage.messageRequestResponse != null && syncMessage.messageRequestResponse.groupId == null && syncMessage.messageRequestResponse.threadAci.isNullOrInvalidAci()) { + if (syncMessage.messageRequestResponse != null && syncMessage.messageRequestResponse.groupId == null && ACI.parseOrNull(syncMessage.messageRequestResponse.threadAci, syncMessage.messageRequestResponse.threadAciBinary).isNullOrInvalidServiceId()) { return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.messageRequestResponse!") } @@ -329,7 +331,7 @@ object EnvelopeContentValidator { return Result.Invalid("[EditMessage] Invalid AttachmentPointer on DataMessage.previewList.image!") } - if (dataMessage.bodyRanges.any { it.mentionAci != null && it.mentionAci.isNullOrInvalidAci() }) { + if (dataMessage.bodyRanges.any { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && ACI.parseOrNull(it.mentionAci, it.mentionAciBinary).isNullOrInvalidServiceId() }) { return Result.Invalid("[EditMessage] Invalid UUID on body range!") } @@ -362,11 +364,6 @@ object EnvelopeContentValidator { return parsed == null || parsed.isUnknown } - private fun String.isInvalidServiceId(): Boolean { - val parsed = ServiceId.parseOrNull(this) - return parsed == null || parsed.isUnknown - } - private fun String?.isNullOrInvalidAci(): Boolean { val parsed = ACI.parseOrNull(this) return parsed == null || parsed.isUnknown @@ -382,6 +379,10 @@ object EnvelopeContentValidator { return parsed == null || parsed.isUnknown } + private fun ServiceId?.isNullOrInvalidServiceId(): Boolean { + return this == null || this.isUnknown + } + private fun Content?.meetsStoryFlagCriteria(): Boolean { return when { this == null -> false diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java index 462654b8d7..3d0ce24b78 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java @@ -16,6 +16,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.ContactDetails; import org.whispersystems.signalservice.internal.util.Util; @@ -42,11 +43,11 @@ public class DeviceContactsInputStream extends ChunkedInputStream { ContactDetails details = ContactDetails.ADAPTER.decode(detailsSerialized); - if (!SignalServiceAddress.isValidAddress(details.aci, details.number)) { + if (ACI.parseOrNull(details.aci, details.aciBinary) == null) { throw new IOException("Missing contact address!"); } - Optional aci = Optional.ofNullable(ACI.parseOrNull(details.aci)); + Optional aci = Optional.ofNullable(ACI.parseOrNull(details.aci, details.aciBinary)); Optional e164 = Optional.ofNullable(details.number); Optional name = Optional.ofNullable(details.name); Optional avatar = Optional.empty(); diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsOutputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsOutputStream.java index 03d5091788..99ecb285fe 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsOutputStream.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsOutputStream.java @@ -16,8 +16,11 @@ import okio.ByteString; public class DeviceContactsOutputStream extends ChunkedOutputStream { - public DeviceContactsOutputStream(OutputStream out) { + private final boolean useBinaryId; + + public DeviceContactsOutputStream(OutputStream out, boolean useBinaryId) { super(out); + this.useBinaryId = useBinaryId; } public void write(DeviceContact contact) throws IOException { @@ -40,6 +43,7 @@ public class DeviceContactsOutputStream extends ChunkedOutputStream { if (contact.getAci().isPresent()) { contactDetails.aci(contact.getAci().get().toString()); + contactDetails.aciBinary(useBinaryId ? contact.getAci().get().toByteString() : null); } if (contact.getE164().isPresent()) { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt index aa6f098a6f..0b1ca31413 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/push/ServiceId.kt @@ -86,7 +86,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { /** Parses a ServiceId serialized as a string. Crashes if the ServiceId is invalid. */ @JvmStatic @Throws(IllegalArgumentException::class) - fun parseOrThrow(raw: String): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!") + fun parseOrThrow(raw: String?): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!") /** Parses a ServiceId serialized as a byte array. Crashes if the ServiceId is invalid. */ @JvmStatic @@ -104,6 +104,19 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { fun parseOrUnknown(bytes: ByteString): ServiceId { return parseOrNull(bytes) ?: ACI.UNKNOWN } + + /** Parses a ServiceId serialized as either a byteString or string, with preference to the byteString if available. Returns null if invalid. */ + @JvmStatic + fun parseOrNull(raw: String?, bytes: ByteString?): ServiceId? { + return parseOrNull(bytes) ?: parseOrNull(raw) + } + + /** Parses a ServiceId serialized as either a byteString or string, with preference to the byteString if available. Throws if invalid. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String?, bytes: ByteString?): ServiceId { + return parseOrNull(bytes) ?: parseOrThrow(raw) + } } val rawUuid: UUID = libSignalServiceId.rawUUID @@ -144,7 +157,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { fun parseOrNull(raw: ByteArray?): ACI? = ServiceId.parseOrNull(raw).let { if (it is ACI) it else null } @JvmStatic - fun parseOrNull(bytes: ByteString): ACI? = parseOrNull(bytes.toByteArray()) + fun parseOrNull(bytes: ByteString?): ACI? = parseOrNull(bytes?.toByteArray()) @JvmStatic @Throws(IllegalArgumentException::class) @@ -163,6 +176,19 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { @JvmStatic fun parseOrUnknown(raw: String?): ACI = parseOrNull(raw) ?: UNKNOWN + + /** Parses either a byteString or string as an ACI, with preference to the byteString if available. Returns null if invalid or missing. */ + @JvmStatic + fun parseOrNull(raw: String?, bytes: ByteString?): ACI? { + return parseOrNull(bytes) ?: parseOrNull(raw) + } + + /** Parses either a byteString or string as an ACI, with preference to the byteString if available. Throws if invalid or missing. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String?, bytes: ByteString?): ACI { + return parseOrNull(bytes) ?: parseOrThrow(raw) + } } override fun toString(): String = super.toString() @@ -212,7 +238,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { /** Parses a [ByteString] as a PNI, regardless if the `PNI:` prefix is present or not. Only use this if you are certain that what you're reading is a PNI. */ @JvmStatic - fun parseOrNull(bytes: ByteString): PNI? = parseOrNull(bytes.toByteArray()) + fun parseOrNull(bytes: ByteString?): PNI? = parseOrNull(bytes?.toByteArray()) /** Parses a string as a PNI, regardless if the `PNI:` prefix is present or not. Only use this if you are certain that what you're reading is a PNI. */ @JvmStatic @@ -231,6 +257,24 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) { /** Parses a string as a PNI, expecting that the value has a `PNI:` prefix. If it does not have the prefix (or is otherwise invalid), this will return null. */ fun parsePrefixedOrNull(raw: String?): PNI? = ServiceId.parseOrNull(raw).let { if (it is PNI) it else null } + + /** Parses either a byteString or string as a PNI, with preference to the byteString. Expecting that the value has a `PNI:` prefix. If it does not have the prefix (or is otherwise invalid), this will return null. */ + fun parsePrefixedOrNull(raw: String?, bytes: ByteString?): PNI? { + return parseOrNull(bytes).let { if (it is PNI) it else null } ?: parsePrefixedOrNull(raw) + } + + /** Parses either a byteString or string as a PNI, with preference to the byteString. Only use this if you are certain what you're reading is a PNI. Returns null if invalid. */ + @JvmStatic + fun parseOrNull(raw: String?, bytes: ByteString?): PNI? { + return parseOrNull(bytes) ?: parseOrNull(raw) + } + + /** Parses either a byteString or string as a PNI, with preference to the byteString. Only use this if you are certain what you're reading is a PNI. Throws if missing or invalid. */ + @JvmStatic + @Throws(IllegalArgumentException::class) + fun parseOrThrow(raw: String?, bytes: ByteString?): PNI { + return parseOrNull(bytes) ?: parseOrThrow(raw) + } } override fun toString(): String = super.toString() diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/AccountRecordExtensions.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/AccountRecordExtensions.kt index 5134f1bb18..6a5f4fadd9 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/AccountRecordExtensions.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/AccountRecordExtensions.kt @@ -8,7 +8,6 @@ package org.whispersystems.signalservice.api.storage import okio.ByteString import okio.ByteString.Companion.toByteString import org.signal.core.util.isNotEmpty -import org.signal.core.util.isNotNullOrBlank import org.whispersystems.signalservice.api.payments.PaymentsConstants import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.SignalServiceAddress @@ -58,6 +57,6 @@ fun AccountRecord.Builder.safeSetBackupsSubscriber(subscriberId: ByteString, iap } fun AccountRecord.PinnedConversation.Contact.toSignalServiceAddress(): SignalServiceAddress { - val serviceId = ServiceId.parseOrNull(this.serviceId) + val serviceId = ServiceId.parseOrNull(this.serviceId, this.serviceIdBinary) return SignalServiceAddress(serviceId, this.e164) } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ContactRecordExtensions.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ContactRecordExtensions.kt index 88329f97d0..591b932a27 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ContactRecordExtensions.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/ContactRecordExtensions.kt @@ -9,7 +9,7 @@ import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.internal.storage.protos.ContactRecord val ContactRecord.signalAci: ServiceId.ACI? - get() = ServiceId.ACI.parseOrNull(this.aci) + get() = ServiceId.ACI.parseOrNull(this.aci, this.aciBinary) val ContactRecord.signalPni: ServiceId.PNI? - get() = ServiceId.PNI.parseOrNull(this.pni) + get() = ServiceId.PNI.parseOrNull(this.pni, this.pniBinary) diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/StoryDistributionListRecordExtensions.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/StoryDistributionListRecordExtensions.kt index baa1764791..68db9b31ec 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/StoryDistributionListRecordExtensions.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/storage/StoryDistributionListRecordExtensions.kt @@ -11,7 +11,10 @@ import org.whispersystems.signalservice.internal.storage.protos.StoryDistributio val StoryDistributionListRecord.recipientServiceAddresses: List get() { - return this.recipientServiceIds - .mapNotNull { ServiceId.parseOrNull(it) } - .map { SignalServiceAddress(it) } + val serviceIds = if (this.recipientServiceIdsBinary.isNotEmpty()) { + this.recipientServiceIdsBinary.mapNotNull { ServiceId.parseOrNull(it) } + } else { + this.recipientServiceIds.mapNotNull { ServiceId.parseOrNull(it) } + } + return serviceIds.map { SignalServiceAddress(it) } } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/util/UuidUtil.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/util/UuidUtil.java index 562117a9cb..89ba3d4839 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/util/UuidUtil.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/util/UuidUtil.java @@ -78,6 +78,11 @@ public final class UuidUtil { return parseOrNull(bytes.toByteArray()); } + public static @Nullable String getStringUUID(@Nullable String stringId, @Nullable ByteString bytes) { + UUID uuid = parseOrNull(bytes); + return (uuid != null) ? uuid.toString() : stringId; + } + public static UUID fromByteStringOrUnknown(ByteString bytes) { UUID uuid = fromByteStringOrNull(bytes); return uuid != null ? uuid : UNKNOWN_UUID; @@ -88,7 +93,7 @@ public final class UuidUtil { } public static UUID parseOrNull(ByteString byteString) { - return parseOrNull(byteString.toByteArray()); + return byteString != null ? parseOrNull(byteString.toByteArray()): null; } public static List fromByteStrings(Collection byteStringCollection) { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/Util.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/Util.java index b14eb2e014..e50bb3749c 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/Util.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/Util.java @@ -164,4 +164,22 @@ public class Util { return defaultValue; } } + + public static boolean anyNotNull(Object... values) { + for (Object value : values) { + if (value != null) { + return true; + } + } + return false; + } + + public static boolean allAreNull(Object... values) { + for (Object value : values) { + if (value != null) { + return false; + } + } + return true; + } } diff --git a/libsignal-service/src/main/protowire/SignalService.proto b/libsignal-service/src/main/protowire/SignalService.proto index 249c4ebb65..8c0bc517a9 100644 --- a/libsignal-service/src/main/protowire/SignalService.proto +++ b/libsignal-service/src/main/protowire/SignalService.proto @@ -40,7 +40,11 @@ message Envelope { optional bool story = 16; // indicates that the content is a story. optional bytes report_spam_token = 17; // token sent when reporting spam reserved 18; // internal server use - // next: 19 + optional bytes sourceServiceIdBinary = 19; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) + optional bytes destinationServiceIdBinary = 20; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) + optional bytes serverGuidBinary = 21; // 16-byte UUID + optional bytes updatedPniBinary = 22; // 16-byte UUID + // next: 22 } message Content { @@ -193,6 +197,7 @@ message DataMessage { repeated QuotedAttachment attachments = 4; repeated BodyRange bodyRanges = 6; optional Type type = 7; + optional bytes authorAciBinary = 8; // 16-byte UUID } message Contact { @@ -277,6 +282,7 @@ message DataMessage { reserved /* targetAuthorE164 */ 3; optional string targetAuthorAci = 4; optional uint64 targetSentTimestamp = 5; + optional bytes targetAuthorAciBinary = 6; // 16-byte UUID } message Delete { @@ -290,6 +296,7 @@ message DataMessage { message StoryContext { optional string authorAci = 1; optional uint64 sentTimestamp = 2; + optional bytes authorAciBinary = 3; // 16-byte UUID } enum ProtocolVersion { @@ -442,6 +449,7 @@ message Verified { optional bytes identityKey = 2; optional State state = 3; optional bytes nullMessage = 4; + optional bytes destinationAciBinary = 6; // 16-byte UUID } message SyncMessage { @@ -452,6 +460,7 @@ message SyncMessage { optional bool unidentified = 2; reserved /*destinationPni */ 4; optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations + optional bytes destinationServiceIdBinary = 6; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } message StoryMessageRecipient { @@ -459,6 +468,7 @@ message SyncMessage { repeated string distributionListIds = 2; optional bool isAllowedToReply = 3; reserved /*destinationPni */ 4; + optional bytes destinationServiceIdBinary = 5; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } optional string destinationE164 = 1; @@ -472,7 +482,8 @@ message SyncMessage { repeated StoryMessageRecipient storyMessageRecipients = 9; optional EditMessage editMessage = 10; reserved /*destinationPni */ 11; - // Next ID: 12 + optional bytes destinationServiceIdBinary = 12; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) + // Next ID: 13 } message Contacts { @@ -484,6 +495,7 @@ message SyncMessage { repeated string numbers = 1; repeated string acis = 3; repeated bytes groupIds = 2; + repeated bytes acisBinary = 4; // 16-byte UUID } message Request { @@ -504,12 +516,14 @@ message SyncMessage { reserved /*senderE164*/ 1; optional string senderAci = 3; optional uint64 timestamp = 2; + optional bytes senderAciBinary = 4; // 16-byte UUID } message Viewed { reserved /*senderE164*/ 1; optional string senderAci = 3; optional uint64 timestamp = 2; + optional bytes senderAciBinary = 4; // 16-byte UUID } message Configuration { @@ -536,6 +550,7 @@ message SyncMessage { reserved /*senderE164*/ 1; optional string senderAci = 3; optional uint64 timestamp = 2; + optional bytes senderAciBinary = 4; // 16-byte UUID } message FetchLatest { @@ -576,6 +591,7 @@ message SyncMessage { optional string threadAci = 2; optional bytes groupId = 3; optional Type type = 4; + optional bytes threadAciBinary = 5; // 16-byte UUID } message OutgoingPayment { @@ -823,6 +839,7 @@ message ContactDetails { optional string number = 1; optional string aci = 9; + optional bytes aciBinary = 13; // 16-byte UUID optional string name = 2; optional Avatar avatar = 3; reserved /* color */ 4; @@ -833,7 +850,7 @@ message ContactDetails { optional uint32 expireTimerVersion = 12; optional uint32 inboxPosition = 10; reserved /* archived */ 11; - // NEXT ID: 13 + // NEXT ID: 14 } message PaymentAddress { @@ -880,6 +897,7 @@ message BodyRange { oneof associatedValue { string mentionAci = 3; Style style = 4; + bytes mentionAciBinary = 5; // 16-byte UUID } } @@ -887,6 +905,7 @@ message AddressableMessage { oneof author { string authorServiceId = 1; string authorE164 = 2; + bytes authorServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } optional uint64 sentTimestamp = 3; } @@ -896,5 +915,6 @@ message ConversationIdentifier { string threadServiceId = 1; bytes threadGroupId = 2; string threadE164 = 3; + bytes threadServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } } diff --git a/libsignal-service/src/main/protowire/StorageService.proto b/libsignal-service/src/main/protowire/StorageService.proto index 4258fbabc9..f49fa2dc68 100644 --- a/libsignal-service/src/main/protowire/StorageService.proto +++ b/libsignal-service/src/main/protowire/StorageService.proto @@ -140,7 +140,9 @@ message ContactRecord { Name nickname = 22; string note = 23; optional AvatarColor avatarColor = 24; - // Next ID: 25 + bytes aciBinary = 25; // 16-byte UUID + bytes pniBinary = 26; // 16-byte UUID + // Next ID: 27 } message GroupV1Record { @@ -187,8 +189,9 @@ message AccountRecord { message PinnedConversation { message Contact { - string serviceId = 1; - string e164 = 2; + string serviceId = 1; + string e164 = 2; + bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } oneof identifier { @@ -294,12 +297,13 @@ message AccountRecord { } message StoryDistributionListRecord { - bytes identifier = 1; - string name = 2; - repeated string recipientServiceIds = 3; - uint64 deletedAtTimestamp = 4; - bool allowsReplies = 5; - bool isBlockList = 6; + bytes identifier = 1; + string name = 2; + repeated string recipientServiceIds = 3; + uint64 deletedAtTimestamp = 4; + bool allowsReplies = 5; + bool isBlockList = 6; + repeated bytes recipientServiceIdsBinary = 7; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } message CallLinkRecord { @@ -311,8 +315,9 @@ message CallLinkRecord { message Recipient { message Contact { - string serviceId = 1; - string e164 = 2; + string serviceId = 1; + string e164 = 2; + bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI) } oneof identifier { diff --git a/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java b/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java index b485560d9d..0be32f845a 100644 --- a/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java +++ b/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStreamTest.java @@ -17,7 +17,7 @@ public class DeviceContactsInputStreamTest { @Test public void read() throws IOException, InvalidInputException { ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); - DeviceContactsOutputStream output = new DeviceContactsOutputStream(byteArrayOut); + DeviceContactsOutputStream output = new DeviceContactsOutputStream(byteArrayOut, true); Optional aciFirst = Optional.of(ACI.from(UUID.randomUUID())); Optional e164First = Optional.of("+1404555555"); Optional aciSecond = Optional.of(ACI.from(UUID.randomUUID())); diff --git a/libsignal-service/src/test/java/org/whispersystems/signalservice/internal/crypto/SecondaryProvisioningCipherTest.kt b/libsignal-service/src/test/java/org/whispersystems/signalservice/internal/crypto/SecondaryProvisioningCipherTest.kt index 950c8cfb61..0e2d87dd59 100644 --- a/libsignal-service/src/test/java/org/whispersystems/signalservice/internal/crypto/SecondaryProvisioningCipherTest.kt +++ b/libsignal-service/src/test/java/org/whispersystems/signalservice/internal/crypto/SecondaryProvisioningCipherTest.kt @@ -9,12 +9,14 @@ import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import okio.ByteString +import okio.ByteString.Companion.toByteString import org.junit.Test import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.IdentityKeyPair import org.signal.libsignal.protocol.ecc.ECPrivateKey import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.whispersystems.signalservice.api.util.UuidUtil +import org.whispersystems.signalservice.api.util.toByteArray import org.whispersystems.signalservice.internal.push.ProvisionEnvelope import org.whispersystems.signalservice.internal.push.ProvisionMessage import org.whispersystems.signalservice.internal.push.ProvisioningVersion @@ -29,6 +31,7 @@ class SecondaryProvisioningCipherTest { val primaryIdentityKeyPair = IdentityKeyPair.generate() val primaryProfileKey = generateProfileKey() val primaryProvisioningCipher = PrimaryProvisioningCipher(provisioningCipher.secondaryDevicePublicKey.publicKey) + val aci = UUID.randomUUID() val message = ProvisionMessage( aciIdentityKeyPublic = ByteString.of(*primaryIdentityKeyPair.publicKey.serialize()), @@ -36,9 +39,10 @@ class SecondaryProvisioningCipherTest { provisioningCode = "code", provisioningVersion = ProvisioningVersion.CURRENT.value, number = "+14045555555", - aci = UUID.randomUUID().toString(), + aci = aci.toString(), profileKey = ByteString.of(*primaryProfileKey.serialize()), - readReceipts = true + readReceipts = true, + aciBinary = aci.toByteArray().toByteString() ) val provisionMessage = ProvisionEnvelope.ADAPTER.decode(primaryProvisioningCipher.encrypt(message)) @@ -56,6 +60,7 @@ class SecondaryProvisioningCipherTest { assertThat(message.userAgent).isEqualTo(success.message.userAgent) assertThat(message.provisioningCode).isEqualTo(success.message.provisioningCode!!) assertThat(message.provisioningVersion).isEqualTo(success.message.provisioningVersion!!) + assertThat(message.aciBinary).isEqualTo(UuidUtil.parseOrThrow(success.message.aciBinary).toByteArray().toByteString()) } companion object { diff --git a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt index b7e4174485..c6c8f7b5d1 100644 --- a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt +++ b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt @@ -27,6 +27,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.api.util.toByteArray import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.DataMessage import org.whispersystems.signalservice.internal.push.Envelope @@ -98,6 +99,7 @@ class SignalClient { ) val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content) + val serviceGuid = UUID.randomUUID() return Envelope( sourceServiceId = aci.toString(), @@ -105,10 +107,13 @@ class SignalClient { destinationServiceId = to.aci.toString(), timestamp = sentTimestamp, serverTimestamp = sentTimestamp, - serverGuid = UUID.randomUUID().toString(), + serverGuid = serviceGuid.toString(), type = Envelope.Type.fromValue(outgoingPushMessage.type), urgent = true, - content = encryptedContent.toByteString() + content = encryptedContent.toByteString(), + sourceServiceIdBinary = aci.toByteString(), + destinationServiceIdBinary = to.aci.toByteString(), + serverGuidBinary = serviceGuid.toByteArray().toByteString() ) } @@ -129,6 +134,7 @@ class SignalClient { ) val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content) + val serverGuid = UUID.randomUUID() return Envelope( sourceServiceId = aci.toString(), @@ -136,10 +142,13 @@ class SignalClient { destinationServiceId = to.aci.toString(), timestamp = sentTimestamp, serverTimestamp = sentTimestamp, - serverGuid = UUID.randomUUID().toString(), + serverGuid = serverGuid.toString(), type = Envelope.Type.fromValue(outgoingPushMessage.type), urgent = true, - content = encryptedContent.toByteString() + content = encryptedContent.toByteString(), + sourceServiceIdBinary = aci.toByteString(), + destinationServiceIdBinary = to.aci.toByteString(), + serverGuidBinary = serverGuid.toByteArray().toByteString() ) }