From df7ca74b70cf65c75b033053b3ed709831277f23 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 12 Jun 2019 14:23:01 +1000 Subject: [PATCH] Simplify LokiAPI changes --- Signal/Signal-Info.plist | 2 +- Signal/src/AppDelegate.m | 10 +-- SignalMessaging/Views/AvatarImageView.swift | 1 - ...olling.swift => LokiAPI+LongPolling.swift} | 12 ++-- .../src/Loki/API/LokiAPI+Request.swift | 20 ------ .../src/Loki/API/LokiAPI+SwarmAPI.swift | 29 ++++---- SignalServiceKit/src/Loki/API/LokiAPI.swift | 70 +++++++------------ .../src/Loki/API/LokiP2PManager.swift | 6 +- .../Loki/Utilities/Notification+Loki.swift | 8 +-- 9 files changed, 55 insertions(+), 103 deletions(-) rename SignalServiceKit/src/Loki/API/{LokiAPI+Polling.swift => LokiAPI+LongPolling.swift} (87%) delete mode 100644 SignalServiceKit/src/Loki/API/LokiAPI+Request.swift diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 7396df8e5..258a90a89 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -7,7 +7,7 @@ CarthageVersion 0.33.0 OSXVersion - 10.14.4 + 10.14.5 WebRTCCommit 1445d719bf05280270e9f77576f80f973fd847f8 M73 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 1e39b61ea..d52da76a0 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -310,8 +310,8 @@ static NSTimeInterval launchStartedAt; name:NSNotificationName_2FAStateDidChange object:nil]; - // Loki Message received - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedNewMessages:) name:NSNotification.receivedNewMessages object:nil]; + // Loki - Observe messages received notifications + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewMessagesReceived:) name:NSNotification.newMessagesReceived object:nil]; OWSLogInfo(@"application: didFinishLaunchingWithOptions completed."); @@ -1413,17 +1413,17 @@ static NSTimeInterval launchStartedAt; #pragma mark - Long polling -- (void)receivedNewMessages:(NSNotification *)notification +- (void)handleNewMessagesReceived:(NSNotification *)notification { NSArray *messages = (NSArray *)notification.userInfo[@"messages"]; - OWSLogInfo(@"[Loki] Received %lu messages from long polling", messages.count); + OWSLogInfo(@"[Loki] Received %lu messages through long polling.", messages.count); for (SSKProtoEnvelope *envelope in messages) { NSData *envelopeData = envelope.serializedDataIgnoringErrors; if (envelopeData != nil) { [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:envelopeData]; } else { - OWSFailDebug(@"Failed to serialize envelope"); + OWSFailDebug(@"Failed to deserialize envelope."); } } } diff --git a/SignalMessaging/Views/AvatarImageView.swift b/SignalMessaging/Views/AvatarImageView.swift index 9e0d0d300..e08f6cd79 100644 --- a/SignalMessaging/Views/AvatarImageView.swift +++ b/SignalMessaging/Views/AvatarImageView.swift @@ -3,7 +3,6 @@ // import UIKit -import SignalServiceKit @objc public class AvatarImageView: UIImageView { diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Polling.swift b/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift similarity index 87% rename from SignalServiceKit/src/Loki/API/LokiAPI+Polling.swift rename to SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift index 94970545a..515554c9c 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Polling.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift @@ -37,7 +37,7 @@ public extension LokiAPI { // This is here so we can stop the infinite loop guard !shouldStopPolling else { return } - fetchSwarmIfNeeded(for: hexEncodedPublicKey).then { _ -> Guarantee<[Result]> in + getSwarm(for: hexEncodedPublicKey).then { _ -> Guarantee<[Result]> in var promises = [Promise]() let connections = 3 for i in 0.. [LokiAPITarget] { - let snodes = getCachedSnodes(for: hexEncodedPublicKey) + let snodes = LokiAPI.swarmCache[hexEncodedPublicKey] ?? [] return snodes.filter { !usedSnodes.contains($0) } } @@ -84,15 +84,15 @@ public extension LokiAPI { func getMessagesInfinitely(from target: LokiAPITarget) -> Promise { // The only way to exit the infinite loop is to throw an error 3 times or cancel - return getRawMessages(from: target).then { rawMessages -> Promise in + return getRawMessages(from: target, useLongPolling: true).then { rawResponse -> Promise in // Check if we need to abort guard !isCancelled else { throw PMKError.cancelled } // Process the messages - let messages = process(rawMessages: rawMessages, from: target) + let messages = parseRawMessagesResponse(rawResponse, from: target) // Send our messages as a notification - NotificationCenter.default.post(name: .receivedNewMessages, object: nil, userInfo: ["messages": messages]) + NotificationCenter.default.post(name: .newMessagesReceived, object: nil, userInfo: ["messages": messages]) // Continue fetching if we haven't cancelled return getMessagesInfinitely(from: target) @@ -107,7 +107,7 @@ public extension LokiAPI { // Connect to the next snode if we haven't cancelled // We also need to remove the cached snode so we don't contact it again - removeCachedSnode(nextSnode, for: hexEncodedPublicKey) + dropIfNeeded(nextSnode, hexEncodedPublicKey: hexEncodedPublicKey) return connectToNextSnode() } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Request.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Request.swift deleted file mode 100644 index 27d0a7f03..000000000 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Request.swift +++ /dev/null @@ -1,20 +0,0 @@ - -internal extension LokiAPI { - internal struct Request { - var method: LokiAPITarget.Method - var target: LokiAPITarget - var publicKey: String - var parameters: [String:Any] - var headers: [String:String] - var timeout: TimeInterval? - - init(method: LokiAPITarget.Method, target: LokiAPITarget, publicKey: String, parameters: [String:Any] = [:]) { - self.method = method - self.target = target - self.publicKey = publicKey - self.parameters = parameters - self.headers = [:] - self.timeout = nil - } - } -} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 21688868f..981d293a8 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -11,7 +11,7 @@ public extension LokiAPI { private static let swarmCacheKey = "swarmCacheKey" private static let swarmCacheCollection = "swarmCacheCollection" - fileprivate static var swarmCache: [String:[LokiAPITarget]] { + internal static var swarmCache: [String:[LokiAPITarget]] { get { var result: [String:[LokiAPITarget]]? = nil storage.dbReadConnection.read { transaction in @@ -26,6 +26,14 @@ public extension LokiAPI { } } + internal static func dropIfNeeded(_ target: LokiAPITarget, hexEncodedPublicKey: String) { + let swarm = LokiAPI.swarmCache[hexEncodedPublicKey] + if var swarm = swarm, let index = swarm.firstIndex(of: target) { + swarm.remove(at: index) + LokiAPI.swarmCache[hexEncodedPublicKey] = swarm + } + } + // MARK: Internal API private static func getRandomSnode() -> Promise { return Promise { seal in @@ -33,16 +41,7 @@ public extension LokiAPI { } } - internal static func getCachedSnodes(for hexEncodedPublicKey: String) -> [LokiAPITarget] { - return swarmCache[hexEncodedPublicKey] ?? [] - } - - internal static func removeCachedSnode(_ target: LokiAPITarget, for hexEncodedPublicKey: String) { - guard let cache = swarmCache[hexEncodedPublicKey] else { return } - swarmCache[hexEncodedPublicKey] = cache.filter { $0 != target } - } - - internal static func fetchSwarmIfNeeded(for hexEncodedPublicKey: String) -> Promise<[LokiAPITarget]> { + internal static func getSwarm(for hexEncodedPublicKey: String) -> Promise<[LokiAPITarget]> { if let cachedSwarm = swarmCache[hexEncodedPublicKey], cachedSwarm.count >= minimumSnodeCount { return Promise<[LokiAPITarget]> { $0.fulfill(cachedSwarm) } } else { @@ -54,7 +53,7 @@ public extension LokiAPI { // MARK: Public API internal static func getTargetSnodes(for hexEncodedPublicKey: String) -> Promise<[LokiAPITarget]> { // shuffled() uses the system's default random generator, which is cryptographically secure - return fetchSwarmIfNeeded(for: hexEncodedPublicKey).map { Array($0.shuffled().prefix(targetSnodeCount)) } + return getSwarm(for: hexEncodedPublicKey).map { Array($0.shuffled().prefix(targetSnodeCount)) } } // MARK: Parsing @@ -85,11 +84,7 @@ internal extension Promise { case 421: // The snode isn't associated with the given public key anymore Logger.warn("[Loki] Invalidating swarm for: \(hexEncodedPublicKey).") - let swarm = LokiAPI.swarmCache[hexEncodedPublicKey] - if var swarm = swarm, let index = swarm.firstIndex(of: target) { - swarm.remove(at: index) - LokiAPI.swarmCache[hexEncodedPublicKey] = swarm - } + LokiAPI.dropIfNeeded(target, hexEncodedPublicKey: hexEncodedPublicKey) default: break } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 0c583d592..a3ba6e67b 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -6,7 +6,8 @@ import PromiseKit // MARK: Settings private static let version = "v1" private static let maxRetryCount: UInt = 3 - public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 + private static let longPollingTimeout: TimeInterval = 40 + public static let defaultMessageTTL: UInt64 = 24 * 60 * 60 * 1000 // MARK: Types public typealias RawResponse = Any @@ -31,56 +32,30 @@ import PromiseKit override private init() { } // MARK: Internal API - internal static func invoke(_ method: LokiAPITarget.Method, on target: LokiAPITarget, associatedWith hexEncodedPublicKey: String, parameters: [String:Any]) -> RawResponsePromise { - return invoke(request: Request(method: method, target: target, publicKey: hexEncodedPublicKey, parameters: parameters)) + internal static func invoke(_ method: LokiAPITarget.Method, on target: LokiAPITarget, associatedWith hexEncodedPublicKey: String, + parameters: [String:Any], headers: [String:String]? = nil, timeout: TimeInterval? = nil) -> RawResponsePromise { + let url = URL(string: "\(target.address):\(target.port)/\(version)/storage_rpc")! + let request = TSRequest(url: url, method: "POST", parameters: [ "method" : method.rawValue, "params" : parameters ]) + if let headers = headers { request.allHTTPHeaderFields = headers } + if let timeout = timeout { request.timeoutInterval = timeout } + return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject } + .handlingSwarmSpecificErrorsIfNeeded(for: target, associatedWith: hexEncodedPublicKey).recoveringNetworkErrorsIfNeeded() } - internal static func invoke(request: Request) -> RawResponsePromise { - let url = URL(string: "\(request.target.address):\(request.target.port)/\(version)/storage_rpc")! - let networkRequest = TSRequest(url: url, method: "POST", parameters: [ "method" : request.method.rawValue, "params" : request.parameters ]) - networkRequest.allHTTPHeaderFields = request.headers - if let timeout = request.timeout { - networkRequest.timeoutInterval = timeout - } - - return TSNetworkManager.shared().makePromise(request: networkRequest).map { $0.responseObject } - .handlingSwarmSpecificErrorsIfNeeded(for: request.target, associatedWith: request.publicKey).recoveringNetworkErrorsIfNeeded() - } - - - internal static func getMessages(from target: LokiAPITarget, longPolling: Bool = true) -> MessageListPromise { - return getRawMessages(from: target, longPolling: longPolling).map { process(rawMessages: $0, from: target) } - } - - internal static func getRawMessages(from target: LokiAPITarget, longPolling: Bool = true) -> Promise<[JSON]> { + internal static func getRawMessages(from target: LokiAPITarget, useLongPolling: Bool) -> RawResponsePromise { let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey let lastHashValue = getLastMessageHashValue(for: target) ?? "" let parameters = [ "pubKey" : hexEncodedPublicKey, "lastHash" : lastHashValue ] - - var request = Request(method: .getMessages, target: target, publicKey: hexEncodedPublicKey, parameters: parameters) - if (longPolling) { - request.headers = ["X-Loki-Long-Poll" : "true"] - request.timeout = 40 // 40 second timeout - } - - return invoke(request: request).map { rawResponse in - guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } - return rawMessages - } - } - - internal static func process(rawMessages: [JSON], from target: LokiAPITarget) -> [SSKProtoEnvelope] { - updateLastMessageHashValueIfPossible(for: target, from: rawMessages) - let newRawMessages = removeDuplicates(from: rawMessages) - return parseProtoEnvelopes(from: newRawMessages) + let headers: [String:String]? = useLongPolling ? [ "X-Loki-Long-Poll" : "true" ] : nil + let timeout: TimeInterval? = useLongPolling ? longPollingTimeout : nil + return invoke(.getMessages, on: target, associatedWith: hexEncodedPublicKey, parameters: parameters, headers: headers, timeout: timeout) } // MARK: Public API - public static func getMessages() -> Promise> { let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey return getTargetSnodes(for: hexEncodedPublicKey).mapValues { targetSnode in - return getMessages(from: targetSnode, longPolling: false) + return getRawMessages(from: targetSnode, useLongPolling: false).map { parseRawMessagesResponse($0, from: targetSnode) } }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount) } @@ -132,12 +107,19 @@ import PromiseKit // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. + internal static func parseRawMessagesResponse(_ rawResponse: Any, from target: LokiAPITarget) -> [SSKProtoEnvelope] { + guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } + updateLastMessageHashValueIfPossible(for: target, from: rawMessages) + let newRawMessages = removeDuplicates(from: rawMessages) + return parseProtoEnvelopes(from: newRawMessages) + } + private static func updateLastMessageHashValueIfPossible(for target: LokiAPITarget, from rawMessages: [JSON]) { - guard let lastMessage = rawMessages.last, let hashValue = lastMessage["hash"] as? String, let expiresAt = lastMessage["expiration"] as? Int else { - if rawMessages.count > 0 { Logger.warn("[Loki] Failed to update last message hash value from: \(rawMessages).") } - return + if let lastMessage = rawMessages.last, let hashValue = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? Int { + setLastMessageHashValue(for: target, hashValue: hashValue, expiresAt: UInt64(expirationDate)) + } else if (!rawMessages.isEmpty) { + Logger.warn("[Loki] Failed to update last message hash value from: \(rawMessages).") } - setLastMessageHashValue(for: target, hashValue: hashValue, expiresAt: UInt64(expiresAt)) } private static func removeDuplicates(from rawMessages: [JSON]) -> [JSON] { diff --git a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift index 28ca7ae0e..1ec75770e 100644 --- a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift @@ -174,12 +174,12 @@ AssertIsOnMainThread() guard let message = onlineBroadcastMessage(forThread: thread) else { - Logger.warn("[Loki] P2P Address not set") + Logger.warn("[Loki] P2P address not set.") return } messageSender.sendPromise(message: message).catch { error in - Logger.warn("Failed to send online status to \(thread.contactIdentifier())") + Logger.warn("Failed to send online status to \(thread.contactIdentifier()).") }.retainUntilComplete() } @@ -198,7 +198,7 @@ private static func createLokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? { guard let ourAddress = ourP2PAddress else { - Logger.error("P2P Address not set") + Logger.warn("[Loki] P2P address not set.") return nil } diff --git a/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift b/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift index 47216ad4e..4969f25b8 100644 --- a/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift +++ b/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift @@ -1,9 +1,7 @@ public extension Notification.Name { public static let contactOnlineStatusChanged = Notification.Name("contactOnlineStatusChanged") - public static let receivedNewMessages = Notification.Name("receivedNewMessages") - - // Friend request + public static let newMessagesReceived = Notification.Name("newMessagesReceived") public static let threadFriendRequestStatusChanged = Notification.Name("threadFriendRequestStatusChanged") public static let messageFriendRequestStatusChanged = Notification.Name("messageFriendRequestStatusChanged") } @@ -12,9 +10,7 @@ public extension Notification.Name { @objc public extension NSNotification { @objc public static let contactOnlineStatusChanged = Notification.Name.contactOnlineStatusChanged.rawValue as NSString - @objc public static let receivedNewMessages = Notification.Name.receivedNewMessages.rawValue as NSString - - // Friend request + @objc public static let newMessagesReceived = Notification.Name.newMessagesReceived.rawValue as NSString @objc public static let threadFriendRequestStatusChanged = Notification.Name.threadFriendRequestStatusChanged.rawValue as NSString @objc public static let messageFriendRequestStatusChanged = Notification.Name.messageFriendRequestStatusChanged.rawValue as NSString }