From ae2b290a07bcc9bc0eb2d08fa34dbba541be8b2c Mon Sep 17 00:00:00 2001 From: Max Radermacher Date: Wed, 22 Mar 2023 12:25:05 -0700 Subject: [PATCH] Use ServiceId in message sending code Co-authored-by: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> --- .../ViewControllers/DebugUI/DebugUIMessages.m | 9 +- .../ChangePhoneNumberPniManager+Shims.swift | 24 +- .../ChangePhoneNumberPniManager.swift | 16 +- .../Messages/Interactions/TSOutgoingMessage.h | 3 +- .../Messages/Interactions/TSOutgoingMessage.m | 5 +- .../Interactions/TSOutgoingMessage.swift | 6 +- .../Messages/MessageSender+SenderKey.swift | 227 +++++---- SignalServiceKit/src/Messages/MessageSender.h | 12 +- SignalServiceKit/src/Messages/MessageSender.m | 238 +++++----- .../src/Messages/MessageSender.swift | 446 +++++++++--------- .../src/Messages/OWSIdentityManager.swift | 9 +- .../src/Messages/OWSMessageSend.swift | 8 +- .../src/Messages/OWSOutgoingResendResponse.m | 6 +- .../src/Messages/UD/OWSUDManager.swift | 35 +- .../Messages/UD/SMKSecretSessionCipher.swift | 29 +- .../AxolotlStore/SSKSessionStore.swift | 11 +- .../Storage/AxolotlStore/SenderKeyStore.swift | 79 ++-- ...hangePhoneNumberPniManagerTest+Mocks.swift | 10 +- .../DisappearingMessageFinderTest.swift | 14 +- .../Interactions/TSOutgoingMessageTest.swift | 35 +- .../tests/Messages/OWSUDManagerTest.swift | 66 ++- .../Messages/SMKSecretSessionCipherTest.swift | 56 ++- 22 files changed, 716 insertions(+), 628 deletions(-) diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 684fa5d77e..cd680904d9 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -4235,6 +4235,13 @@ typedef OWSContact * (^OWSContactBlock)(SDSAnyWriteTransaction *transaction); } SignalServiceAddress *address = incomingSenderAddress; + ServiceIdObjC *_Nullable incomingSenderServiceId = incomingSenderAddress.serviceIdObjC; + if (incomingSenderServiceId == nil) { + OWSFailDebug(@"Missing incomingSenderServiceId."); + return; + } + ServiceIdObjC *serviceId = incomingSenderServiceId; + DatabaseStorageWrite(SDSDatabaseStorage.shared, ^(SDSAnyWriteTransaction *transaction) { for (NSNumber *timestamp in timestamps) { NSString *randomText = [self randomText]; @@ -4256,7 +4263,7 @@ typedef OWSContact * (^OWSContactBlock)(SDSAnyWriteTransaction *transaction); TSOutgoingMessage *message = [messageBuilder buildWithTransaction:transaction]; [message anyInsertWithTransaction:transaction]; [message updateWithFakeMessageState:TSOutgoingMessageStateSent transaction:transaction]; - [message updateWithSentRecipient:address wasSentByUD:NO transaction:transaction]; + [message updateWithSentRecipient:serviceId wasSentByUD:NO transaction:transaction]; [message updateWithDeliveredRecipient:address recipientDeviceId:0 deliveryTimestamp:timestamp.unsignedLongLongValue diff --git a/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager+Shims.swift b/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager+Shims.swift index 6387f982b0..174bd7ea7d 100644 --- a/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager+Shims.swift +++ b/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager+Shims.swift @@ -39,12 +39,12 @@ protocol _ChangePhoneNumberPniManager_MessageSenderShim { func buildDeviceMessage( forMessagePlaintextContent messagePlaintextContent: Data?, messageEncryptionStyle: EncryptionStyle, - recipientServiceId: ServiceId, - recipientAccountId: String, - recipientDeviceId: NSNumber, + recipientId: String, + serviceId: ServiceId, + deviceId: NSNumber, isOnlineMessage: Bool, isTransientSenderKeyDistributionMessage: Bool, - isStorySendMessage: Bool, + isStoryMessage: Bool, isResendRequestMessage: Bool, udSendingParamsProvider: UDSendingParamsProvider? ) throws -> DeviceMessage? @@ -108,24 +108,24 @@ class _ChangePhoneNumberPniManager_MessageSenderWrapper: _ChangePhoneNumberPniMa func buildDeviceMessage( forMessagePlaintextContent messagePlaintextContent: Data?, messageEncryptionStyle: EncryptionStyle, - recipientServiceId: ServiceId, - recipientAccountId: String, - recipientDeviceId: NSNumber, + recipientId: String, + serviceId: ServiceId, + deviceId: NSNumber, isOnlineMessage: Bool, isTransientSenderKeyDistributionMessage: Bool, - isStorySendMessage: Bool, + isStoryMessage: Bool, isResendRequestMessage: Bool, udSendingParamsProvider: UDSendingParamsProvider? ) throws -> DeviceMessage? { try messageSender.buildDeviceMessage( forMessagePlaintextContent: messagePlaintextContent, messageEncryptionStyle: messageEncryptionStyle, - recipientAddress: SignalServiceAddress(recipientServiceId), - recipientAccountId: recipientAccountId, - recipientDeviceId: recipientDeviceId, + recipientId: recipientId, + serviceId: ServiceIdObjC(serviceId), + deviceId: deviceId, isOnlineMessage: isOnlineMessage, isTransientSenderKeyDistributionMessage: isTransientSenderKeyDistributionMessage, - isStorySendMessage: isStorySendMessage, + isStoryMessage: isStoryMessage, isResendRequestMessage: isResendRequestMessage, udSendingParamsProvider: udSendingParamsProvider ) diff --git a/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager.swift b/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager.swift index 91f9838eff..31616571f4 100644 --- a/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager.swift +++ b/SignalServiceKit/ChangePhoneNumber/ChangePhoneNumberPniManager.swift @@ -297,8 +297,8 @@ class ChangePhoneNumberPniManagerImpl: ChangePhoneNumberPniManager { logger.info("Building device message for device with ID \(linkedDeviceId).") return encryptPniChangeNumber( - forRecipientAci: localAci, - recipientAccountId: localAccountId, + recipientId: localAccountId, + recipientAci: localAci, recipientDeviceId: linkedDeviceId, identityKeyPair: pniIdentityKeyPair, signedPreKey: signedPreKey, @@ -331,8 +331,8 @@ class ChangePhoneNumberPniManagerImpl: ChangePhoneNumberPniManager { /// The message for the linked device. If `nil`, indicates the device was /// invalid and should be skipped. private func encryptPniChangeNumber( - forRecipientAci recipientAci: ServiceId, - recipientAccountId: String, + recipientId: String, + recipientAci: ServiceId, recipientDeviceId: UInt32, identityKeyPair: ECKeyPair, signedPreKey: SignedPreKeyRecord, @@ -357,12 +357,12 @@ class ChangePhoneNumberPniManagerImpl: ChangePhoneNumberPniManager { let deviceMessage: DeviceMessage? = try self.messageSender.buildDeviceMessage( forMessagePlaintextContent: plaintextContent, messageEncryptionStyle: .whisper, - recipientServiceId: recipientAci, - recipientAccountId: recipientAccountId, - recipientDeviceId: NSNumber(value: recipientDeviceId), + recipientId: recipientId, + serviceId: recipientAci, + deviceId: NSNumber(value: recipientDeviceId), isOnlineMessage: false, isTransientSenderKeyDistributionMessage: false, - isStorySendMessage: false, + isStoryMessage: false, isResendRequestMessage: false, udSendingParamsProvider: nil // Sync messages do not use UD ) diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h index c59ce0709c..4fa075b35c 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h @@ -71,6 +71,7 @@ typedef NS_ENUM(NSInteger, EncryptionStyle) { @class SSKProtoAttachmentPointer; @class SSKProtoContentBuilder; @class SSKProtoDataMessageBuilder; +@class ServiceIdObjC; @class SignalServiceAddress; @class TSOutgoingMessageBuilder; @@ -280,7 +281,7 @@ NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:receivedAtTimestamp #pragma mark - Update With... Methods // This method is used to record a successful send to one recipient. -- (void)updateWithSentRecipient:(SignalServiceAddress *)recipientAddress +- (void)updateWithSentRecipient:(ServiceIdObjC *)serviceId wasSentByUD:(BOOL)wasSentByUD transaction:(SDSAnyWriteTransaction *)transaction; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 712caa3a4b..357e3acab1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -667,13 +667,14 @@ NSUInteger const TSOutgoingMessageSchemaVersion = 1; }]; } -- (void)updateWithSentRecipient:(SignalServiceAddress *)recipientAddress +- (void)updateWithSentRecipient:(ServiceIdObjC *)serviceId wasSentByUD:(BOOL)wasSentByUD transaction:(SDSAnyWriteTransaction *)transaction { - OWSAssertDebug(recipientAddress.isValid); + OWSAssertDebug(serviceId); OWSAssertDebug(transaction); + SignalServiceAddress *recipientAddress = [[SignalServiceAddress alloc] initWithServiceIdObjC:serviceId]; [self anyUpdateOutgoingMessageWithTransaction:transaction block:^(TSOutgoingMessage *message) { TSOutgoingMessageRecipientState *_Nullable recipientState diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift index eee9b4628d..c46c15083e 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.swift @@ -335,6 +335,10 @@ public extension TSOutgoingMessage { throw OWSAssertionError("Missing local thread") } + guard let localUuid = Self.tsAccountManager.localUuid else { + throw OWSAssertionError("Missing local uuid") + } + guard let transcript = self.buildTranscriptSyncMessage( localThread: localThread, transaction: transaction @@ -353,7 +357,7 @@ public extension TSOutgoingMessage { plaintextContent: plaintext, plaintextPayloadId: payloadId, thread: localThread, - address: Self.tsAccountManager.localAddress!, + serviceId: ServiceIdObjC(ServiceId(localUuid)), udSendingAccess: nil, localAddress: Self.tsAccountManager.localAddress!, sendErrorBlock: nil diff --git a/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift b/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift index 1e6bb2fe27..2991fde3b7 100644 --- a/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift +++ b/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift @@ -10,12 +10,12 @@ extension MessageSender { private static var maxSenderKeyEnvelopeSize: UInt64 { 256 * 1024 } struct Recipient { - let address: SignalServiceAddress + let serviceId: ServiceId let devices: [UInt32] var protocolAddresses: [ProtocolAddress] { devices.compactMap { do { - return try ProtocolAddress(from: address, deviceId: $0) + return try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: $0) } catch { owsFailDebug("\(error)") return nil @@ -23,10 +23,13 @@ extension MessageSender { } } - init(address: SignalServiceAddress, transaction readTx: SDSAnyReadTransaction) { - let recipient = SignalRecipient.get(address: address, mustHaveDevices: false, transaction: readTx) - - self.address = address + init(serviceId: ServiceId, transaction readTx: SDSAnyReadTransaction) { + let recipient = SignalRecipient.get( + address: SignalServiceAddress(serviceId), + mustHaveDevices: false, + transaction: readTx + ) + self.serviceId = serviceId self.devices = recipient?.deviceIds ?? [] } } @@ -80,12 +83,12 @@ extension MessageSender { case NeedsSKDM case FanoutOnly } - var participants: [SignalServiceAddress: ParticipantState] + var participants: [ServiceIdObjC: ParticipantState] init(numberOfParticipants: Int) { self.participants = Dictionary(minimumCapacity: numberOfParticipants) } - init(fanoutOnlyParticipants: [SignalServiceAddress]) { + init(fanoutOnlyParticipants: [ServiceIdObjC]) { self.participants = Dictionary(minimumCapacity: fanoutOnlyParticipants.count) super.init() @@ -93,20 +96,20 @@ extension MessageSender { } @objc - var fanoutParticipants: [SignalServiceAddress] { + var fanoutParticipants: [ServiceIdObjC] { Array(participants.lazy.filter { $0.value == .FanoutOnly }.map { $0.key }) } @objc - var allSenderKeyParticipants: [SignalServiceAddress] { + var allSenderKeyParticipants: [ServiceIdObjC] { Array(participants.lazy.filter { $0.value != .FanoutOnly }.map { $0.key }) } - var participantsNeedingSKDM: [SignalServiceAddress] { + var participantsNeedingSKDM: [ServiceIdObjC] { Array(participants.lazy.filter { $0.value == .NeedsSKDM }.map { $0.key }) } - var readyParticipants: [SignalServiceAddress] { + var readyParticipants: [ServiceIdObjC] { Array(participants.lazy.filter { $0.value == .SenderKeyReady }.map { $0.key }) } } @@ -115,8 +118,8 @@ extension MessageSender { @objc func senderKeyStatus( for thread: TSThread, - intendedRecipients: [SignalServiceAddress], - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess] + intendedRecipients: [ServiceIdObjC], + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess] ) -> SenderKeyStatus { guard !RemoteConfig.senderKeyKillSwitch else { Logger.info("Sender key kill switch activated. No recipients support sender key.") @@ -131,18 +134,13 @@ extension MessageSender { let isCurrentKeyValid = senderKeyStore.isKeyValid(for: thread, readTx: readTx) let recipientsWithoutSenderKey = senderKeyStore.recipientsInNeedOfSenderKey( for: thread, - addresses: intendedRecipients, - readTx: readTx) + serviceIds: intendedRecipients, + readTx: readTx + ) let senderKeyStatus = SenderKeyStatus(numberOfParticipants: intendedRecipients.count) - let threadRecipients = thread.recipientAddresses(with: readTx) + let threadRecipients = thread.recipientAddresses(with: readTx).compactMap { $0.serviceIdObjC } intendedRecipients.forEach { candidate in - // Sender key is UUID-only - guard candidate.isValid, candidate.uuid != nil, !candidate.isLocalAddress else { - senderKeyStatus.participants[candidate] = .FanoutOnly - return - } - // Sender key requires that you're a full member of the group and you support UD guard threadRecipients.contains(candidate), @@ -152,10 +150,16 @@ extension MessageSender { return } + guard !SignalServiceAddress(candidate.wrappedValue).isLocalAddress else { + senderKeyStatus.participants[candidate] = .FanoutOnly + owsFailBeta("Callers must not provide UD access for the local ACI.") + return + } + // If all registrationIds aren't valid, we should fallback to fanout // This should be removed once we've sorted out why there are invalid // registrationIds - let registrationIdStatus = candidate.registrationIdStatus(transaction: readTx) + let registrationIdStatus = Self.registrationIdStatus(for: candidate.wrappedValue, transaction: readTx) switch registrationIdStatus { case .valid: // All good, keep going. @@ -190,9 +194,9 @@ extension MessageSender { payloadId: NSNumber?, thread: TSThread, status: SenderKeyStatus, - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess], + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess], senderCertificates: SenderCertificates, - sendErrorBlock: @escaping (SignalServiceAddress, NSError) -> Void + sendErrorBlock: @escaping (ServiceIdObjC, NSError) -> Void ) -> AnyPromise { AnyPromise( @@ -214,23 +218,23 @@ extension MessageSender { payloadId: Int64?, thread: TSThread, status: SenderKeyStatus, - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess], + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess], senderCertificates: SenderCertificates, - sendErrorBlock: @escaping (SignalServiceAddress, NSError) -> Void + sendErrorBlock: @escaping (ServiceIdObjC, NSError) -> Void ) -> Promise { // Because of the way promises are combined further up the chain, we need to ensure that if // *any* send fails, the entire Promise rejcts. The error it rejects with doesn't really matter // and isn't consulted. let didHitAnyFailure = AtomicBool(false) - let wrappedSendErrorBlock = { (address: SignalServiceAddress, error: Error) -> Void in - Logger.info("Sender key send failed for \(address): \(error)") + let wrappedSendErrorBlock = { (serviceId: ServiceIdObjC, error: Error) -> Void in + Logger.info("Sender key send failed for \(serviceId): \(error)") _ = didHitAnyFailure.tryToSetFlag() if let senderKeyError = error as? SenderKeyError { - sendErrorBlock(address, senderKeyError.asSSKError) + sendErrorBlock(serviceId, senderKeyError.asSSKError) } else { - sendErrorBlock(address, (error as NSError)) + sendErrorBlock(serviceId, (error as NSError)) } } @@ -240,7 +244,7 @@ extension MessageSender { // pend indefinitely. let senderKeyGuarantee: Guarantee - senderKeyGuarantee = { () -> Guarantee<[SignalServiceAddress]> in + senderKeyGuarantee = { () -> Guarantee<[ServiceIdObjC]> in // If none of our recipients need an SKDM let's just skip the database write. if status.participantsNeedingSKDM.count > 0 { return senderKeyDistributionPromise( @@ -252,7 +256,7 @@ extension MessageSender { } else { return .value(status.readyParticipants) } - }().then(on: senderKeyQueue) { (senderKeyRecipients: [SignalServiceAddress]) -> Guarantee in + }().then(on: senderKeyQueue) { (senderKeyRecipients: [ServiceIdObjC]) -> Guarantee in guard senderKeyRecipients.count > 0 else { // Something went wrong with the SKDM promise. Exit early. owsAssertDebug(didHitAnyFailure.get()) @@ -262,43 +266,48 @@ extension MessageSender { return firstly { () -> Promise in Logger.info("Sending sender key message with timestamp \(message.timestamp) to \(senderKeyRecipients)") - let senderCertificate = Self.senderCertificate(forSenderKeyRecipients: senderKeyRecipients, - senderCertificates: senderCertificates) + let senderCertificate = Self.senderCertificate( + forSenderKeyRecipients: senderKeyRecipients, + senderCertificates: senderCertificates + ) return self.sendSenderKeyRequest( message: message, plaintext: plaintextContent, thread: thread, - addresses: senderKeyRecipients, + serviceIds: senderKeyRecipients, udAccessMap: udAccessMap, - senderCertificate: senderCertificate) + senderCertificate: senderCertificate + ) }.done(on: self.senderKeyQueue) { (sendResult: SenderKeySendResult) in - Logger.info("Sender key message with timestamp \(message.timestamp) sent! Recipients: \(sendResult.successAddresses). Unregistered: \(sendResult.unregisteredAddresses)") + Logger.info("Sender key message with timestamp \(message.timestamp) sent! Recipients: \(sendResult.successServiceIds). Unregistered: \(sendResult.unregisteredServiceIds)") - return try self.databaseStorage.write { writeTx in - sendResult.unregisteredAddresses.forEach { address in - self.markAddressAsUnregistered(address, message: message, thread: thread, transaction: writeTx) + return self.databaseStorage.write { writeTx in + sendResult.unregisteredServiceIds.forEach { serviceId in + self.markAsUnregistered(serviceId: serviceId, message: message, thread: thread, transaction: writeTx) let error = MessageSenderNoSuchSignalRecipientError() - wrappedSendErrorBlock(address, error) + wrappedSendErrorBlock(ServiceIdObjC(serviceId), error) } - try sendResult.success.forEach { recipient in - guard let uuid = recipient.address.uuid else { - throw OWSAssertionError("Invalid address") - } + sendResult.success.forEach { recipient in + message.update( + withSentRecipient: ServiceIdObjC(recipient.serviceId), + wasSentByUD: true, + transaction: writeTx + ) - message.update(withSentRecipient: recipient.address, wasSentByUD: true, transaction: writeTx) + let recipientAddress = SignalServiceAddress(recipient.serviceId) // If we're sending a story, we generally get a 200, even if the account // doesn't exist. Therefore, don't use this to mark accounts as registered. if !message.isStorySend { - SignalRecipient.fetchOrCreate(for: recipient.address, trustLevel: .low, transaction: writeTx) + SignalRecipient.fetchOrCreate(for: recipientAddress, trustLevel: .low, transaction: writeTx) .markAsRegistered(transaction: writeTx) } self.profileManager.didSendOrReceiveMessage( - from: recipient.address, + from: recipientAddress, authedAccount: .implicit(), transaction: writeTx ) @@ -307,10 +316,11 @@ extension MessageSender { recipient.devices.forEach { deviceId in MessageSendLog.recordPendingDelivery( payloadId: payloadId, - recipientUuid: uuid, + recipientUuid: recipient.serviceId.uuidValue, recipientDeviceId: Int64(deviceId), message: message, - transaction: writeTx) + transaction: writeTx + ) } } } @@ -332,15 +342,18 @@ extension MessageSender { } } - private static func senderCertificate(forSenderKeyRecipients senderKeyRecipients: [SignalServiceAddress], - senderCertificates: SenderCertificates) -> SenderCertificate { + private static func senderCertificate( + forSenderKeyRecipients senderKeyRecipients: [ServiceIdObjC], + senderCertificates: SenderCertificates + ) -> SenderCertificate { let phoneNumberSharingMode = udManager.phoneNumberSharingMode switch phoneNumberSharingMode { case .everybody: return senderCertificates.defaultCert case .contactsOnly: let areAllSystemContacts: Bool = databaseStorage.read { transaction in - for address in senderKeyRecipients { + for serviceId in senderKeyRecipients { + let address = SignalServiceAddress(serviceId.wrappedValue) if !Self.contactsManager.isSystemContact(address: address, transaction: transaction) { return false } @@ -363,14 +376,14 @@ extension MessageSender { // // Returns the list of all recipients ready for the SenderKeyMessage. private func senderKeyDistributionPromise( - recipients: [SignalServiceAddress], + recipients: [ServiceIdObjC], thread: TSThread, originalMessage: TSOutgoingMessage, - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess], - sendErrorBlock: @escaping (SignalServiceAddress, Error) -> Void - ) -> Guarantee<[SignalServiceAddress]> { + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess], + sendErrorBlock: @escaping (ServiceIdObjC, Error) -> Void + ) -> Guarantee<[ServiceIdObjC]> { - var recipientsNotNeedingSKDM: Set = Set() + var recipientsNotNeedingSKDM: Set = Set() return databaseStorage.write(.promise) { writeTx -> [OWSMessageSend] in // Here we fetch all of the recipients that need an SKDM // We then construct an OWSMessageSend for each recipient that needs an SKDM. @@ -386,8 +399,9 @@ extension MessageSender { let recipientsNeedingSKDM = self.senderKeyStore.recipientsInNeedOfSenderKey( for: thread, - addresses: recipients, - readTx: writeTx) + serviceIds: recipients, + readTx: writeTx + ) recipientsNotNeedingSKDM = Set(recipients).subtracting(recipientsNeedingSKDM) guard !recipientsNeedingSKDM.isEmpty else { return [] } @@ -395,17 +409,22 @@ extension MessageSender { throw OWSAssertionError("Couldn't build SKDM") } - return recipientsNeedingSKDM.map { address in + return recipientsNeedingSKDM.map { serviceId in if let groupThread = thread as? TSGroupThread { - Logger.info("Sending SKDM to \(address) for group thread \(groupThread.groupId)") + Logger.info("Sending SKDM to \(serviceId) for group thread \(groupThread.groupId)") } else { - Logger.info("Sending SKDM to \(address) for thread \(thread.uniqueId)") + Logger.info("Sending SKDM to \(serviceId) for thread \(thread.uniqueId)") } - let contactThread = TSContactThread.getOrCreateThread(withContactAddress: address, transaction: writeTx) + let contactThread = TSContactThread.getOrCreateThread( + withContactAddress: SignalServiceAddress(serviceId.wrappedValue), + transaction: writeTx + ) let skdmMessage = OWSOutgoingSenderKeyDistributionMessage( thread: contactThread, - senderKeyDistributionMessageBytes: skdmBytes, transaction: writeTx) + senderKeyDistributionMessageBytes: skdmBytes, + transaction: writeTx + ) skdmMessage.configureAsSentOnBehalfOf(originalMessage, in: thread) let plaintext = skdmMessage.buildPlainTextData(contactThread, transaction: writeTx) @@ -423,8 +442,8 @@ extension MessageSender { plaintextContent: plaintext, plaintextPayloadId: payloadId, thread: contactThread, - address: address, - udSendingAccess: udAccessMap[address], + serviceId: serviceId, + udSendingAccess: udAccessMap[serviceId], localAddress: localAddress, sendErrorBlock: nil) } @@ -449,28 +468,33 @@ extension MessageSender { }.recover(on: self.senderKeyQueue) { error -> Promise in if error is MessageSenderNoSuchSignalRecipientError { self.databaseStorage.write { transaction in - self.markAddressAsUnregistered(messageSend.address, message: originalMessage, thread: thread, transaction: transaction) + self.markAsUnregistered( + serviceId: messageSend.serviceId.wrappedValue, + message: originalMessage, + thread: thread, + transaction: transaction + ) } } // Note that we still rethrow. It's just easier to access the address // while we still have the messageSend in scope. let wrappedError = SenderKeyError.recipientSKDMFailed(error) - sendErrorBlock(messageSend.address, wrappedError) + sendErrorBlock(messageSend.serviceId, wrappedError) throw wrappedError } }) } - }.map(on: self.senderKeyQueue) { resultArray -> [SignalServiceAddress] in + }.map(on: self.senderKeyQueue) { resultArray -> [ServiceIdObjC] in // This is a hot path, so we do a bit of a dance here to prepare all of the successful send // info before opening the write transaction. We need the recipient address and the SKDM // timestamp. - let successfulSendInfo: [(recipient: SignalServiceAddress, timestamp: UInt64)] + let successfulSendInfo: [(recipient: ServiceIdObjC, timestamp: UInt64)] successfulSendInfo = resultArray.compactMap { result in switch result { case let .success(messageSend): - return (recipient: messageSend.address, timestamp: messageSend.message.timestamp) + return (recipient: messageSend.serviceId, timestamp: messageSend.message.timestamp) case .failure: return nil } @@ -479,13 +503,16 @@ extension MessageSender { if successfulSendInfo.count > 0 { try self.databaseStorage.write { writeTx in try successfulSendInfo.forEach { - try self.senderKeyStore.recordSenderKeySent(for: thread, - to: $0.recipient, - timestamp: $0.timestamp, - writeTx: writeTx) + try self.senderKeyStore.recordSenderKeySent( + for: thread, + to: $0.recipient, + timestamp: $0.timestamp, + writeTx: writeTx + ) } } } + // We want to return all recipients that are now ready for sender key return Array(recipientsNotNeedingSKDM) + successfulSendInfo.map { $0.recipient } @@ -502,8 +529,8 @@ extension MessageSender { let success: [Recipient] let unregistered: [Recipient] - var successAddresses: [SignalServiceAddress] { success.map { $0.address } } - var unregisteredAddresses: [SignalServiceAddress] { unregistered.map { $0.address } } + var successServiceIds: [ServiceId] { success.map { $0.serviceId } } + var unregisteredServiceIds: [ServiceId] { unregistered.map { $0.serviceId } } } // Encrypts and sends the message using SenderKey @@ -513,8 +540,8 @@ extension MessageSender { message: TSOutgoingMessage, plaintext: Data?, thread: TSThread, - addresses: [SignalServiceAddress], - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess], + serviceIds: [ServiceIdObjC], + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess], senderCertificate: SenderCertificate ) -> Promise { guard let plaintext = plaintext else { @@ -522,14 +549,15 @@ extension MessageSender { } return self.databaseStorage.write(.promise) { writeTx -> ([Recipient], Data) in - let recipients = addresses.map { Recipient(address: $0, transaction: writeTx) } + let recipients = serviceIds.map { Recipient(serviceId: $0.wrappedValue, transaction: writeTx) } let ciphertext = try self.senderKeyMessageBody( plaintext: plaintext, message: message, thread: thread, recipients: recipients, senderCertificate: senderCertificate, - transaction: writeTx) + transaction: writeTx + ) return (recipients, ciphertext) }.then(on: senderKeyQueue) { (recipients: [Recipient], ciphertext: Data) -> Promise in @@ -557,7 +585,7 @@ extension MessageSender { isStory: Bool, thread: TSThread, recipients: [Recipient], - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess], + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess], senderCertificate: SenderCertificate, remainingAttempts: UInt ) -> Promise { @@ -578,15 +606,8 @@ extension MessageSender { } let response = try Self.decodeSuccessResponse(data: response.responseBodyData) let uuids404 = Set(response.uuids404) - - let successful = try recipients.filter { - guard let uuid = $0.address.uuid else { throw OWSAssertionError("Invalid address") } - return !uuids404.contains(uuid) - } - let unregistered = try recipients.filter { - guard let uuid = $0.address.uuid else { throw OWSAssertionError("Invalid address") } - return uuids404.contains(uuid) - } + let successful = recipients.filter { !uuids404.contains($0.serviceId.uuidValue) } + let unregistered = recipients.filter { uuids404.contains($0.serviceId.uuidValue) } return SenderKeySendResult(success: successful, unregistered: unregistered) }.recover(on: senderKeyQueue) { error -> Promise in let retryIfPossible = { () throws -> Promise in @@ -729,11 +750,13 @@ extension MessageSender { isStory: Bool, thread: TSThread, recipients: [Recipient], - udAccessMap: [SignalServiceAddress: OWSUDSendingAccess] + udAccessMap: [ServiceIdObjC: OWSUDSendingAccess] ) throws -> Promise { // Sender key messages use an access key composed of every recipient's individual access key. - let allAccessKeys = recipients.compactMap { udAccessMap[$0.address]?.udAccess.senderKeyUDAccessKey } + let allAccessKeys = recipients.compactMap { + udAccessMap[ServiceIdObjC($0.serviceId)]?.udAccess.senderKeyUDAccessKey + } guard recipients.count == allAccessKeys.count else { throw OWSAssertionError("Incomplete access key set") } @@ -803,7 +826,7 @@ extension MessageSender { } } -fileprivate extension SignalServiceAddress { +fileprivate extension MessageSender { enum RegistrationIdStatus { /// The address has a session with a valid registration id @@ -826,16 +849,16 @@ fileprivate extension SignalServiceAddress { /// Also check for missing sessions (shouldn't happen if we've gotten this far, since /// SenderKeyStore already said this address has previous Sender Key sends). We should /// investigate how this ever happened, but for now fall back to sending another SKDM. - func registrationIdStatus(transaction readTx: SDSAnyReadTransaction) -> RegistrationIdStatus { - let candidateDevices = MessageSender.Recipient(address: self, transaction: readTx).devices + static func registrationIdStatus(for serviceId: ServiceId, transaction: SDSAnyReadTransaction) -> RegistrationIdStatus { + let candidateDevices = MessageSender.Recipient(serviceId: serviceId, transaction: transaction).devices let sessionStore = signalProtocolStore(for: .aci).sessionStore for deviceId in candidateDevices { do { guard let sessionRecord = try sessionStore.loadSession( - for: self, + for: SignalServiceAddress(serviceId), deviceId: Int32(deviceId), - transaction: readTx + transaction: transaction ), sessionRecord.hasCurrentState else { return .noSession } @@ -848,7 +871,7 @@ fileprivate extension SignalServiceAddress { } catch { // An error is never thrown on nil result; only if there's something // on disk but parsing fails. - owsFailDebug("Failed to fetch registrationId for \(self): \(error)") + owsFailDebug("Failed to fetch registrationId for \(serviceId): \(error)") return .invalid } } diff --git a/SignalServiceKit/src/Messages/MessageSender.h b/SignalServiceKit/src/Messages/MessageSender.h index b47a405ba9..5d52cc36f7 100644 --- a/SignalServiceKit/src/Messages/MessageSender.h +++ b/SignalServiceKit/src/Messages/MessageSender.h @@ -111,17 +111,17 @@ NS_SWIFT_NAME(OutgoingAttachmentInfo) /// could not be built due to an invalid device ID. - (nullable DeviceMessage *)buildDeviceMessageForMessagePlaintextContent:(nullable NSData *)messagePlaintextContent messageEncryptionStyle:(EncryptionStyle)messageEncryptionStyle - recipientAddress:(SignalServiceAddress *)recipientAddress - recipientAccountId:(NSString *)recipientAccountId - recipientDeviceId:(NSNumber *)recipientDeviceId + recipientId:(NSString *)recipientId + serviceId:(ServiceIdObjC *)serviceId + deviceId:(NSNumber *)deviceId isOnlineMessage:(BOOL)isOnlineMessage isTransientSenderKeyDistributionMessage:(BOOL)isTransientSenderKeyDistributionMessage - isStorySendMessage:(BOOL)isStorySendMessage + isStoryMessage:(BOOL)isStoryMessage isResendRequestMessage:(BOOL)isResendRequestMessage udSendingParamsProvider: (nullable id)udSendingParamsProvider - error:(NSError **)errorHandle; -__attribute__((swift_error(nonnull_error))); + error:(NSError **)errorHandle + __attribute__((swift_error(nonnull_error))); + (NSOperationQueuePriority)queuePriorityForMessage:(TSOutgoingMessage *)message; diff --git a/SignalServiceKit/src/Messages/MessageSender.m b/SignalServiceKit/src/Messages/MessageSender.m index 5299a45e99..e540a4938b 100644 --- a/SignalServiceKit/src/Messages/MessageSender.m +++ b/SignalServiceKit/src/Messages/MessageSender.m @@ -570,14 +570,14 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes }); } -- (AnyPromise *)sendPromiseForAddresses:(NSArray *)addresses - message:(TSOutgoingMessage *)message - thread:(TSThread *)thread - senderCertificates:(nullable SenderCertificates *)senderCertificates - sendErrorBlock:(void (^_Nonnull)(SignalServiceAddress *address, NSError *))sendErrorBlock +- (AnyPromise *)sendPromiseForServiceIds:(NSArray *)serviceIds + message:(TSOutgoingMessage *)message + thread:(TSThread *)thread + senderCertificates:(nullable SenderCertificates *)senderCertificates + sendErrorBlock:(void (^_Nonnull)(ServiceIdObjC *address, NSError *))sendErrorBlock { OWSAssertDebug(!NSThread.isMainThread); - OWSAssertDebug(addresses.count > 0); + OWSAssertDebug(serviceIds.count > 0); OWSAssertDebug(message); OWSAssertDebug(thread); @@ -592,31 +592,32 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes OWSLogDebug(@"built message: %@ plainTextData.length: %lu", [message class], (unsigned long)plaintext.length); // 2. Gather "ud sending access". - NSMutableDictionary *sendingAccessMap = [NSMutableDictionary new]; + NSMutableDictionary *sendingAccessMap = [NSMutableDictionary new]; if (senderCertificates != nil) { - for (SignalServiceAddress *address in addresses) { - if (!address.isLocalAddress) { - if (message.isStorySend) { - sendingAccessMap[address] = [self.udManager storySendingAccessForAddress:address - senderCertificates:senderCertificates]; - } else { - sendingAccessMap[address] = [self.udManager udSendingAccessForAddress:address - requireSyncAccess:YES - senderCertificates:senderCertificates]; - } + for (ServiceIdObjC *serviceId in serviceIds) { + if ([self.tsAccountManager.localUuid isEqual:serviceId.uuidValue]) { + continue; + } + if (message.isStorySend) { + sendingAccessMap[serviceId] = [self.udManager storySendingAccessFor:serviceId + senderCertificates:senderCertificates]; + } else { + sendingAccessMap[serviceId] = [self.udManager udSendingAccessFor:serviceId + requireSyncAccess:YES + senderCertificates:senderCertificates]; } } } // 3. If we have any participants that support sender key, build a promise for their send. SenderKeyStatus *senderKeyStatus = [self senderKeyStatusFor:thread - intendedRecipients:addresses + intendedRecipients:serviceIds udAccessMap:sendingAccessMap]; AnyPromise *_Nullable senderKeyMessagePromise = nil; - NSArray *senderKeyAddresses = senderKeyStatus.allSenderKeyParticipants; - NSArray *fanoutSendAddresses = senderKeyStatus.fanoutParticipants; - if (thread.usesSenderKey && senderKeyAddresses.count >= 2 && message.canSendWithSenderKey) { + NSArray *senderKeyServiceIds = senderKeyStatus.allSenderKeyParticipants; + NSArray *fanoutServiceIds = senderKeyStatus.fanoutParticipants; + if (thread.usesSenderKey && senderKeyServiceIds.count >= 2 && message.canSendWithSenderKey) { senderKeyMessagePromise = [self senderKeyMessageSendPromiseWithMessage:message plaintextContent:plaintext payloadId:plaintextPayloadId @@ -627,12 +628,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes sendErrorBlock:sendErrorBlock]; OWSLogDebug(@"%lu / %lu recipients for message: %llu support sender key.", - senderKeyAddresses.count, - addresses.count, + senderKeyServiceIds.count, + serviceIds.count, message.timestamp); } else { - senderKeyAddresses = @[]; - fanoutSendAddresses = addresses; + senderKeyServiceIds = @[]; + fanoutServiceIds = serviceIds; if (!message.canSendWithSenderKey) { OWSLogInfo( @"Last sender key send attempt failed for message %llu. Falling back to fanout.", message.timestamp); @@ -640,21 +641,21 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes OWSLogDebug(@"Sender key not supported for message %llu", message.timestamp); } } - OWSAssertDebug((fanoutSendAddresses.count + senderKeyAddresses.count) == addresses.count); + OWSAssertDebug((fanoutServiceIds.count + senderKeyServiceIds.count) == serviceIds.count); // 4. Build a "OWSMessageSend" for each non-senderKey recipient. NSMutableArray *messageSends = [NSMutableArray new]; - for (SignalServiceAddress *address in fanoutSendAddresses) { - OWSUDSendingAccess *_Nullable udSendingAccess = sendingAccessMap[address]; + for (ServiceIdObjC *serviceId in fanoutServiceIds) { + OWSUDSendingAccess *_Nullable udSendingAccess = sendingAccessMap[serviceId]; OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:message plaintextContent:plaintext plaintextPayloadId:plaintextPayloadId thread:thread - address:address + serviceId:serviceId udSendingAccess:udSendingAccess localAddress:self.tsAccountManager.localAddress - sendErrorBlock:^(NSError *error) { sendErrorBlock(address, error); }]; + sendErrorBlock:^(NSError *error) { sendErrorBlock(serviceId, error); }]; [messageSends addObject:messageSend]; } @@ -718,7 +719,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes }; TSThread *thread = sendInfo.thread; - NSArray *recipientAddresses = sendInfo.recipients; + NSArray *recipientServiceIds = sendInfo.serviceIds; SenderCertificates *senderCertificates = sendInfo.senderCertificates; BOOL canSendToThread = NO; @@ -748,7 +749,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes // (In particular, regular data messages are sent via their implicit sync message only.) if (contactThread.contactAddress.isLocalAddress && !message.canSendToLocalAddress) { // Send to self. - OWSAssertDebug(sendInfo.recipients.count == 1); + OWSAssertDebug(recipientServiceIds.count == 1); OWSLogInfo(@"dropping %@ sent to local address (expected to be sent by sync message)", [message class]); // Don't mark self-sent messages as read (or sent) until the sync transcript is sent. successHandler(); @@ -756,7 +757,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes } } - if (recipientAddresses.count < 1) { + if (recipientServiceIds.count < 1) { // All recipients are already sent or can be skipped. // NOTE: We might still need to send a sync transcript. successHandler(); @@ -765,20 +766,20 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes BOOL isNonContactThread = thread.isNonContactThread; NSMutableArray *sendErrors = [NSMutableArray array]; - NSMutableDictionary *sendErrorPerRecipient = [NSMutableDictionary dictionary]; + NSMutableDictionary *sendErrorPerRecipient = [NSMutableDictionary dictionary]; [self unlockPreKeyUpdateFailuresPromise] .thenInBackground(^(id value) { - return [self sendPromiseForAddresses:recipientAddresses - message:message - thread:thread - senderCertificates:senderCertificates - sendErrorBlock:^(SignalServiceAddress *address, NSError *error) { - @synchronized(sendErrors) { - [sendErrors addObject:error]; - sendErrorPerRecipient[address] = error; - } - }]; + return [self sendPromiseForServiceIds:recipientServiceIds + message:message + thread:thread + senderCertificates:senderCertificates + sendErrorBlock:^(ServiceIdObjC *serviceId, NSError *error) { + @synchronized(sendErrors) { + [sendErrors addObject:error]; + sendErrorPerRecipient[serviceId] = error; + } + }]; }) .doneInBackground(^(id value) { successHandler(); }) .catchInBackground(^(id failure) { @@ -787,7 +788,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes NSError *firstNonRetryableError = nil; NSArray *sendErrorsCopy; - NSDictionary *sendErrorPerRecipientCopy; + NSDictionary *sendErrorPerRecipientCopy; @synchronized(sendErrors) { sendErrorsCopy = [sendErrors copy]; sendErrorPerRecipientCopy = [sendErrorPerRecipient copy]; @@ -795,8 +796,8 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes // Record the individual error for each "failed" recipient. DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { - for (SignalServiceAddress *address in sendErrorPerRecipientCopy) { - NSError *error = sendErrorPerRecipientCopy[address]; + for (ServiceIdObjC *serviceId in sendErrorPerRecipientCopy) { + NSError *error = sendErrorPerRecipientCopy[serviceId]; // Some errors should be ignored when sending messages // to threads other than TSContactThread. See discussion on @@ -805,6 +806,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes continue; } + SignalServiceAddress *address = [[SignalServiceAddress alloc] initWithServiceIdObjC:serviceId]; [message updateWithFailedRecipient:address error:error transaction:transaction]; } }); @@ -920,12 +922,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes DeviceMessage *deviceMessage = [self buildDeviceMessageForMessagePlaintextContent:messageSend.plaintextContent messageEncryptionStyle:messageSend.message.encryptionStyle - recipientAddress:recipient.address - recipientAccountId:recipient.accountId - recipientDeviceId:deviceId + recipientId:recipient.accountId + serviceId:messageSend.serviceId + deviceId:deviceId isOnlineMessage:messageSend.message.isOnline isTransientSenderKeyDistributionMessage:messageSend.message.isTransientSKDM - isStorySendMessage:messageSend.message.isStorySend + isStoryMessage:messageSend.message.isStorySend isResendRequestMessage:messageSend.message.isResendRequest udSendingParamsProvider:messageSend error:errorHandle]; @@ -944,12 +946,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes - (nullable DeviceMessage *)buildDeviceMessageForMessagePlaintextContent:(nullable NSData *)messagePlaintextContent messageEncryptionStyle:(EncryptionStyle)messageEncryptionStyle - recipientAddress:(SignalServiceAddress *)recipientAddress - recipientAccountId:(NSString *)recipientAccountId - recipientDeviceId:(NSNumber *)recipientDeviceId + recipientId:(NSString *)recipientId + serviceId:(ServiceIdObjC *)serviceId + deviceId:(NSNumber *)deviceId isOnlineMessage:(BOOL)isOnlineMessage isTransientSenderKeyDistributionMessage:(BOOL)isTransientSenderKeyDistributionMessage - isStorySendMessage:(BOOL)isStorySendMessage + isStoryMessage:(BOOL)isStoryMessage isResendRequestMessage:(BOOL)isResendRequestMessage udSendingParamsProvider: (nullable id)udSendingParamsProvider @@ -962,12 +964,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes @try { deviceMessage = [self throws_deviceMessageForMessagePlaintextContent:messagePlaintextContent messageEncryptionStyle:messageEncryptionStyle - recipientAddress:recipientAddress - recipientAccountId:recipientAccountId - recipientDeviceId:recipientDeviceId + recipientId:recipientId + serviceId:serviceId + deviceId:deviceId isOnlineMessage:isOnlineMessage isTransientSenderKeyDistributionMessage:isTransientSenderKeyDistributionMessage - isStorySendMessage:isStorySendMessage + isStoryMessage:isStoryMessage isResendRequestMessage:isResendRequestMessage udSendingParamsProvider:udSendingParamsProvider]; } @catch (NSException *exception) { @@ -976,10 +978,11 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes if ([exception.name isEqualToString:MessageSenderInvalidDeviceException]) { // If we have an invalid device exception, remove this device from // the recipient and suppress the error. + SignalServiceAddress *recipientAddress = [[SignalServiceAddress alloc] initWithServiceIdObjC:serviceId]; DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { [MessageSender updateDevicesWithAddress:recipientAddress devicesToAdd:@[] - devicesToRemove:@[ recipientDeviceId ] + devicesToRemove:@[ deviceId ] transaction:transaction]; }); } else if ([exception.name isEqualToString:NoSessionForTransientMessageException]) { @@ -994,6 +997,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes // she can pull down his latest identity. // If it's happening a lot, we should rethink our profile fetching strategy. OWSProdInfo([OWSAnalyticsEvents messageSendErrorFailedDueToUntrustedKey]); + SignalServiceAddress *recipientAddress = [[SignalServiceAddress alloc] initWithServiceIdObjC:serviceId]; error = [UntrustedIdentityError asNSErrorWithAddress:recipientAddress]; } else if ([exception.name isEqualToString:MessageSenderRateLimitedException]) { error = [SignalServiceRateLimitedError asNSError]; @@ -1042,11 +1046,10 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes OWSAssertDebug(messageSend.thread); TSOutgoingMessage *message = messageSend.message; - SignalServiceAddress *address = messageSend.address; - OWSAssertDebug(address.isValid); + ServiceIdObjC *serviceId = messageSend.serviceId; OWSLogInfo( - @"attempting to send message: %@, timestamp: %llu, recipient: %@", message.class, message.timestamp, address); + @"attempting to send message: %@, timestamp: %llu, recipient: %@", message.class, message.timestamp, serviceId); if (messageSend.remainingAttempts <= 0) { // We should always fail with a specific error. @@ -1056,14 +1059,6 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes return messageSend.failure(error); } - // A prior CDS lookup would've resolved the UUID for this recipient if it was registered - // If we have no UUID, consider the recipient unregistered. - ServiceIdObjC *serviceId = address.serviceIdObjC; - if (serviceId == nil) { - [self failSendForUnregisteredRecipient:messageSend]; - return; - } - // Consume an attempt. messageSend.remainingAttempts = messageSend.remainingAttempts - 1; @@ -1163,7 +1158,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes } } - [self performMessageSendRequest:messageSend serviceId:serviceId deviceMessages:deviceMessages]; + [self performMessageSendRequest:messageSend deviceMessages:deviceMessages]; } - (void)handleMessageSentLocally:(TSOutgoingMessage *)message @@ -1243,52 +1238,51 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes - (DeviceMessage *)throws_deviceMessageForMessagePlaintextContent:(nullable NSData *)messagePlaintextContent messageEncryptionStyle:(EncryptionStyle)messageEncryptionStyle - recipientAddress:(SignalServiceAddress *)recipientAddress - recipientAccountId:(NSString *)recipientAccountId - recipientDeviceId:(NSNumber *)recipientDeviceId + recipientId:(NSString *)recipientId + serviceId:(ServiceIdObjC *)serviceId + deviceId:(NSNumber *)deviceId isOnlineMessage:(BOOL)isOnlineMessage isTransientSenderKeyDistributionMessage:(BOOL)isTransientSenderKeyDistributionMessage - isStorySendMessage:(BOOL)isStorySendMessage + isStoryMessage:(BOOL)isStoryMessage isResendRequestMessage:(BOOL)isResendRequestMessage udSendingParamsProvider: (nullable id)udSendingParamsProvider { OWSAssertDebug(!NSThread.isMainThread); - OWSAssertDebug(recipientAddress.isValid); if (!messagePlaintextContent) { OWSRaiseException(InvalidMessageException, @"No message proto"); } // This may involve blocking network requests. - [self throws_ensureRecipientHasSessionForRecipientAddress:recipientAddress - recipientDeviceId:recipientDeviceId - recipientAccountId:recipientAccountId - forOnlineMessage:isOnlineMessage - forTransientSenderKeyDistributionMessage:isTransientSenderKeyDistributionMessage - forStorySendMessage:isStorySendMessage - udSendingParamsProvider:udSendingParamsProvider]; + [self throws_ensureRecipientHasSessionForId:recipientId + serviceId:serviceId + deviceId:deviceId + isOnlineMessage:isOnlineMessage + isTransientSenderKeyDistributionMessage:isTransientSenderKeyDistributionMessage + isStoryMessage:isStoryMessage + udSendingParamsProvider:udSendingParamsProvider]; __block NSError *encryptionError; __block DeviceMessage *__nullable deviceMessage = nil; DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { switch (messageEncryptionStyle) { case EncryptionStyleWhisper: - deviceMessage = [self encryptedMessageForMessagePlaintextContent:messagePlaintextContent - recipientAddress:recipientAddress - deviceId:recipientDeviceId.intValue - udSendingParamsProvider:udSendingParamsProvider - transaction:transaction - error:&encryptionError]; + deviceMessage = [self encryptMessageWithPlaintextContent:messagePlaintextContent + serviceId:serviceId + deviceId:deviceId.intValue + udSendingParamsProvider:udSendingParamsProvider + transaction:transaction + error:&encryptionError]; break; case EncryptionStylePlaintext: - deviceMessage = [self wrappedPlaintextMessageForMessagePlaintextContent:messagePlaintextContent - isResendRequestMessage:isResendRequestMessage - recipientAddress:recipientAddress - deviceId:recipientDeviceId.intValue - udSendingParamsProvider:udSendingParamsProvider - transaction:transaction - error:&encryptionError]; + deviceMessage = [self wrapPlaintextMessageWithPlaintextContent:messagePlaintextContent + serviceId:serviceId + deviceId:deviceId.intValue + isResendRequestMessage:isResendRequestMessage + udSendingParamsProvider:udSendingParamsProvider + transaction:transaction + error:&encryptionError]; break; default: encryptionError = OWSErrorMakeAssertionError(@"Unrecognized encryption style"); @@ -1306,26 +1300,24 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes return deviceMessage; } -- (void)throws_ensureRecipientHasSessionForRecipientAddress:(SignalServiceAddress *)recipientAddress - recipientDeviceId:(NSNumber *)recipientDeviceId - recipientAccountId:(NSString *)recipientAccountId - forOnlineMessage:(BOOL)forOnlineMessage - forTransientSenderKeyDistributionMessage:(BOOL)forTransientSenderKeyDistributionMessage - forStorySendMessage:(BOOL)forStorySendMessage - udSendingParamsProvider: - (nullable id)udSendingParamsProvider +- (void)throws_ensureRecipientHasSessionForId:(NSString *)recipientId + serviceId:(ServiceIdObjC *)serviceId + deviceId:(NSNumber *)deviceId + isOnlineMessage:(BOOL)isOnlineMessage + isTransientSenderKeyDistributionMessage:(BOOL)isTransientSenderKeyDistributionMessage + isStoryMessage:(BOOL)isStoryMessage + udSendingParamsProvider:(nullable id)udSendingParamsProvider { OWSAssertDebug(!NSThread.isMainThread); - OWSAssertDebug(recipientDeviceId); - OWSAssertDebug(recipientAccountId); - - OWSAssertDebug(recipientAddress.isValid); + OWSAssertDebug(recipientId); + OWSAssertDebug(serviceId); + OWSAssertDebug(deviceId); __block BOOL hasSession; [self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) { SSKSessionStore *sessionStore = [self signalProtocolStoreForIdentity:OWSIdentityACI].sessionStore; - hasSession = [sessionStore containsActiveSessionForAccountId:recipientAccountId - deviceId:[recipientDeviceId intValue] + hasSession = [sessionStore containsActiveSessionForAccountId:recipientId + deviceId:[deviceId intValue] transaction:transaction]; }]; if (hasSession) { @@ -1335,12 +1327,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes __block dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block PreKeyBundle *_Nullable bundle; __block NSException *_Nullable exception; - [MessageSender makePrekeyRequestForRecipientAddress:recipientAddress - recipientDeviceId:recipientDeviceId - recipientAccountId:recipientAccountId - forOnlineMessage:forOnlineMessage - forTransientSenderKeyDistributionMessage:forTransientSenderKeyDistributionMessage - forStorySendMessage:forStorySendMessage + [MessageSender makePrekeyRequestWithRecipientId:recipientId + serviceId:serviceId + deviceId:deviceId + isOnlineMessage:isOnlineMessage + isTransientSenderKeyDistributionMessage:isTransientSenderKeyDistributionMessage + isStoryMessage:isStoryMessage udSendingParamsProvider:udSendingParamsProvider success:^(PreKeyBundle *_Nullable responseBundle) { bundle = responseBundle; @@ -1401,12 +1393,12 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes __block BOOL success; __block NSError *error; DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { - success = [[self class] createSessionForPreKeyBundle:bundle - accountId:recipientAccountId - recipientAddress:recipientAddress - deviceId:recipientDeviceId - transaction:transaction - error:&error]; + success = [[self class] createSessionFor:bundle + recipientId:recipientId + serviceId:serviceId + deviceId:deviceId + transaction:transaction + error:&error]; }); if (!success) { // NSUnderlyingErrorKey isn't a normal NSException user info key, but it's appropriate here. diff --git a/SignalServiceKit/src/Messages/MessageSender.swift b/SignalServiceKit/src/Messages/MessageSender.swift index 546fb046a6..019a48cddb 100644 --- a/SignalServiceKit/src/Messages/MessageSender.swift +++ b/SignalServiceKit/src/Messages/MessageSender.swift @@ -32,20 +32,14 @@ extension TSOutgoingMessage { // MARK: - extension MessageSender { - - private struct SessionStates { - let deviceAlreadyHasSession = AtomicUInt(0) - let deviceDeviceSessionCreated = AtomicUInt(0) - let failure = AtomicUInt(0) - } - - class func ensureSessions(forMessageSends messageSends: [OWSMessageSend], - ignoreErrors: Bool) -> Promise { + class func ensureSessions( + forMessageSends messageSends: [OWSMessageSend], + ignoreErrors: Bool + ) -> Promise { let promise = firstly(on: DispatchQueue.global()) { () -> Promise in var promises = [Promise]() for messageSend in messageSends { - promises += self.ensureSessions(forMessageSend: messageSend, - ignoreErrors: ignoreErrors) + promises += self.ensureSessions(forMessageSend: messageSend, ignoreErrors: ignoreErrors) } if !promises.isEmpty { Logger.info("Prekey fetches: \(promises.count)") @@ -60,62 +54,61 @@ extension MessageSender { return promise } - private class func ensureSessions(forMessageSend messageSend: OWSMessageSend, - ignoreErrors: Bool) -> [Promise] { - - var accountId: AccountId? - let deviceIdsWithoutSessions: [UInt32] = databaseStorage.read { transaction in - guard let recipient = SignalRecipient.get( + private class func ensureSessions( + forMessageSend messageSend: OWSMessageSend, + ignoreErrors: Bool + ) -> [Promise] { + let (recipientId, deviceIdsWithoutSessions): (AccountId?, [UInt32]) = databaseStorage.read { transaction in + let recipient = SignalRecipient.get( address: messageSend.address, mustHaveDevices: false, transaction: transaction - ) else { - // If there is no existing recipient for this address, try and send to - // the primary device so we can see if they are registered. - return [OWSDevicePrimaryDeviceId] - } + ) - accountId = recipient.accountId + // If there is no existing recipient for this address, try and send to the + // primary device so we can see if they are registered. + guard let recipient else { + return (nil, [OWSDevicePrimaryDeviceId]) + } var deviceIds = recipient.deviceIds ?? [] - // Filter out the current device, we never need a session for it. + // Filter out the current device; we never need a session for it. if messageSend.isLocalAddress { let localDeviceId = tsAccountManager.storedDeviceId(with: transaction) deviceIds = deviceIds.filter { $0 != localDeviceId } } let sessionStore = signalProtocolStore(for: .aci).sessionStore - return deviceIds.filter { deviceId in + return (recipient.accountId, deviceIds.filter { deviceId in !sessionStore.containsActiveSession( forAccountId: recipient.accountId, deviceId: Int32(deviceId), transaction: transaction ) - } + }) } guard !deviceIdsWithoutSessions.isEmpty else { return [] } var promises = [Promise]() for deviceId in deviceIdsWithoutSessions { - - Logger.verbose("Fetching prekey for: \(messageSend.address), \(deviceId)") + Logger.verbose("Fetching prekey for: \(messageSend.serviceId), \(deviceId)") let promise: Promise = firstly(on: DispatchQueue.global()) { () -> Promise in let (promise, future) = Promise.pending() - let forOnlineMessage = messageSend.message.isOnline - let forTransientSKDM = messageSend.message.isTransientSKDM - let forStorySend = messageSend.message.isStorySend + let isOnlineMessage = messageSend.message.isOnline + let isTransientSenderKeyDistributionMessage = messageSend.message.isTransientSKDM + let isStoryMessage = messageSend.message.isStorySend self.makePrekeyRequest( - forRecipientAddress: messageSend.address, - recipientDeviceId: NSNumber(value: deviceId), - recipientAccountId: accountId, - forOnlineMessage: forOnlineMessage, - forTransientSenderKeyDistributionMessage: forTransientSKDM, - forStorySendMessage: forStorySend, + recipientId: recipientId, + serviceId: messageSend.serviceId, + deviceId: NSNumber(value: deviceId), + isOnlineMessage: isOnlineMessage, + isTransientSenderKeyDistributionMessage: isTransientSenderKeyDistributionMessage, + isStoryMessage: isStoryMessage, udSendingParamsProvider: messageSend, success: { preKeyBundle in guard let preKeyBundle = preKeyBundle else { @@ -130,9 +123,8 @@ extension MessageSender { return promise }.done(on: DispatchQueue.global()) { (preKeyBundle: SignalServiceKit.PreKeyBundle) -> Void in try self.databaseStorage.write { transaction in - // Since we successfully fetched the prekey bundle, - // we know this device is registered. We can safely - // mark it as such to acquire a stable accountId. + // Since we successfully fetched the prekey bundle, we know this device is + // registered and can mark it as such to acquire a stable recipientId. let recipient = SignalRecipient.fetchOrCreate( for: messageSend.address, trustLevel: .low, @@ -141,9 +133,9 @@ extension MessageSender { recipient.markAsRegistered(deviceId: deviceId, transaction: transaction) try self.createSession( - forPreKeyBundle: preKeyBundle, - accountId: recipient.accountId, - recipientAddress: messageSend.address, + for: preKeyBundle, + recipientId: recipient.accountId, + serviceId: messageSend.serviceId, deviceId: NSNumber(value: deviceId), transaction: transaction ) @@ -152,10 +144,12 @@ extension MessageSender { switch error { case MessageSenderError.missingDevice: self.databaseStorage.write { transaction in - MessageSender.updateDevices(address: messageSend.address, - devicesToAdd: [], - devicesToRemove: [NSNumber(value: deviceId)], - transaction: transaction) + MessageSender.updateDevices( + address: messageSend.address, + devicesToAdd: [], + devicesToRemove: [NSNumber(value: deviceId)], + transaction: transaction + ) } default: break @@ -184,29 +178,24 @@ extension MessageSender { @objc public extension MessageSender { - class func makePrekeyRequest( - forRecipientAddress recipientAddress: SignalServiceAddress, - recipientDeviceId deviceId: NSNumber, - recipientAccountId accountId: AccountId?, - forOnlineMessage isOnline: Bool, - forTransientSenderKeyDistributionMessage isTransientSKDM: Bool, - forStorySendMessage isStorySend: Bool, + recipientId: AccountId?, + serviceId: ServiceIdObjC, + deviceId: NSNumber, + isOnlineMessage: Bool, + isTransientSenderKeyDistributionMessage: Bool, + isStoryMessage: Bool, udSendingParamsProvider: UDSendingParamsProvider?, success: @escaping (SignalServiceKit.PreKeyBundle?) -> Void, failure: @escaping (Error) -> Void ) { assert(!Thread.isMainThread) - assert(recipientAddress.isValid) - Logger.info("recipientAddress: \(recipientAddress), deviceId: \(deviceId)") + let serviceId = serviceId.wrappedValue - guard let serviceId = recipientAddress.serviceIdObjC else { - owsFailDebug("Skipping prekey request due to missing uuid.") - return failure(MessageSenderError.missingDevice) - } + Logger.info("serviceId: \(serviceId), deviceId: \(deviceId)") - if deviceRecentlyReportedMissing(serviceId: serviceId.wrappedValue, deviceId: deviceId.uint32Value) { + if deviceRecentlyReportedMissing(serviceId: serviceId, deviceId: deviceId.uint32Value) { // We don't want to retry prekey requests if we've recently gotten a "404 // missing device" for the same recipient/device. Fail immediately as // though we hit the "404 missing device" error again. @@ -214,12 +203,12 @@ public extension MessageSender { return failure(MessageSenderError.missingDevice) } - // If we've never interacted with this account before, we won't - // have an accountId. It's safe to skip the identity key check - // in that case, since we don't yet know anything about them yet. - if let accountId = accountId { - guard isPrekeyIdentityKeySafe(accountId: accountId, - recipientAddress: recipientAddress) else { + // If we've never interacted with this account before, we won't have a + // recipientId. It's safe to skip the identity key check in that case, + // since we don't know anything about them yet. + if let recipientId { + let recipientAddress = SignalServiceAddress(serviceId) + guard isPrekeyIdentityKeySafe(accountId: recipientId, recipientAddress: recipientAddress) else { // We don't want to make prekey requests if we can anticipate that // we're going to get an untrusted identity error. Logger.info("Skipping prekey request due to untrusted identity.") @@ -227,20 +216,20 @@ public extension MessageSender { } } - if isOnline || isTransientSKDM { + if isOnlineMessage || isTransientSenderKeyDistributionMessage { Logger.info("Skipping prekey request for transient message") return failure(MessageSenderNoSessionForTransientMessageError()) } // Don't use UD for story preKey fetches, we don't have a valid UD auth key - let udAccess = isStorySend ? nil : udSendingParamsProvider?.udSendingAccess?.udAccess + let udAccess = isStoryMessage ? nil : udSendingParamsProvider?.udSendingAccess?.udAccess let requestMaker = RequestMaker( label: "Prekey Fetch", requestFactoryBlock: { (udAccessKeyForRequest: SMKUDAccessKey?) -> TSRequest? in - Logger.verbose("Building prekey request for recipientAddress: \(recipientAddress), deviceId: \(deviceId)") + Logger.verbose("Building prekey request for serviceId: \(serviceId), deviceId: \(deviceId)") return OWSRequestFactory.recipientPreKeyRequest( - withServiceId: serviceId, + withServiceId: ServiceIdObjC(serviceId), deviceId: deviceId.stringValue, udAccessKey: udAccessKeyForRequest ) @@ -250,7 +239,7 @@ public extension MessageSender { // to this recipient also use basic auth. udSendingParamsProvider?.disableUDAuth() }, - serviceId: serviceId.wrappedValue, + serviceId: serviceId, udAccess: udAccess, authedAccount: .implicit(), // The v2/keys endpoint isn't supported via web sockets, so don't try and @@ -269,7 +258,7 @@ public extension MessageSender { }.catch(on: DispatchQueue.global()) { error in if let httpStatusCode = error.httpStatusCode { if httpStatusCode == 404 { - self.reportMissingDeviceError(serviceId: serviceId.wrappedValue, deviceId: deviceId.uint32Value) + self.reportMissingDeviceError(serviceId: serviceId, deviceId: deviceId.uint32Value) return failure(MessageSenderError.missingDevice) } else if httpStatusCode == 413 || httpStatusCode == 429 { return failure(MessageSenderError.prekeyRateLimit) @@ -303,19 +292,28 @@ public extension MessageSender { } } - @objc(createSessionForPreKeyBundle:accountId:recipientAddress:deviceId:transaction:error:) - class func createSession(forPreKeyBundle preKeyBundle: SignalServiceKit.PreKeyBundle, - accountId: String, - recipientAddress: SignalServiceAddress, - deviceId: NSNumber, - transaction: SDSAnyWriteTransaction) throws { + class func createSession( + for preKeyBundle: SignalServiceKit.PreKeyBundle, + recipientId: String, + serviceId: ServiceIdObjC, + deviceId: NSNumber, + transaction: SDSAnyWriteTransaction + ) throws { assert(!Thread.isMainThread) + let recipientAddress = SignalServiceAddress(serviceIdObjC: serviceId) + Logger.info("Creating session for recipientAddress: \(recipientAddress), deviceId: \(deviceId)") - guard !signalProtocolStore(for: .aci).sessionStore.containsActiveSession(forAccountId: accountId, - deviceId: deviceId.int32Value, - transaction: transaction) else { + let containsActiveSession = { () -> Bool in + signalProtocolStore(for: .aci).sessionStore.containsActiveSession( + forAccountId: recipientId, + deviceId: deviceId.int32Value, + transaction: transaction + ) + } + + guard !containsActiveSession() else { Logger.warn("Session already exists.") return } @@ -342,24 +340,24 @@ public extension MessageSender { } do { - let protocolAddress = try ProtocolAddress(from: recipientAddress, deviceId: deviceId.uint32Value) - try processPreKeyBundle(bundle, - for: protocolAddress, - sessionStore: signalProtocolStore(for: .aci).sessionStore, - identityStore: identityManager.store(for: .aci, transaction: transaction), - context: transaction) + let protocolAddress = try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: deviceId.uint32Value) + try processPreKeyBundle( + bundle, + for: protocolAddress, + sessionStore: signalProtocolStore(for: .aci).sessionStore, + identityStore: identityManager.store(for: .aci, transaction: transaction), + context: transaction + ) } catch SignalError.untrustedIdentity(_) { - handleUntrustedIdentityKeyError(accountId: accountId, - recipientAddress: recipientAddress, - preKeyBundle: preKeyBundle, - transaction: transaction) + handleUntrustedIdentityKeyError( + accountId: recipientId, + recipientAddress: recipientAddress, + preKeyBundle: preKeyBundle, + transaction: transaction + ) throw UntrustedIdentityError(address: recipientAddress) } - if !signalProtocolStore(for: .aci).sessionStore.containsActiveSession(forAccountId: accountId, - deviceId: deviceId.int32Value, - transaction: transaction) { - owsFailDebug("Session does not exist.") - } + owsAssertDebug(containsActiveSession(), "Session does not exist.") } class func handleUntrustedIdentityKeyError(accountId: String, @@ -540,16 +538,18 @@ public class MessageSendInfo: NSObject { // These recipients should be sent to during this cycle of send attempts. @objc - public let recipients: [SignalServiceAddress] + public let serviceIds: [ServiceIdObjC] @objc public let senderCertificates: SenderCertificates - required init(thread: TSThread, - recipients: [SignalServiceAddress], - senderCertificates: SenderCertificates) { + required init( + thread: TSThread, + serviceIds: [ServiceId], + senderCertificates: SenderCertificates + ) { self.thread = thread - self.recipients = recipients + self.serviceIds = serviceIds.map { ServiceIdObjC($0) } self.senderCertificates = senderCertificates } } @@ -586,66 +586,64 @@ extension MessageSender { ) return promise }.then(on: DispatchQueue.global()) { senderCertificates in - self.prepareRecipients(of: message, senderCertificates: senderCertificates) - } - } - - private static func prepareRecipients(of message: TSOutgoingMessage, - senderCertificates: SenderCertificates) -> Promise { - - firstly(on: DispatchQueue.global()) { () -> MessageSendInfo in - guard let localAddress = tsAccountManager.localAddress else { - throw OWSAssertionError("Missing localAddress.") - } - let possibleThread = Self.databaseStorage.read { transaction in - TSThread.anyFetch(uniqueId: message.uniqueThreadId, transaction: transaction) - } - guard let thread = possibleThread else { + let thread = self.databaseStorage.read { TSThread.anyFetch(uniqueId: message.uniqueThreadId, transaction: $0) } + guard let thread else { Logger.warn("Skipping send due to missing thread.") throw MessageSenderError.threadMissing } - - if message.isSyncMessage { - // Sync messages are just sent to the local user. - return MessageSendInfo(thread: thread, - recipients: [localAddress], - senderCertificates: senderCertificates) + return try self.prepareRecipients(of: message, thread: thread).map(on: DispatchQueue.global()) { preparedRecipients in + return MessageSendInfo( + thread: thread, + serviceIds: preparedRecipients, + senderCertificates: senderCertificates + ) } + } + } - let proposedRecipients = try self.unsentRecipients(of: message, thread: thread) - return MessageSendInfo(thread: thread, - recipients: proposedRecipients, - senderCertificates: senderCertificates) - }.then(on: DispatchQueue.global()) { (sendInfo: MessageSendInfo) -> Promise in + private static func prepareRecipients(of message: TSOutgoingMessage, thread: TSThread) throws -> Promise<[ServiceId]> { + guard let localAddress = tsAccountManager.localAddress else { + throw OWSAssertionError("Missing localAddress.") + } + + // Figure out which addresses we expect to receive the message. + let proposedRecipients: [SignalServiceAddress] + if message.isSyncMessage { + // Sync messages are just sent to the local user. + proposedRecipients = [localAddress] + } else { + proposedRecipients = try self.unsentRecipients(of: message, thread: thread) + } + + return firstly(on: DispatchQueue.global()) { () -> Promise<[ServiceId]> in // We might need to use CDS to fill in missing UUIDs and/or identify // which recipients are unregistered. - return firstly(on: DispatchQueue.global()) { () -> Promise<[SignalServiceAddress]> in - Self.ensureRecipientAddresses(sendInfo.recipients, message: message) - }.map(on: DispatchQueue.global()) { (registeredRecipients: [SignalServiceAddress]) in - // For group story replies, we must check if the recipients are stories capable - guard message.isGroupStoryReply else { return registeredRecipients } + return fetchServiceIds(for: proposedRecipients) - let profiles = databaseStorage.read { - Self.profileManager.getUserProfiles(forAddresses: registeredRecipients, transaction: $0) + }.map(on: DispatchQueue.global()) { (registeredRecipients: [ServiceId]) in + var filteredRecipients = registeredRecipients + + // For group story replies, we must check if the recipients are stories capable + if message.isGroupStoryReply { + let userProfiles = databaseStorage.read { + Self.profileManager.getUserProfiles( + forAddresses: registeredRecipients.map { SignalServiceAddress($0) }, + transaction: $0 + ) + } + filteredRecipients = filteredRecipients.filter { + userProfiles[SignalServiceAddress($0)]?.isStoriesCapable == true } - - return registeredRecipients.filter { profiles[$0]?.isStoriesCapable == true } - }.map(on: DispatchQueue.global()) { (validRecipients: [SignalServiceAddress]) in - // Replace recipients with validRecipients. - MessageSendInfo(thread: sendInfo.thread, - recipients: validRecipients, - senderCertificates: sendInfo.senderCertificates) } - }.map(on: DispatchQueue.global()) { (sendInfo: MessageSendInfo) -> MessageSendInfo in - // Mark skipped recipients as such. We skip because: + + // Mark skipped recipients as such. We may skip because: // - // * Recipient is no longer in the group. - // * Recipient is blocked. - // * Recipient is unregistered. - // * Recipient does not have the required capability. - // - // Elsewhere, we skip recipient if their Signal account has been deactivated. - let skippedRecipients = Set(message.sendingRecipientAddresses()).subtracting(sendInfo.recipients) + // * A recipient is no longer in the group. + // * A recipient is blocked. + // * A recipient is unregistered. + // * A recipient does not have the required capability. + let skippedRecipients = Set(message.sendingRecipientAddresses()) + .subtracting(filteredRecipients.lazy.map { SignalServiceAddress($0) }) if !skippedRecipients.isEmpty { self.databaseStorage.write { transaction in for address in skippedRecipients { @@ -655,7 +653,7 @@ extension MessageSender { } } - return sendInfo + return filteredRecipients } } @@ -750,28 +748,38 @@ extension MessageSender { } } - private static func ensureRecipientAddresses(_ addresses: [SignalServiceAddress], - message: TSOutgoingMessage) -> Promise<[SignalServiceAddress]> { + private static func fetchServiceIds(for addresses: [SignalServiceAddress]) -> Promise<[ServiceId]> { + var serviceIds = [ServiceId]() + var phoneNumbersToFetch = [E164]() - let invalidRecipients = addresses.filter { $0.uuid == nil } - guard !invalidRecipients.isEmpty else { - // All recipients are already valid. - return Promise.value(addresses) + for address in addresses { + if let serviceId = address.serviceId { + serviceIds.append(serviceId) + } else if let phoneNumber = address.e164 { + phoneNumbersToFetch.append(phoneNumber) + } else { + owsFailDebug("Recipient has neither ServiceId nor E164.") + } } - let phoneNumbersToFetch = invalidRecipients.compactMap { $0.phoneNumber } - owsAssertDebug(!phoneNumbersToFetch.isEmpty, "Invalid recipients have neither phone number nor UUID.") + // Check if all recipients are already valid. + if phoneNumbersToFetch.isEmpty { + return .value(serviceIds) + } + // If not, look up ServiceIds for the phone numbers that don't have them. return firstly { () -> Promise> in - contactDiscoveryManager.lookUp(phoneNumbers: Set(phoneNumbersToFetch), mode: .outgoingMessage) - }.map(on: DispatchQueue.sharedUtility) { (signalRecipients: Set) -> [SignalServiceAddress] in + contactDiscoveryManager.lookUp( + phoneNumbers: Set(phoneNumbersToFetch.lazy.map { $0.stringValue }), + mode: .outgoingMessage + ) + }.map(on: DispatchQueue.sharedUtility) { (signalRecipients: Set) -> [ServiceId] in for signalRecipient in signalRecipients { owsAssertDebug(signalRecipient.address.phoneNumber != nil) owsAssertDebug(signalRecipient.address.uuid != nil) } - var validRecipients = Set(addresses).subtracting(invalidRecipients) - validRecipients.formUnion(signalRecipients.compactMap { $0.address }) - return Array(validRecipients) + serviceIds.append(contentsOf: signalRecipients.lazy.compactMap { ServiceId(uuidString: $0.recipientUUID) }) + return serviceIds } } } @@ -803,7 +811,6 @@ extension MessageSender { func performMessageSendRequest( _ messageSend: OWSMessageSend, - serviceId: ServiceIdObjC, deviceMessages: [DeviceMessage] ) { owsAssertDebug(!Thread.isMainThread) @@ -831,7 +838,7 @@ extension MessageSender { label: "Message Send", requestFactoryBlock: { (udAccessKey: SMKUDAccessKey?) in OWSRequestFactory.submitMessageRequest( - withServiceId: serviceId, + withServiceId: messageSend.serviceId, messages: deviceMessages, timestamp: message.timestamp, udAccessKey: udAccessKey, @@ -845,7 +852,7 @@ extension MessageSender { // to this recipient also use basic auth. messageSend.disableUDAuth() }, - serviceId: serviceId.wrappedValue, + serviceId: messageSend.serviceId.wrappedValue, udAccess: messageSend.udSendingAccess?.udAccess, authedAccount: .implicit(), options: [] @@ -907,20 +914,17 @@ extension MessageSender { Self.databaseStorage.write { transaction in deviceMessages.forEach { deviceMessage in - guard let uuid = address.uuid else { - return owsFailDebug("Expected a destination") - } - if let payloadId = messageSend.plaintextPayloadId { MessageSendLog.recordPendingDelivery( payloadId: payloadId, - recipientUuid: uuid, + recipientUuid: messageSend.serviceId.uuidValue, recipientDeviceId: Int64(deviceMessage.destinationDeviceId), message: message, transaction: transaction) } } - message.update(withSentRecipient: address, wasSentByUD: wasSentByUD, transaction: transaction) + + message.update(withSentRecipient: messageSend.serviceId, wasSentByUD: wasSentByUD, transaction: transaction) transaction.addSyncCompletion { BenchManager.completeEvent(eventId: "sendMessageNetwork-\(message.timestamp)") @@ -1070,14 +1074,18 @@ extension MessageSender { func failSendForUnregisteredRecipient(_ messageSend: OWSMessageSend) { owsAssertDebug(!Thread.isMainThread) - let address: SignalServiceAddress = messageSend.address let message: TSOutgoingMessage = messageSend.message - Logger.verbose("Unregistered recipient: \(address)") + Logger.verbose("Unregistered recipient: \(messageSend.serviceId)") if !message.isSyncMessage { databaseStorage.write { writeTx in - markAddressAsUnregistered(address, message: message, thread: messageSend.thread, transaction: writeTx) + markAsUnregistered( + serviceId: messageSend.serviceId.wrappedValue, + message: message, + thread: messageSend.thread, + transaction: writeTx + ) } } @@ -1085,12 +1093,17 @@ extension MessageSender { messageSend.failure(error) } - func markAddressAsUnregistered(_ address: SignalServiceAddress, - message: TSOutgoingMessage, - thread: TSThread, - transaction: SDSAnyWriteTransaction) { + @nonobjc + func markAsUnregistered( + serviceId: ServiceId, + message: TSOutgoingMessage, + thread: TSThread, + transaction: SDSAnyWriteTransaction + ) { owsAssertDebug(!Thread.isMainThread) + let address = SignalServiceAddress(serviceId) + if thread.isNonContactThread { // Mark as "skipped" group members who no longer have signal accounts. message.update(withSkippedRecipient: address, transaction: transaction) @@ -1186,23 +1199,27 @@ extension MessageSender { extension MessageSender { - @objc(encryptedMessageForMessagePlaintextContent:recipientAddress:deviceId:udSendingParamsProvider:transaction:error:) - func encryptedMessage( - messagePlaintextContent plaintextContent: Data?, - recipientAddress: SignalServiceAddress, + @objc + func encryptMessage( + plaintextContent: Data?, + serviceId: ServiceIdObjC, deviceId: Int32, udSendingParamsProvider: UDSendingParamsProvider?, transaction: SDSAnyWriteTransaction ) throws -> DeviceMessage { owsAssertDebug(!Thread.isMainThread) - owsAssertDebug(recipientAddress.isValid) + let serviceId = serviceId.wrappedValue let signalProtocolStore = signalProtocolStore(for: .aci) - guard signalProtocolStore.sessionStore.containsActiveSession(for: recipientAddress, - deviceId: deviceId, - transaction: transaction) else { - throw MessageSendEncryptionError(recipientAddress: recipientAddress, deviceId: deviceId) + guard + signalProtocolStore.sessionStore.containsActiveSession( + for: serviceId, + deviceId: deviceId, + transaction: transaction + ) + else { + throw MessageSendEncryptionError(recipientAddress: SignalServiceAddress(serviceId), deviceId: deviceId) } guard let plainText = plaintextContent else { throw OWSAssertionError("Missing message content") @@ -1213,36 +1230,37 @@ extension MessageSender { let serializedMessage: Data let messageType: SSKProtoEnvelopeType - let protocolAddress = try ProtocolAddress(from: recipientAddress, deviceId: UInt32(bitPattern: deviceId)) + let protocolAddress = try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: UInt32(bitPattern: deviceId)) - if - let udSendingParamsProvider, - let udSendingAccess = udSendingParamsProvider.udSendingAccess - { + if let udSendingParamsProvider, let udSendingAccess = udSendingParamsProvider.udSendingAccess { let secretCipher = try SMKSecretSessionCipher( sessionStore: signalProtocolStore.sessionStore, preKeyStore: signalProtocolStore.preKeyStore, signedPreKeyStore: signalProtocolStore.signedPreKeyStore, identityStore: identityManager.store(for: .aci, transaction: transaction), - senderKeyStore: Self.senderKeyStore) + senderKeyStore: Self.senderKeyStore + ) serializedMessage = try secretCipher.encryptMessage( - recipient: recipientAddress, + for: serviceId, deviceId: deviceId, paddedPlaintext: paddedPlaintext, contentHint: udSendingParamsProvider.contentHint.signalClientHint, groupId: udSendingParamsProvider.envelopeGroupId(transaction: transaction), senderCertificate: udSendingAccess.senderCertificate, - protocolContext: transaction) + protocolContext: transaction + ) messageType = .unidentifiedSender } else { - let result = try signalEncrypt(message: paddedPlaintext, - for: protocolAddress, - sessionStore: signalProtocolStore.sessionStore, - identityStore: identityManager.store(for: .aci, transaction: transaction), - context: transaction) + let result = try signalEncrypt( + message: paddedPlaintext, + for: protocolAddress, + sessionStore: signalProtocolStore.sessionStore, + identityStore: identityManager.store(for: .aci, transaction: transaction), + context: transaction + ) switch result.messageType { case .whisper: @@ -1277,19 +1295,19 @@ extension MessageSender { ) } - @objc(wrappedPlaintextMessageForMessagePlaintextContent:isResendRequestMessage:recipientAddress:deviceId:udSendingParamsProvider:transaction:error:) - func wrappedPlaintextMessage( - messagePlaintextContent plaintextContent: Data?, - isResendRequestMessage: Bool, - recipientAddress: SignalServiceAddress, + @objc + func wrapPlaintextMessage( + plaintextContent: Data?, + serviceId: ServiceIdObjC, deviceId: Int32, + isResendRequestMessage: Bool, udSendingParamsProvider: UDSendingParamsProvider?, transaction: SDSAnyWriteTransaction ) throws -> DeviceMessage { owsAssertDebug(!Thread.isMainThread) - guard recipientAddress.isValid else { throw OWSAssertionError("Invalid address") } - let protocolAddress = try ProtocolAddress(from: recipientAddress, deviceId: UInt32(bitPattern: deviceId)) + let serviceId = serviceId.wrappedValue + let protocolAddress = try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: UInt32(bitPattern: deviceId)) // Only resend request messages are allowed to use this codepath. guard isResendRequestMessage else { @@ -1302,10 +1320,7 @@ extension MessageSender { let serializedMessage: Data let messageType: SSKProtoEnvelopeType - if - let udSendingParamsProvider, - let udSendingAccess = udSendingParamsProvider.udSendingAccess - { + if let udSendingParamsProvider, let udSendingAccess = udSendingParamsProvider.udSendingAccess { let usmc = try UnidentifiedSenderMessageContent( CiphertextMessage(plaintext), from: udSendingAccess.senderCertificate, @@ -1316,7 +1331,8 @@ extension MessageSender { usmc, for: protocolAddress, identityStore: identityManager.store(for: .aci, transaction: transaction), - context: transaction) + context: transaction + ) serializedMessage = Data(outerBytes) messageType = .unidentifiedSender diff --git a/SignalServiceKit/src/Messages/OWSIdentityManager.swift b/SignalServiceKit/src/Messages/OWSIdentityManager.swift index 326a432bc2..44cfab6d4f 100644 --- a/SignalServiceKit/src/Messages/OWSIdentityManager.swift +++ b/SignalServiceKit/src/Messages/OWSIdentityManager.swift @@ -280,10 +280,15 @@ extension OWSIdentityManager { @objc(shouldSharePhoneNumberWithAddress:transaction:) func shouldSharePhoneNumber(with recipient: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool { - guard let recipientUuid = recipient.uuidString else { + guard let serviceId = recipient.serviceId else { return false } - return shareMyPhoneNumberStore.getBool(recipientUuid, defaultValue: false, transaction: transaction) + return shouldSharePhoneNumber(with: serviceId, transaction: transaction) + } + + func shouldSharePhoneNumber(with serviceId: ServiceId, transaction: SDSAnyReadTransaction) -> Bool { + let uuidString = serviceId.uuidValue.uuidString + return shareMyPhoneNumberStore.getBool(uuidString, defaultValue: false, transaction: transaction) } func setShouldSharePhoneNumber(with recipient: SignalServiceAddress, transaction: SDSAnyWriteTransaction) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index fa09ec117f..d6c0d16f62 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -43,6 +43,9 @@ public class OWSMessageSend: NSObject, UDSendingParamsProvider { @objc public let thread: TSThread + @objc + public let serviceId: ServiceIdObjC + @objc public let address: SignalServiceAddress @@ -79,7 +82,7 @@ public class OWSMessageSend: NSObject, UDSendingParamsProvider { plaintextContent: Data?, plaintextPayloadId: NSNumber?, thread: TSThread, - address: SignalServiceAddress, + serviceId: ServiceIdObjC, udSendingAccess: OWSUDSendingAccess?, localAddress: SignalServiceAddress, sendErrorBlock: ((Error) -> Void)?) { @@ -87,7 +90,8 @@ public class OWSMessageSend: NSObject, UDSendingParamsProvider { self.plaintextContent = plaintextContent self.plaintextPayloadId = plaintextPayloadId?.int64Value self.thread = thread - self.address = address + self.serviceId = serviceId + self.address = SignalServiceAddress(serviceId.wrappedValue) self.localAddress = localAddress self.isLocalAddress = address.isLocalAddress diff --git a/SignalServiceKit/src/Messages/OWSOutgoingResendResponse.m b/SignalServiceKit/src/Messages/OWSOutgoingResendResponse.m index 41728e1b85..37dee5e6a0 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingResendResponse.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingResendResponse.m @@ -118,11 +118,11 @@ } } -- (void)updateWithSentRecipient:(SignalServiceAddress *)recipientAddress +- (void)updateWithSentRecipient:(ServiceIdObjC *)serviceId wasSentByUD:(BOOL)wasSentByUD transaction:(SDSAnyWriteTransaction *)transaction { - [super updateWithSentRecipient:recipientAddress wasSentByUD:wasSentByUD transaction:transaction]; + [super updateWithSentRecipient:serviceId wasSentByUD:wasSentByUD transaction:transaction]; // Message was sent! Re-mark the recipient as having been sent an SKDM if (self.didAppendSKDM) { @@ -133,7 +133,7 @@ if (originalThread.usesSenderKey) { NSError *error = nil; [self.senderKeyStore recordSenderKeySentFor:originalThread - to:recipientAddress + to:serviceId timestamp:self.timestamp writeTx:transaction error:&error]; diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index 9f562139d4..fd230cc509 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -150,12 +150,14 @@ public protocol OWSUDManager: AnyObject { func udAccess(forAddress address: SignalServiceAddress, requireSyncAccess: Bool) -> OWSUDAccess? @objc - func udSendingAccess(forAddress address: SignalServiceAddress, - requireSyncAccess: Bool, - senderCertificates: SenderCertificates) -> OWSUDSendingAccess? + func udSendingAccess( + for serviceId: ServiceIdObjC, + requireSyncAccess: Bool, + senderCertificates: SenderCertificates + ) -> OWSUDSendingAccess? @objc - func storySendingAccess(forAddress address: SignalServiceAddress, senderCertificates: SenderCertificates) -> OWSUDSendingAccess + func storySendingAccess(for serviceId: ServiceIdObjC, senderCertificates: SenderCertificates) -> OWSUDSendingAccess // MARK: Sender Certificate @@ -518,36 +520,43 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { // Returns the UD access key and appropriate sender certificate for sending to a given recipient @objc - public func udSendingAccess(forAddress address: SignalServiceAddress, - requireSyncAccess: Bool, - senderCertificates: SenderCertificates) -> OWSUDSendingAccess? { + public func udSendingAccess( + for serviceId: ServiceIdObjC, + requireSyncAccess: Bool, + senderCertificates: SenderCertificates + ) -> OWSUDSendingAccess? { + let address = SignalServiceAddress(serviceId.wrappedValue) guard let udAccess = self.udAccess(forAddress: address, requireSyncAccess: requireSyncAccess) else { return nil } - - return udSendingAccess(for: address, udAccess: udAccess, senderCertificates: senderCertificates) + return udSendingAccess(for: serviceId.wrappedValue, udAccess: udAccess, senderCertificates: senderCertificates) } @objc - public func storySendingAccess(forAddress address: SignalServiceAddress, senderCertificates: SenderCertificates) -> OWSUDSendingAccess { + public func storySendingAccess(for serviceId: ServiceIdObjC, senderCertificates: SenderCertificates) -> OWSUDSendingAccess { let udAccess = OWSUDAccess(udAccessKey: randomUDAccessKey(), udAccessMode: .unrestricted, isRandomKey: true) - return udSendingAccess(for: address, udAccess: udAccess, senderCertificates: senderCertificates) + return udSendingAccess(for: serviceId.wrappedValue, udAccess: udAccess, senderCertificates: senderCertificates) } - private func udSendingAccess(for address: SignalServiceAddress, udAccess: OWSUDAccess, senderCertificates: SenderCertificates) -> OWSUDSendingAccess { + private func udSendingAccess( + for serviceId: ServiceId, + udAccess: OWSUDAccess, + senderCertificates: SenderCertificates + ) -> OWSUDSendingAccess { databaseStorage.read { transaction in let senderCertificate: SenderCertificate switch phoneNumberSharingMode { case .everybody: senderCertificate = senderCertificates.defaultCert case .contactsOnly: + let address = SignalServiceAddress(serviceId) if Self.contactsManager.isSystemContact(address: address, transaction: transaction) { senderCertificate = senderCertificates.defaultCert break } fallthrough case .nobody: - if identityManager.shouldSharePhoneNumber(with: address, transaction: transaction) { + if identityManager.shouldSharePhoneNumber(with: serviceId, transaction: transaction) { senderCertificate = senderCertificates.defaultCert break } diff --git a/SignalServiceKit/src/Messages/UD/SMKSecretSessionCipher.swift b/SignalServiceKit/src/Messages/UD/SMKSecretSessionCipher.swift index 80d93cc143..cdd7cc10ef 100644 --- a/SignalServiceKit/src/Messages/UD/SMKSecretSessionCipher.swift +++ b/SignalServiceKit/src/Messages/UD/SMKSecretSessionCipher.swift @@ -143,7 +143,7 @@ public class SMKSecretSessionCipher: NSObject { // MARK: - Public public func encryptMessage( - recipient: SignalServiceAddress, + for serviceId: ServiceId, deviceId: Int32, paddedPlaintext: Data, contentHint: UnidentifiedSenderMessageContent.ContentHint, @@ -155,7 +155,7 @@ public class SMKSecretSessionCipher: NSObject { guard deviceId > 0 else { throw SMKError.assertionError(description: "\(logTag) invalid deviceId") } - let recipientAddress = try ProtocolAddress(from: recipient, deviceId: UInt32(bitPattern: deviceId)) + let recipientAddress = try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: UInt32(bitPattern: deviceId)) let ciphertextMessage = try signalEncrypt( message: paddedPlaintext, @@ -320,28 +320,3 @@ public class SMKSecretSessionCipher: NSObject { return Data(plaintextData) } } - -// MARK: - Internal for testing - -extension SMKSecretSessionCipher { - - // Only allow nil contexts for testing - func encryptMessage( - recipient: SignalServiceAddress, - deviceId: Int32, - paddedPlaintext: Data, - contentHint: UnidentifiedSenderMessageContent.ContentHint = .default, - groupId: Data? = nil, - senderCertificate: SenderCertificate, - protocolContext: StoreContext? = nil - ) throws -> Data { - try encryptMessage( - recipient: recipient, - deviceId: deviceId, - paddedPlaintext: paddedPlaintext, - contentHint: contentHint, - groupId: groupId, - senderCertificate: senderCertificate, - protocolContext: protocolContext ?? NullContext()) - } -} diff --git a/SignalServiceKit/src/Storage/AxolotlStore/SSKSessionStore.swift b/SignalServiceKit/src/Storage/AxolotlStore/SSKSessionStore.swift index b475773ffc..d3031e052d 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/SSKSessionStore.swift +++ b/SignalServiceKit/src/Storage/AxolotlStore/SSKSessionStore.swift @@ -89,11 +89,12 @@ public class SSKSessionStore: NSObject { keyValueStore.setObject(dictionary, key: accountId, transaction: transaction) } - @objc(containsActiveSessionForAddress:deviceId:transaction:) - public func containsActiveSession(for address: SignalServiceAddress, - deviceId: Int32, - transaction: SDSAnyReadTransaction) -> Bool { - owsAssertDebug(address.isValid) + public func containsActiveSession( + for serviceId: ServiceId, + deviceId: Int32, + transaction: SDSAnyReadTransaction + ) -> Bool { + let address = SignalServiceAddress(serviceId) guard let accountId = OWSAccountIdFinder.accountId(forAddress: address, transaction: transaction) else { Logger.info("No accountId for: \(address). There must not be a stored session.") return false diff --git a/SignalServiceKit/src/Storage/AxolotlStore/SenderKeyStore.swift b/SignalServiceKit/src/Storage/AxolotlStore/SenderKeyStore.swift index dfc65a5778..2580536826 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/SenderKeyStore.swift +++ b/SignalServiceKit/src/Storage/AxolotlStore/SenderKeyStore.swift @@ -43,30 +43,35 @@ public class SenderKeyStore: NSObject { @objc public func recipientsInNeedOfSenderKey( for thread: TSThread, - addresses: [SignalServiceAddress], + serviceIds: [ServiceIdObjC], readTx: SDSAnyReadTransaction - ) -> [SignalServiceAddress] { - var addressesNeedingSenderKey = Set(addresses) + ) -> [ServiceIdObjC] { + var serviceIdsNeedingSenderKey = Set(serviceIds) storageLock.withLock { // If we haven't saved a distributionId yet, then there's no way we have any keyMetadata cached // All intended recipients will certainly need an SKDM (if they even support sender key) - guard let keyId = keyIdForSendingToThreadId(thread.threadUniqueId, readTx: readTx), - let keyMetadata = getKeyMetadata(for: keyId, readTx: readTx) else { + guard + let keyId = keyIdForSendingToThreadId(thread.threadUniqueId, readTx: readTx), + let keyMetadata = getKeyMetadata(for: keyId, readTx: readTx) + else { return } // Iterate over each cached recipient. If no new devices or reregistrations have occurred since // we last recorded an SKDM send, we can skip sending to them. for (address, sendInfo) in keyMetadata.sentKeyInfo { + guard let serviceId = address.serviceIdObjC else { + continue + } do { let priorSendRecipientState = sendInfo.keyRecipient // Only remove the recipient in question from our send targets if the cached state contains // every device from the current state. Any new devices mean we need to re-send. - let currentRecipientState = try KeyRecipient.currentState(for: address, transaction: readTx) + let currentRecipientState = try KeyRecipient.currentState(for: serviceId.wrappedValue, transaction: readTx) if priorSendRecipientState.containsEveryDevice(from: currentRecipientState) { - addressesNeedingSenderKey.remove(address) + serviceIdsNeedingSenderKey.remove(serviceId) } } catch { // It's likely there's no session for the current recipient. Maybe it was cleared? @@ -79,23 +84,25 @@ public class SenderKeyStore: NSObject { } } } - return Array(addressesNeedingSenderKey) + return Array(serviceIdsNeedingSenderKey) } /// Records that the current sender key for the `thread` has been sent to `participant` @objc public func recordSenderKeySent( for thread: TSThread, - to address: SignalServiceAddress, + to serviceId: ServiceIdObjC, timestamp: UInt64, writeTx: SDSAnyWriteTransaction) throws { try storageLock.withLock { - guard let keyId = keyIdForSendingToThreadId(thread.threadUniqueId, writeTx: writeTx), - let existingMetadata = getKeyMetadata(for: keyId, readTx: writeTx) else { + guard + let keyId = keyIdForSendingToThreadId(thread.threadUniqueId, writeTx: writeTx), + let existingMetadata = getKeyMetadata(for: keyId, readTx: writeTx) + else { throw OWSAssertionError("Failed to look up key metadata") } var updatedMetadata = existingMetadata - try updatedMetadata.recordSKDMSent(at: timestamp, address: address, transaction: writeTx) + try updatedMetadata.recordSKDMSent(at: timestamp, serviceId: serviceId.wrappedValue, transaction: writeTx) setMetadata(updatedMetadata, writeTx: writeTx) } } @@ -422,35 +429,46 @@ private struct KeyRecipient: Codable, Dependencies { } static func == (lhs: Device, rhs: Device) -> Bool { - // We can only be sure that a device hasn't changed if the registrationIds are the same - // If either registrationId is nil, that means the Device was constructed before we had a session - // established for the device. + // We can only be sure that a device hasn't changed if the registrationIds + // are the same. If either registrationId is nil, that means the Device was + // constructed before we had a session established for the device. // - // If we end up trying to send a SenderKey message to a device without a session, this ensures that - // this ensures that we will always send an SKDM to that device. A session will be created in order to - // send the SKDM, so by the time we're ready to mark success we should have something to store. + // If we end up trying to send a SenderKey message to a device without a + // session, this ensures that we will always send an SKDM to that device. A + // session will be created in order to send the SKDM, so by the time we're + // ready to mark success we should have something to store. guard lhs.registrationId != nil, rhs.registrationId != nil else { return false } return lhs.registrationId == rhs.registrationId && lhs.deviceId == rhs.deviceId } } - let ownerAddress: SignalServiceAddress + enum CodingKeys: String, CodingKey { + case devices + + // We previously stored "ownerAddress" on the recipient. This is redundant + // because "sentKeyInfo" stores the same value, and that's the one we use. + } + let devices: Set - private init(ownerAddress: SignalServiceAddress, devices: Set) { - self.ownerAddress = ownerAddress + + private init(devices: Set) { self.devices = devices } /// Build a KeyRecipient for the given address by fetching all of the devices and corresponding registrationIds - static func currentState(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) throws -> KeyRecipient { + static func currentState(for serviceId: ServiceId, transaction: SDSAnyReadTransaction) throws -> KeyRecipient { guard - let recipient = SignalRecipient.get(address: address, mustHaveDevices: false, transaction: transaction), + let recipient = SignalRecipient.get( + address: SignalServiceAddress(serviceId), + mustHaveDevices: false, + transaction: transaction + ), let deviceIds = recipient.deviceIds else { throw OWSAssertionError("Invalid device array") } - let protocolAddresses = try deviceIds.map { try ProtocolAddress(from: address, deviceId: $0) } + let protocolAddresses = try deviceIds.map { try ProtocolAddress(uuid: serviceId.uuidValue, deviceId: $0) } let sessionStore = signalProtocolStore(for: .aci).sessionStore let devices: [Device] = try protocolAddresses.map { // We have to fetch the registrationId since deviceIds can be reused. @@ -464,16 +482,11 @@ private struct KeyRecipient: Codable, Dependencies { return Device(deviceId: $0.deviceId, registrationId: registrationId) } - return KeyRecipient(ownerAddress: address, devices: Set(devices)) + return KeyRecipient(devices: Set(devices)) } /// Returns `true` as long as the argument does not contain any devices that are unknown to the receiver func containsEveryDevice(from other: KeyRecipient) -> Bool { - guard ownerAddress == other.ownerAddress else { - owsFailDebug("Address mismatch") - return false - } - let newDevices = other.devices.subtracting(self.devices) return newDevices.isEmpty } @@ -541,10 +554,10 @@ private struct KeyMetadata { sentKeyInfo[address] = nil } - mutating func recordSKDMSent(at timestamp: UInt64, address: SignalServiceAddress, transaction: SDSAnyReadTransaction) throws { - let recipient = try KeyRecipient.currentState(for: address, transaction: transaction) + mutating func recordSKDMSent(at timestamp: UInt64, serviceId: ServiceId, transaction: SDSAnyReadTransaction) throws { + let recipient = try KeyRecipient.currentState(for: serviceId, transaction: transaction) let sendInfo = SKDMSendInfo(skdmTimestamp: timestamp, keyRecipient: recipient) - sentKeyInfo[address] = sendInfo + sentKeyInfo[SignalServiceAddress(serviceId)] = sendInfo } } diff --git a/SignalServiceKit/tests/ChangePhoneNumber/ChangePhoneNumberPniManagerTest+Mocks.swift b/SignalServiceKit/tests/ChangePhoneNumber/ChangePhoneNumberPniManagerTest+Mocks.swift index 2cd32e8f49..a63003a28e 100644 --- a/SignalServiceKit/tests/ChangePhoneNumber/ChangePhoneNumberPniManagerTest+Mocks.swift +++ b/SignalServiceKit/tests/ChangePhoneNumber/ChangePhoneNumberPniManagerTest+Mocks.swift @@ -54,12 +54,12 @@ class _ChangePhoneNumberPniManager_MessageSenderMock: _ChangePhoneNumberPniManag func buildDeviceMessage( forMessagePlaintextContent messagePlaintextContent: Data?, messageEncryptionStyle: EncryptionStyle, - recipientServiceId: ServiceId, - recipientAccountId: String, - recipientDeviceId: NSNumber, + recipientId: String, + serviceId: ServiceId, + deviceId: NSNumber, isOnlineMessage: Bool, isTransientSenderKeyDistributionMessage: Bool, - isStorySendMessage: Bool, + isStoryMessage: Bool, isResendRequestMessage: Bool, udSendingParamsProvider: UDSendingParamsProvider? ) throws -> DeviceMessage? { @@ -74,7 +74,7 @@ class _ChangePhoneNumberPniManager_MessageSenderMock: _ChangePhoneNumberPniManag case let .valid(registrationId): return DeviceMessage( type: .ciphertext, - destinationDeviceId: recipientDeviceId.uint32Value, + destinationDeviceId: deviceId.uint32Value, destinationRegistrationId: registrationId, serializedMessage: Cryptography.generateRandomBytes(32) ) diff --git a/SignalServiceKit/tests/Messages/DisappearingMessageFinderTest.swift b/SignalServiceKit/tests/Messages/DisappearingMessageFinderTest.swift index 2591653235..70df851548 100644 --- a/SignalServiceKit/tests/Messages/DisappearingMessageFinderTest.swift +++ b/SignalServiceKit/tests/Messages/DisappearingMessageFinderTest.swift @@ -21,13 +21,11 @@ final class DisappearingMessageFinderTest: SSKBaseTestSwift { SignalServiceAddress(phoneNumber: "+12225550123") } - func otherAddress() -> SignalServiceAddress { - SignalServiceAddress(phoneNumber: "+13335550198") - } + private lazy var otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+13335550198") func thread(with transaction: SDSAnyWriteTransaction) -> TSThread { TSContactThread.getOrCreateThread( - withContactAddress: otherAddress(), + withContactAddress: otherAddress, transaction: transaction ) } @@ -48,7 +46,7 @@ final class DisappearingMessageFinderTest: SSKBaseTestSwift { let incomingMessageBuilder = TSIncomingMessageBuilder(thread: thread, messageBody: body) incomingMessageBuilder.timestamp = 1 - incomingMessageBuilder.authorAddress = otherAddress() + incomingMessageBuilder.authorAddress = otherAddress incomingMessageBuilder.expiresInSeconds = expiresInSeconds let message = incomingMessageBuilder.build() message.anyInsert(transaction: transaction) @@ -215,12 +213,12 @@ final class DisappearingMessageFinderTest: SSKBaseTestSwift { write { transaction in // Mark outgoing message as "sent", "delivered" or "delivered and read" using production methods. expiringSentOutgoingMessage.update( - withSentRecipient: otherAddress(), + withSentRecipient: otherAddress.serviceIdObjC!, wasSentByUD: false, transaction: transaction ) expiringDeliveredOutgoingMessage.update( - withDeliveredRecipient: otherAddress(), + withDeliveredRecipient: otherAddress, recipientDeviceId: 0, deliveryTimestamp: Date.ows_millisecondTimestamp(), context: PassthroughDeliveryReceiptContext(), @@ -228,7 +226,7 @@ final class DisappearingMessageFinderTest: SSKBaseTestSwift { ) let nowMs = Date.ows_millisecondTimestamp() expiringDeliveredAndReadOutgoingMessage.update( - withReadRecipient: otherAddress(), + withReadRecipient: otherAddress, recipientDeviceId: 0, readTimestamp: nowMs, transaction: transaction diff --git a/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.swift b/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.swift index a3f2ebfeaa..272dd2f18f 100644 --- a/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.swift +++ b/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.swift @@ -28,7 +28,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testShouldNotStartExpireTimerWithMessageThatDoesNotExpire() { write { transaction in - let otherAddress = SignalServiceAddress(phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil) messageBuilder.timestamp = 100 @@ -36,7 +37,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { XCTAssertFalse(message.shouldStartExpireTimer()) - message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: false, transaction: transaction) XCTAssertFalse(message.shouldStartExpireTimer()) } @@ -44,7 +45,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testShouldStartExpireTimerWithSentMessage() { write { transaction in - let otherAddress = SignalServiceAddress(phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil) messageBuilder.timestamp = 100 @@ -53,7 +55,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { XCTAssertFalse(message.shouldStartExpireTimer()) - message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: false, transaction: transaction) XCTAssertTrue(message.shouldStartExpireTimer()) } @@ -112,7 +114,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testReceiptClearsSharePhoneNumber() { write { transaction in - let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) @@ -121,7 +124,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { let message = messageBuilder.build(transaction: transaction) let messageData = message.buildPlainTextData(thread, transaction: transaction)! - message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: true, transaction: transaction) let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64 MessageSendLog.recordPendingDelivery(payloadId: payloadId, @@ -145,7 +148,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testReceiptClearsSharePhoneNumberOnlyOnLastDevice() { write { transaction in - let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) @@ -154,7 +158,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { let message = messageBuilder.build(transaction: transaction) let messageData = message.buildPlainTextData(thread, transaction: transaction)! - message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: true, transaction: transaction) let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64 MessageSendLog.recordPendingDelivery(payloadId: payloadId, @@ -193,7 +197,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testReceiptDoesNotClearSharePhoneNumberIfNotSealedSender() { write { transaction in - let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) @@ -202,7 +207,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { let message = messageBuilder.build(transaction: transaction) let messageData = message.buildPlainTextData(thread, transaction: transaction)! - message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: false, transaction: transaction) let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64 MessageSendLog.recordPendingDelivery(payloadId: payloadId, @@ -227,7 +232,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { func testReceiptDoesNotClearSharePhoneNumberIfNoPniSignature() { write { transaction in - let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction) let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil) @@ -235,7 +241,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { let message = messageBuilder.build(transaction: transaction) let messageData = message.buildPlainTextData(thread, transaction: transaction)! - message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: true, transaction: transaction) let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64 MessageSendLog.recordPendingDelivery(payloadId: payloadId, @@ -260,7 +266,8 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { } func testReceiptDoesNotClearSharePhoneNumberIfPniHasChanged() { - let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444") + let otherServiceId = ServiceId(UUID()) + let otherAddress = SignalServiceAddress(uuid: otherServiceId.uuidValue, phoneNumber: "+12223334444") var message: TSOutgoingMessage! write { transaction in @@ -272,7 +279,7 @@ class TSOutgoingMessageTest: SSKBaseTestSwift { message = messageBuilder.build(transaction: transaction) let messageData = message.buildPlainTextData(thread, transaction: transaction)! - message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction) + message.update(withSentRecipient: ServiceIdObjC(otherServiceId), wasSentByUD: true, transaction: transaction) let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64 MessageSendLog.recordPendingDelivery(payloadId: payloadId, diff --git a/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift b/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift index 8fd85453fb..e8f8f7f9f6 100644 --- a/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift +++ b/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift @@ -226,7 +226,7 @@ class OWSUDManagerTest: SSKBaseTestSwift { // Ensure UD is enabled by setting our own access level to enabled. udManagerImpl.setUnidentifiedAccessMode(.enabled, address: localAddress) - let bobRecipientAddress = SignalServiceAddress(phoneNumber: "+13213214322") + let bobRecipientAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+13213214322") XCTAssertFalse(bobRecipientAddress.isLocalAddress) write { transaction in self.profileManager.setProfileKeyData(OWSAES256Key.generateRandom().keyData, @@ -239,9 +239,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(.unknown, sendingAccess.udAccess.udAccessMode) XCTAssertFalse(sendingAccess.udAccess.isRandomKey) XCTAssertEqual(sendingAccess.senderCertificate.serialize(), @@ -258,9 +260,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(.unknown, sendingAccess.udAccess.udAccessMode) XCTAssertFalse(sendingAccess.udAccess.isRandomKey) XCTAssertEqual(sendingAccess.senderCertificate.serialize(), @@ -277,9 +281,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(.unknown, sendingAccess.udAccess.udAccessMode) XCTAssertFalse(sendingAccess.udAccess.isRandomKey) XCTAssertEqual(sendingAccess.senderCertificate.serialize(), @@ -292,9 +298,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(.unknown, sendingAccess.udAccess.udAccessMode) XCTAssertFalse(sendingAccess.udAccess.isRandomKey) XCTAssertEqual(sendingAccess.senderCertificate.serialize(), @@ -329,9 +337,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(sendingAccess.senderCertificate.serialize(), self.defaultSenderCert.serialize()) }.expect(timeout: 1.0) @@ -346,9 +356,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(sendingAccess.senderCertificate.serialize(), self.defaultSenderCert.serialize()) }.expect(timeout: 1.0) @@ -363,9 +375,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(sendingAccess.senderCertificate.serialize(), self.defaultSenderCert.serialize()) }.expect(timeout: 1.0) @@ -383,9 +397,11 @@ class OWSUDManagerTest: SSKBaseTestSwift { firstly { udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict) }.done { senderCertificates in - let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, - requireSyncAccess: false, - senderCertificates: senderCertificates)! + let sendingAccess = self.udManagerImpl.udSendingAccess( + for: bobRecipientAddress.serviceIdObjC!, + requireSyncAccess: false, + senderCertificates: senderCertificates + )! XCTAssertEqual(sendingAccess.senderCertificate.serialize(), self.uuidOnlySenderCert.serialize()) }.expect(timeout: 1.0) diff --git a/SignalServiceKit/tests/Messages/SMKSecretSessionCipherTest.swift b/SignalServiceKit/tests/Messages/SMKSecretSessionCipherTest.swift index fa2b974f6e..febd659e29 100644 --- a/SignalServiceKit/tests/Messages/SMKSecretSessionCipherTest.swift +++ b/SignalServiceKit/tests/Messages/SMKSecretSessionCipherTest.swift @@ -43,10 +43,15 @@ class SMKSecretSessionCipherTest: SSKBaseTestSwift { // senderCertificate, "smert za smert".getBytes()); // NOTE: The java tests don't bother padding the plaintext. let alicePlaintext = "smert za smert".data(using: String.Encoding.utf8)! - let ciphertext = try! aliceCipher.encryptMessage(recipient: bobMockClient.address, - deviceId: bobMockClient.deviceId, - paddedPlaintext: alicePlaintext, - senderCertificate: senderCertificate) + let ciphertext = try! aliceCipher.encryptMessage( + for: bobMockClient.address.serviceId!, + deviceId: bobMockClient.deviceId, + paddedPlaintext: alicePlaintext, + contentHint: .default, + groupId: nil, + senderCertificate: senderCertificate, + protocolContext: NullContext() + ) // SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1)); let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher() @@ -100,12 +105,15 @@ class SMKSecretSessionCipherTest: SSKBaseTestSwift { let alicePlaintext = "и вот я".data(using: String.Encoding.utf8)! let aliceGroupId = Randomness.generateRandomBytes(6) let aliceContentHint = UnidentifiedSenderMessageContent.ContentHint.implicit - let ciphertext = try! aliceCipher.encryptMessage(recipient: bobMockClient.address, - deviceId: bobMockClient.deviceId, - paddedPlaintext: alicePlaintext, - contentHint: aliceContentHint, - groupId: aliceGroupId, - senderCertificate: senderCertificate) + let ciphertext = try! aliceCipher.encryptMessage( + for: bobMockClient.address.serviceId!, + deviceId: bobMockClient.deviceId, + paddedPlaintext: alicePlaintext, + contentHint: aliceContentHint, + groupId: aliceGroupId, + senderCertificate: senderCertificate, + protocolContext: NullContext() + ) // SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore); let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher() @@ -179,12 +187,15 @@ class SMKSecretSessionCipherTest: SSKBaseTestSwift { let aliceGroupId = Randomness.generateRandomBytes(6) let aliceContentHint = UnidentifiedSenderMessageContent.ContentHint.resendable - let ciphertext = try! aliceCipher.encryptMessage(recipient: bobMockClient.address, - deviceId: bobMockClient.deviceId, - paddedPlaintext: alicePlaintext, - contentHint: aliceContentHint, - groupId: aliceGroupId, - senderCertificate: senderCertificate) + let ciphertext = try! aliceCipher.encryptMessage( + for: bobMockClient.address.serviceId!, + deviceId: bobMockClient.deviceId, + paddedPlaintext: alicePlaintext, + contentHint: aliceContentHint, + groupId: aliceGroupId, + senderCertificate: senderCertificate, + protocolContext: NullContext() + ) // SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore); let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher() @@ -257,10 +268,15 @@ class SMKSecretSessionCipherTest: SSKBaseTestSwift { // senderCertificate, "smert za smert".getBytes()); // NOTE: The java tests don't bother padding the plaintext. let alicePlaintext = "smert za smert".data(using: String.Encoding.utf8)! - let ciphertext = try! aliceCipher.encryptMessage(recipient: bobMockClient.address, - deviceId: bobMockClient.deviceId, - paddedPlaintext: alicePlaintext, - senderCertificate: senderCertificate) + let ciphertext = try! aliceCipher.encryptMessage( + for: bobMockClient.address.serviceId!, + deviceId: bobMockClient.deviceId, + paddedPlaintext: alicePlaintext, + contentHint: .default, + groupId: nil, + senderCertificate: senderCertificate, + protocolContext: NullContext() + ) // SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore); let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()