Add read support for binary service ids.

This commit is contained in:
Michelle Tang
2025-10-28 14:29:43 -04:00
committed by jeffrey-signal
parent bf4aa9cae9
commit f16405fabf
48 changed files with 399 additions and 205 deletions
@@ -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)
@@ -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
}
}
@@ -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
}
@@ -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!!),
@@ -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)
@@ -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<SyncMessage.Sent.StoryMessageRecipient>): 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) }
)
@@ -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!")
@@ -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
@@ -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<Recipient> recipients = SignalDatabase.recipients().getRecipientsForMultiDeviceSync();
Map<RecipientId, Integer> inboxPositions = SignalDatabase.threads().getInboxPositions();
Set<RecipientId> archived = SignalDatabase.threads().getArchivedRecipients();
@@ -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()),
@@ -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))
}
@@ -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<Mention> = getMentions(message.bodyRanges.take(BODY_RANGE_PROCESSING_LIMIT))
val sticker: Attachment? = getStickerAttachment(envelope.timestamp!!, message)
val attachments: List<Attachment> = 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<BodyRange>): List<Mention> {
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) {
@@ -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()
@@ -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
@@ -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
}
@@ -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<FollowUpOperation>,
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() ?: "<sealed>", envelope.sourceDevice)
return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)?.logString() ?: "<sealed>", 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)
}
}
@@ -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<ServiceId, Boolean>
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 {
@@ -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()
@@ -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 {
@@ -99,7 +99,7 @@ class ChatFolderRecordProcessor : DefaultStorageRecordProcessor<SignalChatFolder
private fun containsInvalidServiceId(recipients: List<Recipient>): 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
}
}
}
@@ -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 {
@@ -89,7 +89,7 @@ class NotificationProfileRecordProcessor : DefaultStorageRecordProcessor<SignalN
private fun containsInvalidServiceId(recipients: List<Recipient>): 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
}
}
}
@@ -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) {
@@ -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();
@@ -99,6 +99,7 @@ class StoryDistributionListRecordProcessor : DefaultStorageRecordProcessor<Signa
deletedAtTimestamp = remote.proto.deletedAtTimestamp
allowsReplies = remote.proto.allowsReplies
isBlockList = remote.proto.isBlockList
recipientServiceIdsBinary = remote.proto.recipientServiceIdsBinary
}.build().toSignalStoryDistributionListRecord(StorageId.forStoryDistributionList(keyGenerator.generate()))
val matchesRemote = doParamsMatch(remote, merged)
@@ -38,6 +38,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.Verified;
import java.util.List;
@@ -185,7 +186,7 @@ public final class IdentityUtil {
}
public static void processVerifiedMessage(Context context, Verified verified) throws InvalidKeyException {
SignalServiceAddress destination = new SignalServiceAddress(ServiceId.parseOrThrow(verified.destinationAci));
SignalServiceAddress destination = new SignalServiceAddress(ServiceId.ACI.parseOrThrow(verified.destinationAci, verified.destinationAciBinary));
IdentityKey identityKey = new IdentityKey(verified.identityKey.toByteArray(), 0);
VerifiedMessage.VerifiedState state;
@@ -1181,6 +1181,15 @@ object RemoteConfig {
hotSwappable = true
)
/** Whether or not to send over binary service ids (alongside string service ids). */
@JvmStatic
@get:JvmName("useBinaryId")
val useBinaryId: Boolean by remoteBoolean(
key = "android.useBinaryServiceId",
defaultValue = false,
hotSwappable = false
)
@JvmStatic
@get:JvmName("receivePolls")
val receivePolls: Boolean by remoteBoolean(
@@ -197,14 +197,14 @@ class ChatFolderRecordProcessorTest {
val proto = ChatFolderRecord.Builder().apply {
identifier = UuidUtil.toByteArray(UUID.randomUUID()).toByteString()
name = "Folder1"
position = -1
position = 1
showOnlyUnread = false
showMutedChats = false
includeAllIndividualChats = false
includeAllGroupChats = false
folderType = ChatFolderRecord.FolderType.CUSTOM
deletedAtTimestampMs = 0L
includedRecipients = listOf(Recipient(contact = Recipient.Contact(serviceId = "bad")))
includedRecipients = listOf(Recipient(contact = Recipient.Contact(serviceIdBinary = "bad".toByteArray().toByteString())))
}.build()
val record = SignalChatFolderRecord(STORAGE_ID, proto)
@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testutil.EmptyLogger
import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule
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.storage.SignalContactRecord
@@ -58,8 +59,8 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
pni = PNI_B.toStringWithoutPrefix(),
aciBinary = ACI_B.toByteString(),
pniBinary = PNI_B.toByteStringWithoutPrefix(),
e164 = E164_B
)
)
@@ -96,8 +97,8 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI.UNKNOWN.toString(),
pni = PNI.UNKNOWN.toString(),
aciBinary = ACI.UNKNOWN.toByteString(),
pniBinary = PNI.UNKNOWN.toByteString(),
e164 = E164_B
)
)
@@ -116,7 +117,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = E164_A
)
)
@@ -135,7 +136,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_A.toString()
aciBinary = ACI_A.toByteString()
)
)
@@ -153,8 +154,8 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
pni = PNI_A.toStringWithoutPrefix()
aciBinary = ACI_B.toByteString(),
pniBinary = PNI_A.toByteStringWithoutPrefix()
)
)
@@ -172,7 +173,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = E164_B
)
)
@@ -191,7 +192,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = "15551234567"
)
)
@@ -210,7 +211,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = "+1555ABC4567"
)
)
@@ -229,7 +230,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = "+"
)
)
@@ -248,7 +249,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = "+12345678901234567890"
)
)
@@ -267,7 +268,7 @@ class ContactRecordProcessorTest {
val record = buildRecord(
record = ContactRecord(
aci = ACI_B.toString(),
aciBinary = ACI_B.toByteString(),
e164 = "+05551234567"
)
)
@@ -287,18 +288,18 @@ class ContactRecordProcessorTest {
val local = buildRecord(
STORAGE_ID_A,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A,
pni = PNI_A.toStringWithoutPrefix()
pniBinary = PNI_A.toByteStringWithoutPrefix()
)
)
val remote = buildRecord(
STORAGE_ID_B,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A,
pni = PNI_B.toStringWithoutPrefix()
pniBinary = PNI_B.toByteStringWithoutPrefix()
)
)
@@ -306,9 +307,9 @@ class ContactRecordProcessorTest {
val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C))
// THEN
assertEquals(local.proto.aci, result.proto.aci)
assertEquals(ServiceId.parseOrNull(local.proto.aci, local.proto.aciBinary), ServiceId.parseOrNull(result.proto.aci, result.proto.aciBinary))
assertEquals(local.proto.e164, result.proto.e164)
assertEquals(local.proto.pni, result.proto.pni)
assertEquals(ServiceId.parseOrNull(local.proto.pni, local.proto.pniBinary), ServiceId.parseOrNull(result.proto.pni, result.proto.pniBinary))
}
@Test
@@ -319,18 +320,18 @@ class ContactRecordProcessorTest {
val local = buildRecord(
STORAGE_ID_A,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A,
pni = PNI_A.toStringWithoutPrefix()
pniBinary = PNI_A.toByteStringWithoutPrefix()
)
)
val remote = buildRecord(
STORAGE_ID_B,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_B,
pni = PNI_A.toStringWithoutPrefix()
pniBinary = PNI_A.toByteStringWithoutPrefix()
)
)
@@ -338,9 +339,9 @@ class ContactRecordProcessorTest {
val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C))
// THEN
assertEquals(local.proto.aci, result.proto.aci)
assertEquals(ServiceId.parseOrNull(local.proto.aci, local.proto.aciBinary), ServiceId.parseOrNull(result.proto.aci, result.proto.aciBinary))
assertEquals(local.proto.e164, result.proto.e164)
assertEquals(local.proto.pni, result.proto.pni)
assertEquals(ServiceId.parseOrNull(local.proto.pni, local.proto.pniBinary), ServiceId.parseOrNull(result.proto.pni, result.proto.pniBinary))
}
@Test
@@ -351,18 +352,18 @@ class ContactRecordProcessorTest {
val local = buildRecord(
STORAGE_ID_A,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A,
pni = PNI_A.toStringWithoutPrefix()
pniBinary = PNI_A.toByteStringWithoutPrefix()
)
)
val remote = buildRecord(
STORAGE_ID_B,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_B,
pni = PNI_B.toStringWithoutPrefix()
pniBinary = PNI_B.toByteStringWithoutPrefix()
)
)
@@ -370,9 +371,9 @@ class ContactRecordProcessorTest {
val result = subject.merge(remote, local, TestKeyGenerator(STORAGE_ID_C))
// THEN
assertEquals(remote.proto.aci, result.proto.aci)
assertEquals(ServiceId.parseOrNull(remote.proto.aci, remote.proto.aciBinary), ServiceId.parseOrNull(result.proto.aci, result.proto.aciBinary))
assertEquals(remote.proto.e164, result.proto.e164)
assertEquals(remote.proto.pni, result.proto.pni)
assertEquals(ServiceId.parseOrNull(remote.proto.pni, remote.proto.pniBinary), ServiceId.parseOrNull(result.proto.pni, result.proto.pniBinary))
}
@Test
@@ -383,7 +384,7 @@ class ContactRecordProcessorTest {
val local = buildRecord(
STORAGE_ID_A,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A
)
)
@@ -391,7 +392,7 @@ class ContactRecordProcessorTest {
val remote = buildRecord(
STORAGE_ID_B,
record = ContactRecord(
aci = ACI_A.toString(),
aciBinary = ACI_A.toByteString(),
e164 = E164_A,
nickname = ContactRecord.Name(given = "Ghost", family = "Spider"),
note = "Spidey Friend"
@@ -103,7 +103,7 @@ class NotificationProfileRecordProcessorTest {
val proto = NotificationProfile.Builder().apply {
id = UuidUtil.toByteArray(UUID.randomUUID()).toByteString()
name = "Profile"
allowedMembers = listOf(Recipient(contact = Recipient.Contact(serviceId = "bad")))
allowedMembers = listOf(Recipient(contact = Recipient.Contact(serviceIdBinary = "bad".toByteArray().toByteString())))
}.build()
val record = SignalNotificationProfileRecord(STORAGE_ID, proto)
@@ -162,6 +162,7 @@ class StorageSyncHelperTest {
private fun contactBuilder(aci: ACI, e164: String, profileName: String): ContactRecord.Builder {
return ContactRecord.Builder()
.aci(aci.toString())
.aciBinary(aci.toByteString())
.e164(e164)
.givenName(profileName)
}
@@ -1,6 +1,8 @@
package org.signal.core.util
import android.text.SpannableStringBuilder
import okio.ByteString
import okio.ByteString.Companion.toByteString
import okio.utf8Size
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -250,4 +252,8 @@ object StringUtil {
return true
}
fun String?.toByteString(): ByteString? {
return this?.toByteArray()?.toByteString()
}
}
@@ -184,6 +184,7 @@ public class SignalServiceMessageSender {
private final Scheduler scheduler;
private final long maxEnvelopeSize;
private final BooleanSupplier useRestFallback;
private final boolean useBinaryId;
public SignalServiceMessageSender(PushServiceSocket pushServiceSocket,
SignalServiceDataStore store,
@@ -194,7 +195,8 @@ public class SignalServiceMessageSender {
Optional<EventListener> 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<SignalServiceDataMessage.Mention> 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()) {
@@ -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<byte[]> 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 {
@@ -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
@@ -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> aci = Optional.ofNullable(ACI.parseOrNull(details.aci));
Optional<ACI> aci = Optional.ofNullable(ACI.parseOrNull(details.aci, details.aciBinary));
Optional<String> e164 = Optional.ofNullable(details.number);
Optional<String> name = Optional.ofNullable(details.name);
Optional<DeviceContactAvatar> avatar = Optional.empty();
@@ -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()) {
@@ -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()
@@ -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)
}
@@ -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)
@@ -11,7 +11,10 @@ import org.whispersystems.signalservice.internal.storage.protos.StoryDistributio
val StoryDistributionListRecord.recipientServiceAddresses: List<SignalServiceAddress>
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) }
}
@@ -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<UUID> fromByteStrings(Collection<ByteString> byteStringCollection) {
@@ -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;
}
}
@@ -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)
}
}
@@ -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 {
@@ -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<ACI> aciFirst = Optional.of(ACI.from(UUID.randomUUID()));
Optional<String> e164First = Optional.of("+1404555555");
Optional<ACI> aciSecond = Optional.of(ACI.from(UUID.randomUUID()));
@@ -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 {
@@ -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()
)
}