From f10cc651878ba16f5d0e91e092a26b08cebd0dd4 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 7 May 2020 09:29:22 +1000 Subject: [PATCH 1/5] Fix issue where the app doesn't wait for device links to be stored --- SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift | 8 ++++++-- .../Protocol/Sync Messages/SyncMessagesProtocol.swift | 3 +-- SignalServiceKit/src/Messages/OWSMessageManager.m | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift index 44bc97903..52fbd8e1b 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift @@ -84,14 +84,18 @@ public final class LokiFileServerAPI : LokiDotNetAPI { return deviceLink } }) - }.map(on: DispatchQueue.global()) { deviceLinks in + }.then(on: DispatchQueue.global()) { deviceLinks -> Promise> in + let (promise, seal) = Promise>.pending() // Dispatch async on the main queue to avoid nested write transactions DispatchQueue.main.async { storage.dbReadWriteConnection.readWrite { transaction in storage.setDeviceLinks(deviceLinks, in: transaction) } + // We have to wait for the device links to be stored because a lot of our logic relies + // on them being in the database + seal.fulfill(deviceLinks) } - return deviceLinks + return promise } } } diff --git a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift index dad1d2f4b..27a288b6f 100644 --- a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift @@ -94,8 +94,7 @@ public final class SyncMessagesProtocol : NSObject { public static func isValidSyncMessage(_ envelope: SSKProtoEnvelope, in transaction: YapDatabaseReadTransaction) -> Bool { // The envelope source is set during UD decryption let hexEncodedPublicKey = envelope.source! - let linkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - return linkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) + return LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction) } // TODO: We should probably look at why sync messages are being duplicated rather than doing this diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 05f2deea8..9e938bc64 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1271,7 +1271,7 @@ NS_ASSUME_NONNULL_BEGIN }).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) { dispatch_semaphore_signal(semaphore); }) retainUntilComplete]; - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 8 * NSEC_PER_SEC)); + dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)); } // FIXME: ======== From 60cb5b61144bd07e41df57efcca8db206ac7d97d Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 7 May 2020 11:56:35 +1000 Subject: [PATCH 2/5] Add missing session request handling --- .../SessionManagementProtocol.swift | 21 +++++++++++++++++++ .../src/Messages/OWSMessageManager.m | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift index 04a468b8c..76dec4628 100644 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift @@ -166,6 +166,27 @@ public final class SessionManagementProtocol : NSObject { return dataMessage.flags & UInt32(sessionRequestFlag.rawValue) != 0 } + @objc(handleSessionRequestMessage:wrappedIn:using:) + public static func handleSessionRequestMessage(_ dataMessage: SSKProtoDataMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { + // The envelope source is set during UD decryption + // FIXME: Device links should probably update here if they're out of date + let hexEncodedPublicKey = envelope.source! + var validHEPKs: Set = [] + TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in + guard let group = object as? TSGroupThread, group.groupModel.groupType == .closedGroup, + group.shouldThreadBeVisible else { return } + let closedGroupMembersIncludingLinkedDevices = group.groupModel.groupMemberIds.flatMap { + LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: $0, in: transaction) + } + validHEPKs.formUnion(closedGroupMembersIncludingLinkedDevices) + } + guard validHEPKs.contains(hexEncodedPublicKey) else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction) + let ephemeralMessage = EphemeralMessage(in: thread) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction) + } + // TODO: This needs an explanation of when we expect pre key bundles to be attached @objc(handlePreKeyBundleMessageIfNeeded:wrappedIn:using:) public static func handlePreKeyBundleMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 9e938bc64..a161d2ae5 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -468,7 +468,10 @@ NS_ASSUME_NONNULL_BEGIN } else if (contentProto.dataMessage) { // Loki: Don't process session request messages any further - if ([LKSessionManagementProtocol isSessionRequestMessage:contentProto.dataMessage]) { return; } + if ([LKSessionManagementProtocol isSessionRequestMessage:contentProto.dataMessage]) { + [LKSessionManagementProtocol handleSessionRequestMessage:contentProto.dataMessage wrappedIn:envelope using:transaction]; + return; + } // Loki: Don't process session restore messages any further if ([LKSessionManagementProtocol isSessionRestoreMessage:contentProto.dataMessage]) { return; } From 807345868f3108b0cc76a03d9de7bacc02abb934 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 7 May 2020 11:57:07 +1000 Subject: [PATCH 3/5] Attempt to repair session if needed --- .../SessionManagementProtocol.swift | 13 +++++++++++++ SignalServiceKit/src/Messages/OWSMessageSender.m | 1 + 2 files changed, 14 insertions(+) diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift index 76dec4628..e2501df9c 100644 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift @@ -139,6 +139,19 @@ public final class SessionManagementProtocol : NSObject { return promise } + @objc(repairSessionIfNeededForMessage:to:) + public static func repairSessionIfNeeded(for message: TSOutgoingMessage, to hexEncodedPublicKey: String) { + guard (message.thread as? TSGroupThread)?.groupModel.groupType == .closedGroup else { return } + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction) + let sessionRequestMessage = SessionRequestMessage(thread: thread) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction) + } + } + } + // MARK: - Receiving @objc(handleDecryptionError:forHexEncodedPublicKey:using:) public static func handleDecryptionError(_ rawValue: Int32, for hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 1a9838fde..389bb2bcc 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1705,6 +1705,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; */ if (!bundle) { + [LKSessionManagementProtocol repairSessionIfNeededForMessage:messageSend.message to:recipientID]; NSString *missingPrekeyBundleException = @"missingPrekeyBundleException"; OWSRaiseException(missingPrekeyBundleException, @"Missing pre key bundle for: %@.", recipientID); } else { From e61be12bc1ac9da6b3f6df66d1bfe96a9fb9696b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 7 May 2020 11:57:40 +1000 Subject: [PATCH 4/5] Slightly improve performance --- .../Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift index a45356ba5..ab49ec332 100644 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift +++ b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift @@ -33,7 +33,7 @@ extension OnionRequestAPI { /// Encrypts `payload` for `snode` and returns the result. Use this to build the core of an onion request. internal static func encrypt(_ payload: JSON, forTargetSnode snode: LokiAPITarget) -> Promise { let (promise, seal) = Promise.pending() - DispatchQueue.global().async { + DispatchQueue.global(qos: .userInitiated).async { do { guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) } let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: []) @@ -53,7 +53,7 @@ extension OnionRequestAPI { /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. internal static func encryptHop(from lhs: LokiAPITarget, to rhs: LokiAPITarget, using previousEncryptionResult: EncryptionResult) -> Promise { let (promise, seal) = Promise.pending() - DispatchQueue.global().async { + DispatchQueue.global(qos: .userInitiated).async { let parameters: JSON = [ "ciphertext" : previousEncryptionResult.ciphertext.base64EncodedString(), "ephemeral_key" : previousEncryptionResult.ephemeralPublicKey.toHexString(), From f929f10a7f994fddb259456add0a91df08b7ddc0 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 7 May 2020 11:57:55 +1000 Subject: [PATCH 5/5] Clean --- SignalServiceKit/src/Loki/API/LokiAPI.swift | 9 ++++----- SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift | 5 ++--- SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift | 6 +++--- .../src/Loki/API/Open Groups/LokiPublicChatAPI.swift | 4 ++-- .../Session Management/SessionManagementProtocol.swift | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 5a06afbe0..98fd482b0 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -14,7 +14,6 @@ public final class LokiAPI : NSObject { internal static let workQueue = DispatchQueue(label: "LokiAPI.workQueue", qos: .userInitiated) internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - internal static var userHexEncodedPublicKey: String { getUserHexEncodedPublicKey() } // MARK: Settings private static let maxRetryCount: UInt = 4 @@ -63,16 +62,16 @@ public final class LokiAPI : NSObject { internal static func getRawMessages(from target: LokiAPITarget, usingLongPolling useLongPolling: Bool) -> RawResponsePromise { let lastHashValue = getLastMessageHashValue(for: target) ?? "" - let parameters = [ "pubKey" : userHexEncodedPublicKey, "lastHash" : lastHashValue ] + let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "lastHash" : lastHashValue ] let headers: [String:String]? = useLongPolling ? [ "X-Loki-Long-Poll" : "true" ] : nil let timeout: TimeInterval? = useLongPolling ? longPollingTimeout : nil - return invoke(.getMessages, on: target, associatedWith: userHexEncodedPublicKey, parameters: parameters, headers: headers, timeout: timeout) + return invoke(.getMessages, on: target, associatedWith: getUserHexEncodedPublicKey(), parameters: parameters, headers: headers, timeout: timeout) } // MARK: Public API public static func getMessages() -> Promise> { return attempt(maxRetryCount: maxRetryCount, recoveringOn: workQueue) { - getTargetSnodes(for: userHexEncodedPublicKey).mapValues { targetSnode in + getTargetSnodes(for: getUserHexEncodedPublicKey()).mapValues { targetSnode in getRawMessages(from: targetSnode, usingLongPolling: false).map { parseRawMessagesResponse($0, from: targetSnode) } }.map { Set($0) } } @@ -157,7 +156,7 @@ public final class LokiAPI : NSObject { setLastMessageHashValue(for: target, hashValue: hashValue, expirationDate: UInt64(expirationDate)) // FIXME: Move this out of here if UserDefaults.standard[.isUsingFullAPNs] { - LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: hashValue, expiration: expirationDate, hexEncodedPublicKey: userHexEncodedPublicKey) + LokiPushNotificationManager.acknowledgeDelivery(forMessageWithHash: hashValue, expiration: expirationDate, hexEncodedPublicKey: getUserHexEncodedPublicKey()) } } else if (!rawMessages.isEmpty) { print("[Loki] Failed to update last message hash value from: \(rawMessages).") diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift index 23eef7d42..48bcf9854 100644 --- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift @@ -6,7 +6,6 @@ public class LokiDotNetAPI : NSObject { internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var userKeyPair: ECKeyPair { OWSIdentityManager.shared().identityKeyPair()! } - internal static var userHexEncodedPublicKey: String { userKeyPair.hexEncodedPublicKey } // MARK: Settings private static let attachmentType = "network.loki" @@ -60,7 +59,7 @@ public class LokiDotNetAPI : NSObject { // MARK: Private API private static func requestNewAuthToken(for server: String) -> Promise { print("[Loki] Requesting auth token for server: \(server).") - let queryParameters = "pubKey=\(userHexEncodedPublicKey)" + let queryParameters = "pubKey=\(getUserHexEncodedPublicKey())" let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")! let request = TSRequest(url: url) return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: LokiAPI.workQueue).map(on: LokiAPI.workQueue) { rawResponse in @@ -85,7 +84,7 @@ public class LokiDotNetAPI : NSObject { private static func submitAuthToken(_ token: String, for server: String) -> Promise { print("[Loki] Submitting auth token for server: \(server).") let url = URL(string: "\(server)/loki/v1/submit_challenge")! - let parameters = [ "pubKey" : userHexEncodedPublicKey, "token" : token ] + let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "token" : token ] let request = TSRequest(url: url, method: "POST", parameters: parameters) return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global()).map(on: DispatchQueue.global()) { _ in token } } diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift index 52fbd8e1b..e7b706272 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift @@ -103,7 +103,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI { public static func setDeviceLinks(_ deviceLinks: Set) -> Promise { print("[Loki] Updating device links.") return getAuthToken(for: server).then { token -> Promise in - let isMaster = deviceLinks.contains { $0.master.hexEncodedPublicKey == userHexEncodedPublicKey } + let isMaster = deviceLinks.contains { $0.master.hexEncodedPublicKey == getUserHexEncodedPublicKey() } let deviceLinksAsJSON = deviceLinks.map { $0.toJSON() } let value = !deviceLinksAsJSON.isEmpty ? [ "isPrimary" : isMaster ? 1 : 0, "authorisations" : deviceLinksAsJSON ] : nil let annotation: JSON = [ "type" : deviceLinkType, "value" : value ] @@ -124,7 +124,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI { public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise { var deviceLinks: Set = [] storage.dbReadConnection.read { transaction in - deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) + deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction) } deviceLinks.insert(deviceLink) return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise in @@ -144,7 +144,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI { public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise { var deviceLinks: Set = [] storage.dbReadConnection.read { transaction in - deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) + deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction) } deviceLinks.remove(deviceLink) return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise in diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift index 5ef51775d..2aa5f9fe2 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift @@ -19,7 +19,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { // MARK: Convenience private static var userDisplayName: String { - return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: userHexEncodedPublicKey) ?? "Anonymous" + return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: getUserHexEncodedPublicKey()) ?? "Anonymous" } // MARK: Database @@ -187,7 +187,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { throw LokiDotNetAPIError.parsingFailed } let timestamp = UInt64(date.timeIntervalSince1970) * 1000 - return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature) + return LokiPublicChatMessage(serverID: serverID, hexEncodedPublicKey: getUserHexEncodedPublicKey(), displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature) } }.recover { error -> Promise in if let error = error as? NetworkManagerError, error.statusCode == 401 { diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift index e2501df9c..3cd451abe 100644 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift @@ -215,7 +215,7 @@ public final class SessionManagementProtocol : NSObject { // If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session. // The envelope type is set during UD decryption. // TODO: Should this ignore session requests? - if envelope.type == .friendRequest, + if envelope.type == .friendRequest, // TODO: Should this check that the envelope doesn't have the session request flag set? let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction), // TODO: Should this be getOrCreate? thread.isContactFriend { receiving_startSessionReset(in: thread, using: transaction)