From 1796fd8e607440b726c18922a4e3967b1037cdd3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 22 May 2019 16:04:51 +1000 Subject: [PATCH] Prepare for P2P logic integration --- .../src/Loki/API/LokiAPI+SwarmAPI.swift | 46 ++++++++++ SignalServiceKit/src/Loki/API/LokiAPI.swift | 91 ++++++++----------- .../src/Messages/OWSMessageSender.m | 3 +- 3 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift new file mode 100644 index 000000000..4b4018172 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -0,0 +1,46 @@ +import PromiseKit + +extension LokiAPI { + + // MARK: Settings + private static let targetSnodeCount = 2 + private static let defaultSnodePort: UInt16 = 8080 + + // MARK: Caching + private static var swarmCache: [String:[Target]] = [:] + + // MARK: Internal API + private static func getRandomSnode() -> Promise { + return Promise { seal in + seal.fulfill(Target(address: "http://13.238.53.205", port: 8080)) // TODO: For debugging purposes + } + } + + private static func getSwarm(for hexEncodedPublicKey: String) -> Promise<[Target]> { + if let cachedSwarm = swarmCache[hexEncodedPublicKey], cachedSwarm.count >= targetSnodeCount { + return Promise<[Target]> { $0.fulfill(cachedSwarm) } + } else { + let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] + return getRandomSnode().then { invoke(.getSwarm, on: $0, with: parameters) }.map { parseTargets(from: $0) }.get { swarmCache[hexEncodedPublicKey] = $0 } + } + } + + internal static func getTargetSnodes(for hexEncodedPublicKey: String) -> Promise<[Target]> { + // shuffled() uses the system's default random generator, which is cryptographically secure + return getSwarm(for: hexEncodedPublicKey).map { Array($0.shuffled().prefix(targetSnodeCount)) } + } + + // MARK: Parsing + private static func parseTargets(from rawResponse: Any) -> [Target] { + // TODO: For debugging purposes + // ======== + let target = Target(address: "http://13.238.53.205", port: defaultSnodePort) + return Array(repeating: target, count: 3) + // ======== +// guard let json = rawResponse as? JSON, let addresses = json["snodes"] as? [String] else { +// Logger.warn("[Loki] Failed to parse targets from: \(rawResponse).") +// return [] +// } +// return addresses.map { Target(address: $0, port: defaultSnodePort) } + } +} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 826c41bdd..6d0769096 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -5,20 +5,17 @@ import PromiseKit // MARK: Settings private static let version = "v1" - private static let defaultSnodePort: UInt16 = 8080 - private static let targetSnodeCount = 2 - public static let defaultMessageTTL: UInt64 = 4 * 24 * 60 * 60 - - // MARK: Caching - private static var swarmCache: [String:[Target]] = [:] + public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 // MARK: Types - private struct Target : Hashable { + internal struct Target : Hashable { let address: String let port: UInt16 enum Method : String { + /// Only applicable to snode targets. case getSwarm = "get_snodes_for_pubkey" + /// Only applicable to snode targets. case getMessages = "retrieve" case sendMessage = "store" } @@ -27,6 +24,7 @@ import PromiseKit public typealias RawResponse = Any public enum Error : LocalizedError { + /// Only applicable to snode targets as proof of work isn't required for P2P messaging. case proofOfWorkCalculationFailed public var errorDescription: String? { @@ -40,32 +38,12 @@ import PromiseKit override private init() { } // MARK: Internal API - private static func invoke(_ method: Target.Method, on target: Target, with parameters: [String:Any] = [:]) -> Promise { + internal static func invoke(_ method: Target.Method, on target: Target, with parameters: [String:Any] = [:]) -> Promise { let url = URL(string: "\(target.address):\(target.port)/\(version)/storage_rpc")! let request = TSRequest(url: url, method: "POST", parameters: [ "method" : method.rawValue, "params" : parameters ]) return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject } } - private static func getRandomSnode() -> Promise { - return Promise { seal in - seal.fulfill(Target(address: "http://13.238.53.205", port: 8080)) // TODO: For debugging purposes - } - } - - private static func getSwarm(for hexEncodedPublicKey: String) -> Promise<[Target]> { - if let cachedSwarm = swarmCache[hexEncodedPublicKey], cachedSwarm.count >= targetSnodeCount { - return Promise<[Target]> { $0.fulfill(cachedSwarm) } - } else { - let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] - return getRandomSnode().then { invoke(.getSwarm, on: $0, with: parameters) }.map { parseTargets(from: $0) }.get { swarmCache[hexEncodedPublicKey] = $0 } - } - } - - private static func getTargetSnodes(for hexEncodedPublicKey: String) -> Promise<[Target]> { - // shuffled() uses the system's default random generator, which is cryptographically secure - return getSwarm(for: hexEncodedPublicKey).map { Array($0.shuffled().prefix(targetSnodeCount)) } - } - // MARK: Public API public static func getMessages() -> Promise>> { let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey @@ -81,45 +59,41 @@ import PromiseKit }.map { Set($0) } } + public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, timestamp: UInt64) -> Promise>> { + let isP2PMessagingPossible = false + return Message.from(signalMessage: signalMessage, timestamp: timestamp, requiringPoW: !isP2PMessagingPossible).then(sendMessage) + } + public static func sendMessage(_ lokiMessage: Message) -> Promise>> { - let parameters = lokiMessage.toJSON() - return getTargetSnodes(for: lokiMessage.destination).mapValues { invoke(.sendMessage, on: $0, with: parameters).recoverNetworkErrorIfNeeded(on: DispatchQueue.global()) }.map { Set($0) } + let isP2PMessagingPossible = false + if isP2PMessagingPossible { + // TODO: Send using P2P protocol + } else { + let parameters = lokiMessage.toJSON() + return getTargetSnodes(for: lokiMessage.destination).mapValues { invoke(.sendMessage, on: $0, with: parameters).recoverNetworkErrorIfNeeded(on: DispatchQueue.global()) }.map { Set($0) } + } } public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { - let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] // TODO: Figure out correct parameters - return getTargetSnodes(for: hexEncodedPublicKey).mapValues { invoke(.sendMessage, on: $0, with: parameters).recoverNetworkErrorIfNeeded(on: DispatchQueue.global()) }.map { Set($0) } + let isP2PMessagingPossible = false + if isP2PMessagingPossible { + // TODO: Send using P2P protocol + } else { + let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] // TODO: Figure out correct parameters + return getTargetSnodes(for: hexEncodedPublicKey).mapValues { invoke(.sendMessage, on: $0, with: parameters).recoverNetworkErrorIfNeeded(on: DispatchQueue.global()) }.map { Set($0) } + } } // MARK: Public API (Obj-C) - @objc public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, timestamp: UInt64, requiringPoW isPoWRequired: Bool) -> AnyPromise { - let promise = Message.from(signalMessage: signalMessage, timestamp: timestamp, requiringPoW: isPoWRequired).then(sendMessage).mapValues { promise -> AnyPromise in - let anyPromise = AnyPromise(promise) - anyPromise.retainUntilComplete() - return anyPromise - }.map { Set($0) } - let anyPromise = AnyPromise(promise) - anyPromise.retainUntilComplete() - return anyPromise + @objc public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> AnyPromise { + let promise = sendSignalMessage(signalMessage, to: destination, timestamp: timestamp).mapValues { AnyPromise.from($0) }.map { Set($0) } + return AnyPromise.from(promise) } // MARK: Parsing // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. - private static func parseTargets(from rawResponse: Any) -> [Target] { - // TODO: For debugging purposes - // ======== - let target = Target(address: "http://13.238.53.205", port: 8080) - return Array(repeating: target, count: 3) - // ======== -// guard let json = rawResponse as? JSON, let addresses = json["snodes"] as? [String] else { -// Logger.warn("[Loki] Failed to parse targets from: \(rawResponse).") -// return [] -// } -// return addresses.map { Target(address: $0, port: defaultSnodePort) } - } - private static func updateLastMessageHashValueIfPossible(for target: Target, from rawMessages: [JSON]) { guard let lastMessage = rawMessages.last, let hashValue = lastMessage["hash"] as? String, let expiresAt = lastMessage["expiration"] as? Int else { Logger.warn("[Loki] Failed to update last message hash value from: \(rawMessages).") @@ -187,6 +161,15 @@ import PromiseKit } } +private extension AnyPromise { + + static func from(_ promise: Promise) -> AnyPromise { + let result = AnyPromise(promise) + result.retainUntilComplete() + return result + } +} + // MARK: Error Handling private extension Promise { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 32299d3b1..515a0ea13 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1113,8 +1113,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [message.thread saveFriendRequestStatus:TSThreadFriendRequestStatusRequestSending withTransaction:nil]; [message saveFriendRequestStatus:TSMessageFriendRequestStatusPending withTransaction:nil]; } - BOOL isPoWRequired = YES; // TODO: Base on message type - [[LokiAPI objc_sendSignalMessage:signalMessage to:recipient.recipientId timestamp:message.timestamp requiringPoW:isPoWRequired] + [[LokiAPI objc_sendSignalMessage:signalMessage to:recipient.recipientId with:message.timestamp] .thenOn(OWSDispatch.sendingQueue, ^(id result) { NSSet *promises = (NSSet *)result; __block BOOL isSuccess = NO;