diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 7ea9ff6e66..c701ea67c6 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -775,7 +775,7 @@ 506A924F28FA232100A2EFC2 /* ContactDiscoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506A924E28FA232100A2EFC2 /* ContactDiscoveryManager.swift */; }; 506A92512900888900A2EFC2 /* RecipientPickerContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506A92502900888900A2EFC2 /* RecipientPickerContainerViewController.swift */; }; 506A9252290326A600A2EFC2 /* TextHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextHelper.swift */; }; - 5075004628B09CE6001922C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; }; + 5075004628B09CE6001922C9 /* ContactDiscoveryE164CollectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryE164CollectionTest.swift */; }; 507CD5E529660D5100E47DAC /* ServiceId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CD5E429660D5100E47DAC /* ServiceId.swift */; }; 508F0346296F72F4001D88D0 /* CustomCellBackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508F0345296F72F4001D88D0 /* CustomCellBackgroundColor.swift */; }; 509913BE2913274100F34F8E /* PhoneNumberFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509913BD2913274100F34F8E /* PhoneNumberFinderTests.swift */; }; @@ -1738,7 +1738,6 @@ F9C5CCBB289453B300548EEE /* PhoneNumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D3289453B100548EEE /* PhoneNumberUtil.m */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CCBC289453B300548EEE /* PhoneNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D4289453B100548EEE /* PhoneNumber.m */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CCBD289453B300548EEE /* UUIDBackfillTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D6289453B100548EEE /* UUIDBackfillTask.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; - F9C5CCBE289453B300548EEE /* SGXContactDiscoveryOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D7289453B100548EEE /* SGXContactDiscoveryOperation.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CCC0289453B300548EEE /* ContactDiscoveryTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9D9289453B100548EEE /* ContactDiscoveryTask.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CCC3289453B300548EEE /* ContactDiscoveryOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9DC289453B100548EEE /* ContactDiscoveryOperation.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CCC4289453B300548EEE /* OWSDisappearingMessagesConfiguration+SDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5C9DD289453B100548EEE /* OWSDisappearingMessagesConfiguration+SDS.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; @@ -1989,7 +1988,6 @@ F9C5CDBE289453B400548EEE /* RESTNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAEB289453B200548EEE /* RESTNetworkManager.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CDBF289453B400548EEE /* OWSUploadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAEC289453B200548EEE /* OWSUploadOperation.m */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CDC0289453B400548EEE /* RESTNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAED289453B200548EEE /* RESTNetworkManager.m */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; - F9C5CDC1289453B400548EEE /* ContactDiscoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAEE289453B200548EEE /* ContactDiscoveryService.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CDC2289453B400548EEE /* HTTPEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAEF289453B200548EEE /* HTTPEntities.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CDC3289453B400548EEE /* DeviceProvisioningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAF0289453B200548EEE /* DeviceProvisioningService.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; F9C5CDC4289453B400548EEE /* NetworkInterfaceSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CAF1289453B200548EEE /* NetworkInterfaceSet.swift */; settings = {COMPILER_FLAGS = "-fcxx-modules"; }; }; @@ -3118,7 +3116,7 @@ 4C42960F231A1AA400D9D240 /* MessageSendingPerformanceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendingPerformanceTest.swift; sourceTree = ""; }; 4C46361022EB98EC00185951 /* CameraFirstCaptureSendFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFirstCaptureSendFlow.swift; sourceTree = ""; }; 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = ""; }; - 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryOperationTest.swift; sourceTree = ""; }; + 4C4BC6C22102D697004040C9 /* ContactDiscoveryE164CollectionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryE164CollectionTest.swift; sourceTree = ""; }; 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = ""; }; 4C5250D321E7C51900CE3D95 /* PhoneNumberValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidatorTest.swift; sourceTree = ""; }; 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; @@ -4256,7 +4254,6 @@ F9C5C9D3289453B100548EEE /* PhoneNumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberUtil.m; sourceTree = ""; }; F9C5C9D4289453B100548EEE /* PhoneNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumber.m; sourceTree = ""; }; F9C5C9D6289453B100548EEE /* UUIDBackfillTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UUIDBackfillTask.swift; sourceTree = ""; }; - F9C5C9D7289453B100548EEE /* SGXContactDiscoveryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SGXContactDiscoveryOperation.swift; sourceTree = ""; }; F9C5C9D9289453B100548EEE /* ContactDiscoveryTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryTask.swift; sourceTree = ""; }; F9C5C9DC289453B100548EEE /* ContactDiscoveryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryOperation.swift; sourceTree = ""; }; F9C5C9DD289453B100548EEE /* OWSDisappearingMessagesConfiguration+SDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OWSDisappearingMessagesConfiguration+SDS.swift"; sourceTree = ""; }; @@ -4507,7 +4504,6 @@ F9C5CAEB289453B200548EEE /* RESTNetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTNetworkManager.swift; sourceTree = ""; }; F9C5CAEC289453B200548EEE /* OWSUploadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUploadOperation.m; sourceTree = ""; }; F9C5CAED289453B200548EEE /* RESTNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RESTNetworkManager.m; sourceTree = ""; }; - F9C5CAEE289453B200548EEE /* ContactDiscoveryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactDiscoveryService.swift; sourceTree = ""; }; F9C5CAEF289453B200548EEE /* HTTPEntities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPEntities.swift; sourceTree = ""; }; F9C5CAF0289453B200548EEE /* DeviceProvisioningService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceProvisioningService.swift; sourceTree = ""; }; F9C5CAF1289453B200548EEE /* NetworkInterfaceSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkInterfaceSet.swift; sourceTree = ""; }; @@ -6291,8 +6287,8 @@ 5075004528B09CCC001922C9 /* Discovery */ = { isa = PBXGroup; children = ( + 4C4BC6C22102D697004040C9 /* ContactDiscoveryE164CollectionTest.swift */, 50169694291B0627007AD709 /* ContactDiscoveryManagerTest.swift */, - 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */, 500824CF292741D5005A5DC0 /* ContactDiscoveryV2OperationTest.swift */, ); path = Discovery; @@ -8444,7 +8440,6 @@ F9C5C9D9289453B100548EEE /* ContactDiscoveryTask.swift */, 5049FA2E28BEAABE00D6E099 /* ContactDiscoveryV2Connection.swift */, 5049FA2D28BEAABE00D6E099 /* ContactDiscoveryV2Operation.swift */, - F9C5C9D7289453B100548EEE /* SGXContactDiscoveryOperation.swift */, F9C5C9D6289453B100548EEE /* UUIDBackfillTask.swift */, ); path = Discovery; @@ -8813,7 +8808,6 @@ children = ( F9C5CADB289453B200548EEE /* Giphy */, F9C5CAE1289453B200548EEE /* Requests */, - F9C5CAEE289453B200548EEE /* ContactDiscoveryService.swift */, 505C2ED529971D4E00C23FB2 /* DeviceLimitExceededError.swift */, F9C5CAF0289453B200548EEE /* DeviceProvisioningService.swift */, F9C5CAEF289453B200548EEE /* HTTPEntities.swift */, @@ -11465,7 +11459,6 @@ F9C5CCE4289453B300548EEE /* Contact.swift in Sources */, 506A924F28FA232100A2EFC2 /* ContactDiscoveryManager.swift in Sources */, F9C5CCC3289453B300548EEE /* ContactDiscoveryOperation.swift in Sources */, - F9C5CDC1289453B400548EEE /* ContactDiscoveryService.swift in Sources */, F9C5CCC0289453B300548EEE /* ContactDiscoveryTask.swift in Sources */, 5049FA3028BEAABE00D6E099 /* ContactDiscoveryV2Connection.swift in Sources */, 5049FA2F28BEAABE00D6E099 /* ContactDiscoveryV2Operation.swift in Sources */, @@ -11872,7 +11865,6 @@ F9C5CC96289453B300548EEE /* SessionRecord.pb.swift in Sources */, F9C5CD35289453B300548EEE /* SessionRecordMigration.swift in Sources */, 1700E34128BD41150073D949 /* SetAlgebra+SSK.swift in Sources */, - F9C5CCBE289453B300548EEE /* SGXContactDiscoveryOperation.swift in Sources */, F9C5CCC6289453B300548EEE /* SignalAccount+SDS.swift in Sources */, F9C5CCD9289453B300548EEE /* SignalAccount.m in Sources */, F9C5CCC5289453B300548EEE /* SignalAccount.swift in Sources */, @@ -12069,8 +12061,8 @@ F9426273289B1B5500460798 /* BlockingManagerStateTests.swift in Sources */, F9426283289B1B5600460798 /* BlockingManagerTests.swift in Sources */, 661396AF28BE881E00E0C4DF /* ChainedPromiseTest.swift in Sources */, + 5075004628B09CE6001922C9 /* ContactDiscoveryE164CollectionTest.swift in Sources */, 50169695291B0627007AD709 /* ContactDiscoveryManagerTest.swift in Sources */, - 5075004628B09CE6001922C9 /* ContactDiscoveryOperationTest.swift in Sources */, 500824D0292741D5005A5DC0 /* ContactDiscoveryV2OperationTest.swift in Sources */, F962B38C293F9F9F00765BD8 /* CRC32Test.swift in Sources */, 509BBF7A28CA556700F4D8A0 /* Data+SSKTest.swift in Sources */, diff --git a/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryOperation.swift b/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryOperation.swift index 80319c38a6..c4b1338cdd 100644 --- a/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryOperation.swift +++ b/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryOperation.swift @@ -78,15 +78,8 @@ public class ContactDiscoveryError: NSError, UserErrorDescriptionProvider { case generic = 1 case assertion - /// An error indicating that the current user has an expired auth token - case unauthorized - /// An error indicating that a hardcoded resource is unavailable. Best recourse is to update the app. - case unexpectedResponse - /// An error indicating response timeout. - case timeout /// An error indicating that the failure was because of a rate limit case rateLimit - /// Any generic 4xx error that doesn't fit in the above categories case genericClientError /// Any generic 5xx error that doesn't fit in the above categories diff --git a/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryTask.swift b/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryTask.swift index 361c0994a6..68c382fbfc 100644 --- a/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryTask.swift +++ b/SignalServiceKit/src/Contacts/Discovery/ContactDiscoveryTask.swift @@ -42,11 +42,7 @@ final class ContactDiscoveryTaskQueueImpl: ContactDiscoveryTaskQueue, Dependenci } private static func createContactDiscoveryOperation(for e164s: Set, mode: ContactDiscoveryMode) -> ContactDiscoveryOperation { - if !RemoteConfig.contactDiscoveryV2KillSwitch { - return ContactDiscoveryV2CompatibilityOperation(e164sToLookup: e164s, mode: mode) - } else { - return SGXContactDiscoveryOperation(e164sToLookup: e164s, mode: mode) - } + return ContactDiscoveryV2CompatibilityOperation(e164sToLookup: e164s, mode: mode) } private static func storeResults( diff --git a/SignalServiceKit/src/Contacts/Discovery/SGXContactDiscoveryOperation.swift b/SignalServiceKit/src/Contacts/Discovery/SGXContactDiscoveryOperation.swift deleted file mode 100644 index a117900cf4..0000000000 --- a/SignalServiceKit/src/Contacts/Discovery/SGXContactDiscoveryOperation.swift +++ /dev/null @@ -1,232 +0,0 @@ -// -// Copyright 2018 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import Foundation -import LibSignalClient - -struct CDSRegisteredContact: Hashable { - let signalUuid: UUID - let e164PhoneNumber: E164 -} - -/// Fetches contact info from the ContactDiscoveryService -/// Intended to be used by ContactDiscoveryTaskQueue. You probably don't want to use this directly. -class SGXContactDiscoveryOperation: ContactDiscoveryOperation { - static let batchSize = 2048 - - private let e164sToLookup: Set - required init(e164sToLookup: Set, mode: ContactDiscoveryMode) { - self.e164sToLookup = e164sToLookup - Logger.debug("with e164sToLookup.count: \(e164sToLookup.count)") - } - - func perform(on queue: DispatchQueue) -> Promise> { - firstly { () -> Promise<[Set]> in - // First, build a bunch of batch Promises - let batchOperationPromises = Array(e164sToLookup) - .chunked(by: Self.batchSize) - .map { makeContactDiscoveryRequest(e164sToLookup: Array($0)) } - - // Then, wait for them all to be fulfilled before joining the subsets together - return Promise.when(fulfilled: batchOperationPromises) - - }.map(on: queue) { (setArray) -> Set in - setArray.reduce(into: Set()) { (builder, cdsContactSubset) in - builder.formUnion(cdsContactSubset.map { - DiscoveredContactInfo(e164: $0.e164PhoneNumber, uuid: $0.signalUuid) - }) - } - }.recover(on: queue) { error -> Promise> in - throw Self.prepareExternalError(from: error) - } - } - - // Below, we have a bunch of then blocks being performed on a global concurrent queue - // It might be worthwhile to audit and see if we can move these onto the queue passed into `perform(on:)` - - private func makeContactDiscoveryRequest(e164sToLookup: [E164]) -> Promise> { - firstly { () -> Promise in - RemoteAttestation.performForCDS() - - }.then(on: DispatchQueue.global()) { (attestation: RemoteAttestation.CDSAttestation) -> Promise<(RemoteAttestation.CDSAttestation, ContactDiscoveryService.IntersectionResponse)> in - let service = ContactDiscoveryService() - let query = try self.buildIntersectionQuery(e164sToLookup: e164sToLookup, - remoteAttestations: attestation.remoteAttestations) - return service.getRegisteredSignalUsers( - query: query, - cookies: attestation.cookies, - authUsername: attestation.auth.username, - authPassword: attestation.auth.password, - enclaveName: attestation.enclaveConfig.enclaveName, - host: attestation.enclaveConfig.host, - censorshipCircumventionPrefix: attestation.enclaveConfig.censorshipCircumventionPrefix - ).map {(attestation, $0)} - - }.map(on: DispatchQueue.global()) { attestation, response -> Set in - let allEnclaveAttestations = attestation.remoteAttestations - let respondingEnclaveAttestation = allEnclaveAttestations.first(where: { $1.requestId == response.requestId }) - - guard let responseAttestion = respondingEnclaveAttestation?.value else { - throw ContactDiscoveryError.assertionError(description: "Invalid responding enclave for requestId: \(response.requestId)") - } - - let plaintext = try Aes256GcmEncryptedData( - nonce: response.iv, - ciphertext: response.data, - authenticationTag: response.mac - ).decrypt(key: responseAttestion.keys.serverKey.keyData) - - // 16 bytes per UUID - let contactCount = UInt(e164sToLookup.count) - guard plaintext.count == contactCount * 16 else { - throw ContactDiscoveryError.assertionError(description: "failed check: invalid byte count") - } - let dataParser = OWSDataParser(data: plaintext) - let uuidsData = try dataParser.nextData(length: contactCount * 16, name: "uuids") - - guard dataParser.isEmpty else { - throw ContactDiscoveryError.assertionError(description: "failed check: dataParse.isEmpty") - } - - let uuids = type(of: self).uuidArray(from: uuidsData) - - guard uuids.count == contactCount else { - throw ContactDiscoveryError.assertionError(description: "failed check: uuids.count == contactCount") - } - - let unregisteredUuid = UUID(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - - var registeredContacts: Set = Set() - - for (index, e164PhoneNumber) in e164sToLookup.enumerated() { - let uuid = uuids[index] - guard uuid != unregisteredUuid else { - Logger.verbose("not a signal user: \(e164PhoneNumber)") - continue - } - - Logger.verbose("Signal user. e164: \(e164PhoneNumber), uuid: \(uuid)") - registeredContacts.insert(CDSRegisteredContact(signalUuid: uuid, e164PhoneNumber: e164PhoneNumber)) - } - - return registeredContacts - } - } - - func buildIntersectionQuery(e164sToLookup: [E164], remoteAttestations: [RemoteAttestation.CDSAttestation.Id: RemoteAttestation]) throws -> ContactDiscoveryService.IntersectionQuery { - let noncePlainTextData = Randomness.generateRandomBytes(32) - let addressPlainTextData = ContactDiscoveryE164Collection(e164sToLookup).encodedValues - let queryData = Data.join([noncePlainTextData, addressPlainTextData]) - - let key = OWSAES256Key.generateRandom() - let encryptionResult = try Aes256GcmEncryptedData.encrypt(queryData, key: key.keyData) - assert(encryptionResult.ciphertext.count == e164sToLookup.count * 8 + 32) - - let queryEnvelopes: [RemoteAttestation.CDSAttestation.Id: ContactDiscoveryService.IntersectionQuery.EnclaveEnvelope] = try remoteAttestations.mapValues { remoteAttestation in - let perEnclaveKey = try Aes256GcmEncryptedData.encrypt( - key.keyData, - key: remoteAttestation.keys.clientKey.keyData, - associatedData: remoteAttestation.requestId - ) - - return ContactDiscoveryService.IntersectionQuery.EnclaveEnvelope( - requestId: remoteAttestation.requestId, - data: perEnclaveKey.ciphertext, - iv: perEnclaveKey.nonce, - mac: perEnclaveKey.authenticationTag - ) - } - - guard let commitment = Cryptography.computeSHA256Digest(queryData) else { - throw ContactDiscoveryError.assertionError(description: "commitment was unexpectedly nil") - } - - return ContactDiscoveryService.IntersectionQuery( - addressCount: UInt(e164sToLookup.count), - commitment: commitment, - data: encryptionResult.ciphertext, - iv: encryptionResult.nonce, - mac: encryptionResult.authenticationTag, - envelopes: queryEnvelopes - ) - } - - class func uuidArray(from data: Data) -> [UUID] { - return data.withUnsafeBytes { - [uuid_t]($0.bindMemory(to: uuid_t.self)) - .map { UUID(uuid: $0) } - } - } - - /// Parse the error and, if appropriate, construct an error appropriate to return upwards - /// May return the provided error unchanged. - class func prepareExternalError(from error: Error) -> Error { - // Network connectivity failures should never be re-wrapped - if error.isNetworkConnectivityFailure { - return error - } - - let retryAfterDate = error.httpRetryAfterDate.map { min($0, Date(timeIntervalSinceNow: 60 * kMinuteInterval)) } - - if let statusCode = error.httpStatusCode { - switch statusCode { - case 401: - return ContactDiscoveryError( - kind: .unauthorized, - debugDescription: "User is unauthorized", - retryable: false, - retryAfterDate: retryAfterDate) - case 404: - return ContactDiscoveryError( - kind: .unexpectedResponse, - debugDescription: "Unknown enclaveID", - retryable: false, - retryAfterDate: retryAfterDate) - case 408: - return ContactDiscoveryError( - kind: .timeout, - debugDescription: "Server rejected due to a timeout", - retryable: true, - retryAfterDate: retryAfterDate) - case 409: - // Conflict on a discovery request indicates that the requestId specified by the client - // has been dropped due to a delay or high request rate since the preceding corresponding - // attestation request. The client should not retry the request automatically - return ContactDiscoveryError( - kind: .genericClientError, - debugDescription: "RequestID conflict", - retryable: false, - retryAfterDate: retryAfterDate) - case 429: - return ContactDiscoveryError( - kind: .rateLimit, - debugDescription: "Rate limit", - retryable: true, - retryAfterDate: retryAfterDate) - case 400..<500: - return ContactDiscoveryError( - kind: .genericClientError, - debugDescription: "Client error (\(statusCode)): \(error.userErrorDescription)", - retryable: false, - retryAfterDate: retryAfterDate) - case 500..<600: - return ContactDiscoveryError( - kind: .genericServerError, - debugDescription: "Server error (\(statusCode)): \(error.userErrorDescription)", - retryable: true, - retryAfterDate: retryAfterDate) - default: - return ContactDiscoveryError( - kind: .generic, - debugDescription: "Unknown error (\(statusCode)): \(error.userErrorDescription)", - retryable: false, - retryAfterDate: retryAfterDate) - } - - } else { - return error - } - } -} diff --git a/SignalServiceKit/src/Network/API/ContactDiscoveryService.swift b/SignalServiceKit/src/Network/API/ContactDiscoveryService.swift deleted file mode 100644 index 37c479b239..0000000000 --- a/SignalServiceKit/src/Network/API/ContactDiscoveryService.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright 2019 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import Foundation - -public struct ContactDiscoveryService: Dependencies { - - public struct IntersectionQuery: Codable { - public let addressCount: UInt - public let commitment: Data - public let data: Data - public let iv: Data - public let mac: Data - - public struct EnclaveEnvelope: Codable { - public let requestId: Data - public let data: Data - public let iv: Data - public let mac: Data - } - public let envelopes: [RemoteAttestation.CDSAttestation.Id: EnclaveEnvelope] - } - - public struct IntersectionResponse { - let requestId: Data - let data: Data - let iv: Data - let mac: Data - } - - // MARK: - - - public func getRegisteredSignalUsers(query: ContactDiscoveryService.IntersectionQuery, - cookies: [HTTPCookie], - authUsername: String, - authPassword: String, - enclaveName: String, - host: String, - censorshipCircumventionPrefix: String) -> Promise { - owsAssertDebug(authUsername.strippedOrNil != nil) - owsAssertDebug(authPassword.strippedOrNil != nil) - owsAssertDebug(enclaveName.strippedOrNil != nil) - owsAssertDebug(host.strippedOrNil != nil) - - return firstly(on: DispatchQueue.sharedUtility) { () -> Promise in - let urlSession = Self.signalService.urlSessionForCds(host: host, - censorshipCircumventionPrefix: censorshipCircumventionPrefix) - let request = self.buildIntersectionRequest( - query: query, - cookies: cookies, - authUsername: authUsername, - authPassword: authPassword, - enclaveName: enclaveName - ) - guard let requestUrl = request.url else { - owsFailDebug("Missing requestUrl.") - throw OWSHTTPError.missingRequest - } - return firstly { - urlSession.promiseForTSRequest(request) - }.recover(on: DispatchQueue.global()) { error -> Promise in - // OWSUrlSession should only throw OWSHTTPError or OWSAssertionError. - if let httpError = error as? OWSHTTPError { - throw httpError - } else { - owsFailDebug("Unexpected error: \(error)") - throw OWSHTTPError.invalidRequest(requestUrl: requestUrl) - } - } - }.map(on: DispatchQueue.sharedUtility) { (response: HTTPResponse) throws -> IntersectionResponse in - guard let json = response.responseBodyJson else { - throw OWSAssertionError("Invalid JSON") - } - guard let params = ParamParser(responseObject: json) else { - throw ContactDiscoveryError.assertionError(description: "missing response dict") - } - return IntersectionResponse( - requestId: try params.requiredBase64EncodedData(key: "requestId"), - data: try params.requiredBase64EncodedData(key: "data"), - iv: try params.requiredBase64EncodedData(key: "iv", byteCount: 12), - mac: try params.requiredBase64EncodedData(key: "mac", byteCount: 16)) - } - } - - // MARK: - - - private func buildIntersectionRequest(query: IntersectionQuery, - cookies: [HTTPCookie], - authUsername: String, - authPassword: String, - enclaveName: String) -> TSRequest { - let path = "v1/discovery/\(enclaveName)" - let parameters: [String: Any] = [ - "addressCount": query.addressCount, - "commitment": query.commitment.base64EncodedString(), - "data": query.data.base64EncodedString(), - "iv": query.iv.base64EncodedString(), - "mac": query.mac.base64EncodedString(), - "envelopes": query.envelopes.mapValues { - [ - "requestId": $0.requestId.base64EncodedString(), - "data": $0.data.base64EncodedString(), - "iv": $0.iv.base64EncodedString(), - "mac": $0.mac.base64EncodedString() - ] - } - ] - let request = TSRequest(url: URL(string: path)!, method: "PUT", parameters: parameters) - - request.authUsername = authUsername - request.authPassword = authPassword - - // Set the cookie header. - // OWSURLSession disables default cookie handling for all requests. - assert(request.allHTTPHeaderFields?.count == 0) - request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookies) - - return request - } -} diff --git a/SignalServiceKit/src/Network/API/HTTPUtils.swift b/SignalServiceKit/src/Network/API/HTTPUtils.swift index 3a9c3c6ca7..77800b4015 100644 --- a/SignalServiceKit/src/Network/API/HTTPUtils.swift +++ b/SignalServiceKit/src/Network/API/HTTPUtils.swift @@ -278,8 +278,6 @@ fileprivate extension HTTPUtils { return httpError.isNetworkConnectivityError case GroupsV2Error.timeout: return true - case let contactDiscoveryError as ContactDiscoveryError: - return contactDiscoveryError.kind == .timeout case PaymentsError.timeout: return true default: diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h index 89e66adfaa..fa919b3156 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h @@ -135,7 +135,6 @@ typedef NS_ENUM(uint8_t, OWSIdentity); #pragma mark - Remote Attestation + (TSRequest *)remoteAttestationAuthRequestForKeyBackup; -+ (TSRequest *)remoteAttestationAuthRequestForContactDiscovery; + (TSRequest *)remoteAttestationAuthRequestForCDSI; #pragma mark - KBS diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index ade7d33358..5d7a227cb3 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -594,11 +594,6 @@ static NSString *_Nullable queryParamForIdentity(OWSIdentity identity) return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/backup/auth"] method:@"GET" parameters:@{}]; } -+ (TSRequest *)remoteAttestationAuthRequestForContactDiscovery -{ - return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/directory/auth"] method:@"GET" parameters:@{}]; -} - + (TSRequest *)remoteAttestationAuthRequestForCDSI { return [TSRequest requestWithUrl:[NSURL URLWithString:@"v2/directory/auth"] method:@"GET" parameters:@{}]; diff --git a/SignalServiceKit/src/Network/OWSSignalServiceProtocol.swift b/SignalServiceKit/src/Network/OWSSignalServiceProtocol.swift index 41fe918860..7d5cd437d5 100644 --- a/SignalServiceKit/src/Network/OWSSignalServiceProtocol.swift +++ b/SignalServiceKit/src/Network/OWSSignalServiceProtocol.swift @@ -60,7 +60,6 @@ public enum SignalServiceType { case storageService case cdn0 case cdn2 - case cds(host: String, censorshipCircumventionPrefix: String) case remoteAttestation(host: String, censorshipCircumventionPrefix: String) case kbs case updates @@ -104,18 +103,6 @@ public extension OWSSignalServiceProtocol { buildUrlSession(for: SignalServiceType.type(forCdnNumber: cdnNumber)) } - func urlSessionForCds( - host: String, - censorshipCircumventionPrefix: String - ) -> OWSURLSessionProtocol { - buildUrlSession( - for: .cds( - host: host, - censorshipCircumventionPrefix: censorshipCircumventionPrefix - ) - ) - } - func urlSessionForRemoteAttestation( host: String, censorshipCircumventionPrefix: String @@ -189,13 +176,6 @@ extension SignalServiceType { shouldUseSignalCertificate: true, shouldHandleRemoteDeprecation: false ) - case .cds(let host, let censorshipCircumventionPrefix): - return SignalServiceInfo( - baseUrl: URL(string: host)!, - censorshipCircumventionPathPrefix: censorshipCircumventionPrefix, - shouldUseSignalCertificate: true, - shouldHandleRemoteDeprecation: false - ) case .remoteAttestation(let host, let censorshipCircumventionPrefix): return SignalServiceInfo( baseUrl: URL(string: host)!, diff --git a/SignalServiceKit/src/Remote Attestation/RemoteAttestation.swift b/SignalServiceKit/src/Remote Attestation/RemoteAttestation.swift index 4d30af84a5..4a8a1bc04c 100644 --- a/SignalServiceKit/src/Remote Attestation/RemoteAttestation.swift +++ b/SignalServiceKit/src/Remote Attestation/RemoteAttestation.swift @@ -68,60 +68,6 @@ public extension RemoteAttestation { } } -// MARK: - CDS - -public extension RemoteAttestation { - struct CDSAttestation { - /// An opaque, server-specified identifier to link an attestation to its corresponding envelope - public typealias Id = String - - let cookies: [HTTPCookie] - let auth: Auth - let enclaveConfig: EnclaveConfig - let remoteAttestations: [Id: RemoteAttestation] - } - - static func performForCDS() -> Promise { - return performAttestation( - for: .contactDiscovery, - config: EnclaveConfig( - enclaveName: TSConstants.contactDiscoveryEnclaveName, - mrenclave: TSConstants.contactDiscoveryMrEnclave, - host: TSConstants.contactDiscoverySGXURL, - censorshipCircumventionPrefix: TSConstants.contactDiscoveryCensorshipPrefix - ) - ).map { attestationResponse -> CDSAttestation in - let attestationBody: [CDSAttestation.Id: [String: Any]] = try attestationResponse.responseBody.required(key: "attestations") - - // The client MUST reject server responses with more than 3 Remote Attestation Responses attached, - // for security reasons. - guard (1..<4).contains(attestationBody.count) else { - throw ParamParser.ParseError.invalidFormat("attestations", description: "invalid attestation count: \(attestationBody.count)") - } - - let attestations: [CDSAttestation.Id: RemoteAttestation] = try attestationBody.mapValues { attestationParams in - let parser = ParamParser(dictionary: attestationParams) - return try parseAttestationResponse(params: parser, - clientEphemeralKeyPair: attestationResponse.clientEphemeralKeyPair, - cookies: attestationResponse.cookies, - enclaveName: attestationResponse.enclaveConfig.enclaveName, - mrenclave: attestationResponse.enclaveConfig.mrenclave, - auth: attestationResponse.auth) - } - - let attestation = CDSAttestation(cookies: attestationResponse.cookies, - auth: attestationResponse.auth, - enclaveConfig: attestationResponse.enclaveConfig, - remoteAttestations: attestations) - owsAssertDebug(attestation.auth.username.strippedOrNil != nil) - owsAssertDebug(attestation.auth.password.strippedOrNil != nil) - owsAssertDebug(attestation.enclaveConfig.enclaveName.strippedOrNil != nil) - owsAssertDebug(attestation.enclaveConfig.host.strippedOrNil != nil) - return attestation - } - } -} - // MARK: - CSDI extension RemoteAttestation { @@ -302,13 +248,11 @@ public extension RemoteAttestation { fileprivate extension RemoteAttestation { enum Service { - case contactDiscovery case keyBackup case cdsi func authRequest() -> TSRequest { switch self { - case .contactDiscovery: return OWSRequestFactory.remoteAttestationAuthRequestForContactDiscovery() case .keyBackup: return OWSRequestFactory.remoteAttestationAuthRequestForKeyBackup() case .cdsi: return OWSRequestFactory.remoteAttestationAuthRequestForCDSI() } @@ -397,11 +341,6 @@ fileprivate extension RemoteAttestation { "clientPublic": clientEphemeralKeyPair.publicKey.base64EncodedString() ] - // When making requests to CDS, we need to tell the service to use IASv4 - if case .contactDiscovery = service { - parameters["iasVersion"] = 4 - } - let request = TSRequest(url: URL(string: path)!, method: "PUT", parameters: parameters) diff --git a/SignalServiceKit/src/TSConstants.swift b/SignalServiceKit/src/TSConstants.swift index d075ce219d..3ad6929179 100644 --- a/SignalServiceKit/src/TSConstants.swift +++ b/SignalServiceKit/src/TSConstants.swift @@ -47,8 +47,6 @@ public class TSConstants: NSObject { @objc public static var textSecureCDN2ServerURL: String { shared.textSecureCDN2ServerURL } @objc - public static var contactDiscoverySGXURL: String { shared.contactDiscoverySGXURL } - @objc public static var contactDiscoveryV2URL: String { shared.contactDiscoveryV2URL } @objc public static var keyBackupURL: String { shared.keyBackupURL } @@ -75,14 +73,10 @@ public class TSConstants: NSObject { public static var serviceCensorshipPrefix: String { shared.serviceCensorshipPrefix } public static var cdn0CensorshipPrefix: String { shared.cdn0CensorshipPrefix } public static var cdn2CensorshipPrefix: String { shared.cdn2CensorshipPrefix } - public static var contactDiscoveryCensorshipPrefix: String { shared.contactDiscoveryCensorshipPrefix } public static var keyBackupCensorshipPrefix: String { shared.keyBackupCensorshipPrefix } public static var storageServiceCensorshipPrefix: String { shared.storageServiceCensorshipPrefix } public static var contactDiscoveryV2CensorshipPrefix: String { shared.contactDiscoveryV2CensorshipPrefix } - @objc - public static var contactDiscoveryEnclaveName: String { shared.contactDiscoveryMrEnclave.stringValue } - public static var contactDiscoveryMrEnclave: MrEnclave { shared.contactDiscoveryMrEnclave } static var contactDiscoveryV2MrEnclave: MrEnclave { shared.contactDiscoveryV2MrEnclave } static var keyBackupEnclave: KeyBackupEnclave { shared.keyBackupEnclave } @@ -111,7 +105,6 @@ public protocol TSConstantsProtocol: AnyObject { var mainServiceUnidentifiedURL: String { get } var textSecureCDN0ServerURL: String { get } var textSecureCDN2ServerURL: String { get } - var contactDiscoverySGXURL: String { get } var contactDiscoveryV2URL: String { get } var keyBackupURL: String { get } var storageServiceURL: String { get } @@ -128,13 +121,10 @@ public protocol TSConstantsProtocol: AnyObject { var serviceCensorshipPrefix: String { get } var cdn0CensorshipPrefix: String { get } var cdn2CensorshipPrefix: String { get } - var contactDiscoveryCensorshipPrefix: String { get } var keyBackupCensorshipPrefix: String { get } var storageServiceCensorshipPrefix: String { get } var contactDiscoveryV2CensorshipPrefix: String { get } - // SGX Backed Contact Discovery - var contactDiscoveryMrEnclave: MrEnclave { get } var contactDiscoveryV2MrEnclave: MrEnclave { get } var keyBackupEnclave: KeyBackupEnclave { get } @@ -176,7 +166,6 @@ private class TSConstantsProduction: TSConstantsProtocol { public let mainServiceUnidentifiedURL = "https://ud-chat.signal.org" public let textSecureCDN0ServerURL = "https://cdn.signal.org" public let textSecureCDN2ServerURL = "https://cdn2.signal.org" - public let contactDiscoverySGXURL = "https://api.directory.signal.org" public let contactDiscoveryV2URL = "wss://cdsi.signal.org" public let keyBackupURL = "https://api.backup.signal.org" public let storageServiceURL = "https://storage.signal.org" @@ -193,12 +182,10 @@ private class TSConstantsProduction: TSConstantsProtocol { public let serviceCensorshipPrefix = "service" public let cdn0CensorshipPrefix = "cdn" public let cdn2CensorshipPrefix = "cdn2" - public let contactDiscoveryCensorshipPrefix = "directory" public let keyBackupCensorshipPrefix = "backup" public let storageServiceCensorshipPrefix = "storage" public let contactDiscoveryV2CensorshipPrefix = "cdsi" - public var contactDiscoveryMrEnclave = MrEnclave("74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081") public let contactDiscoveryV2MrEnclave = MrEnclave("0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57") public let keyBackupEnclave = KeyBackupEnclave( @@ -230,7 +217,6 @@ private class TSConstantsStaging: TSConstantsProtocol { public let mainServiceUnidentifiedURL = "https://ud-chat.staging.signal.org" public let textSecureCDN0ServerURL = "https://cdn-staging.signal.org" public let textSecureCDN2ServerURL = "https://cdn2-staging.signal.org" - public let contactDiscoverySGXURL = "https://api-staging.directory.signal.org" public let contactDiscoveryV2URL = "wss://cdsi.staging.signal.org" public let keyBackupURL = "https://api-staging.backup.signal.org" public let storageServiceURL = "https://storage-staging.signal.org" @@ -249,13 +235,11 @@ private class TSConstantsStaging: TSConstantsProtocol { public let serviceCensorshipPrefix = "service-staging" public let cdn0CensorshipPrefix = "cdn-staging" public let cdn2CensorshipPrefix = "cdn2-staging" - public let contactDiscoveryCensorshipPrefix = "directory-staging" public let keyBackupCensorshipPrefix = "backup-staging" public let storageServiceCensorshipPrefix = "storage-staging" public let contactDiscoveryV2CensorshipPrefix = "cdsi-staging" // CDS uses the same EnclaveName and MrEnclave - public var contactDiscoveryMrEnclave = MrEnclave("74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081") public let contactDiscoveryV2MrEnclave = MrEnclave("0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57") public let keyBackupEnclave = KeyBackupEnclave( diff --git a/SignalServiceKit/src/Util/RemoteConfigManager.swift b/SignalServiceKit/src/Util/RemoteConfigManager.swift index 0e4c41ba1e..cdcf7d17e1 100644 --- a/SignalServiceKit/src/Util/RemoteConfigManager.swift +++ b/SignalServiceKit/src/Util/RemoteConfigManager.swift @@ -241,10 +241,6 @@ public class RemoteConfig: BaseFlags { getUIntValue(forFlag: .maxGroupCallRingSize, defaultValue: 16) } - public static var contactDiscoveryV2KillSwitch: Bool { - isEnabled(.contactDiscoveryV2KillSwitch) - } - public static var enableAutoAPNSRotation: Bool { return isEnabled(.enableAutoAPNSRotation, defaultValue: false) } @@ -519,7 +515,6 @@ private struct Flags { case paypalOneTimeDonationKillSwitch case paypalGiftDonationKillSwitch case paypalMonthlyDonationKillSwitch - case contactDiscoveryV2KillSwitch case enableAutoAPNSRotation case ringrtcNwPathMonitorTrialKillSwitch } diff --git a/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryE164CollectionTest.swift b/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryE164CollectionTest.swift new file mode 100644 index 0000000000..abcfc21a48 --- /dev/null +++ b/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryE164CollectionTest.swift @@ -0,0 +1,27 @@ +// +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import XCTest +@testable import SignalServiceKit + +class ContactDiscoveryE164CollectionTest: XCTestCase { + func test_encodeNumber() throws { + let phoneNumbers = [try XCTUnwrap(E164("+1011"))] + let actual = ContactDiscoveryE164Collection(phoneNumbers).encodedValues + let expected: Data = Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf3]) + + XCTAssertEqual(expected, actual) + } + + func test_encodeMultipleNumber() throws { + let phoneNumbers = [ try XCTUnwrap(E164("+1011")), try XCTUnwrap(E164("+19875550123")) ] + let actual = ContactDiscoveryE164Collection(phoneNumbers).encodedValues + let expected: Data = Data([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf3, + 0x00, 0x00, 0x00, 0x04, 0xa0, 0xac, 0xd3, 0xab + ]) + XCTAssertEqual(expected, actual) + } +} diff --git a/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryOperationTest.swift b/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryOperationTest.swift deleted file mode 100644 index 93f865e1bc..0000000000 --- a/SignalServiceKit/tests/Contacts/Discovery/ContactDiscoveryOperationTest.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright 2018 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import XCTest -@testable import SignalServiceKit - -class ContactDiscoveryOperationTest: XCTestCase { - func test_uuidArrayFromEmptyData() { - let data = Data() - let uuids = SGXContactDiscoveryOperation.uuidArray(from: data) - XCTAssertEqual([], uuids) - } - - func test_uuidArrayFromZeroBytes() { - let data = Data(repeating: 0x00, count: 16) - let uuids = SGXContactDiscoveryOperation.uuidArray(from: data) - XCTAssertEqual([UUID(uuidString: "00000000-0000-0000-0000-000000000000")], uuids) - } - - func test_uuidArrayFromBytes() { - let bytes: [UInt8] = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 - ] - let data = Data(bytes) - let uuids = SGXContactDiscoveryOperation.uuidArray(from: data) - let expected = [UUID(uuidString: "00000000-0000-0000-0000-000000000000"), - UUID(uuidString: "00000000-0000-0000-0000-000000000000"), - UUID(uuidString: "01020304-0506-0708-090A-0B0C0D0E0F10")] - XCTAssertEqual(expected, uuids) - } - - func test_encodeNumber() throws { - let phoneNumbers = [try XCTUnwrap(E164("+1011"))] - let actual = ContactDiscoveryE164Collection(phoneNumbers).encodedValues - let expected: Data = Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf3]) - - XCTAssertEqual(expected, actual) - } - - func test_encodeMultipleNumber() throws { - let phoneNumbers = [ try XCTUnwrap(E164("+1011")), try XCTUnwrap(E164("+15551231234")) ] - let actual = ContactDiscoveryE164Collection(phoneNumbers).encodedValues - let expected: Data = Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf3, - 0x00, 0x00, 0x00, 0x03, 0x9e, 0xec, 0xf5, 0x02]) - - XCTAssertEqual(expected, actual) - } -}