From 2694699e4e8449a41293f1e8e59ba64ce4d5cf83 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 11:49:05 +1000 Subject: [PATCH 01/22] Added LKAddressMessage. Hooked up p2p api. --- Pods | 2 +- Signal/Signal-Info.plist | 2 +- Signal/src/AppDelegate.m | 4 +- .../src/Loki/API/LokiAPI+P2P.swift | 75 +++++++++++++++++++ .../src/Loki/API/LokiAPI+SwarmAPI.swift | 2 +- SignalServiceKit/src/Loki/API/LokiAPI.swift | 11 +-- .../src/Loki/Messages/LKAddressMessage.h | 21 ++++++ .../src/Loki/Messages/LKAddressMessage.m | 62 +++++++++++++++ .../src/Messages/OWSMessageManager.m | 6 ++ 9 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift create mode 100644 SignalServiceKit/src/Loki/Messages/LKAddressMessage.h create mode 100644 SignalServiceKit/src/Loki/Messages/LKAddressMessage.m diff --git a/Pods b/Pods index 6174a578a..e5dc17be2 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 6174a578a93622fc02184ba8983fc809ef7bc0ec +Subproject commit e5dc17be2dc588fde41caa91484aa9e6b9c56ebd diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 258a90a89..7396df8e5 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -7,7 +7,7 @@ CarthageVersion 0.33.0 OSXVersion - 10.14.5 + 10.14.4 WebRTCCommit 1445d719bf05280270e9f77576f80f973fd847f8 M73 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 5f9be61a3..6cfb6395b 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -320,6 +320,7 @@ static NSTimeInterval launchStartedAt; if (self.lokiP2PServer.isRunning) { break; } BOOL isStarted = [self.lokiP2PServer startOnPort:port.unsignedIntegerValue]; if (isStarted) { + [LokiAPI setOurP2PAddressWithUrl:self.lokiP2PServer.serverURL]; OWSLogInfo(@"[Loki] Started server at %@.", self.lokiP2PServer.serverURL); break; } @@ -751,7 +752,8 @@ static NSTimeInterval launchStartedAt; // Loki: Start poller [Poller.shared startIfNeeded]; - // TODO: Ping friends to let them know we're online + // Loki: Tell our friends that we are online + [LokiAPI broadcastOnlineStatus]; if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet."); diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift new file mode 100644 index 000000000..1f5c495d2 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -0,0 +1,75 @@ + +extension LokiAPI { + private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender + internal static var ourP2PAddress: Target? = nil + + /// Set our local P2P address + /// + /// - Parameter url: The url to our local server + @objc public static func setOurP2PAddress(url: URL) { + guard let port = url.port else { return } + let target = Target(address: url.absoluteString, port: UInt32(port)) + ourP2PAddress = target + } + + /// Broadcash an online message to all our friends. + /// This shouldn't be called inside a transaction. + @objc public static func broadcastOnlineStatus() { + AssertIsOnMainThread() + + let friendThreads = getAllFriendThreads() + for thread in friendThreads { + sendOnlineBroadcastMessage(forThread: thread) + } + } + + /// Get the `LokiAddressMessage` for the given thread. + /// + /// - Parameter thread: The contact thread. + /// - Returns: The `LokiAddressMessage` for that thread. + @objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? { + guard let ourAddress = ourP2PAddress else { + Logger.error("P2P Address not set") + return nil + } + + return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port) + } + + /// Send a `Loki Address` message to the given thread + /// + /// - Parameter thread: The contact thread to send the message to + @objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) { + AssertIsOnMainThread() + + guard let message = onlineBroadcastMessage(forThread: thread) else { + owsFailDebug("P2P Address not set") + return + } + + messageSender.sendPromise(message: message).catch { error in + Logger.warn("Failed to send online status to \(thread.contactIdentifier())") + } + } + + @objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread, transaction: YapDatabaseReadWriteTransaction) { + guard let ourAddress = ourP2PAddress else { + owsFailDebug("P2P Address not set") + return + } + } + + private static func getAllFriendThreads() -> [TSContactThread] { + var friendThreadIds = [String]() + TSContactThread.enumerateCollectionObjects { (object, _) in + guard let thread = object as? TSContactThread, let uniqueId = thread.uniqueId else { return } + + if thread.friendRequestStatus == .friends && thread.contactIdentifier() != ourHexEncodedPubKey { + friendThreadIds.append(thread.uniqueId!) + } + } + + return friendThreadIds.compactMap { TSContactThread.fetch(uniqueId: $0) } + } + +} diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 4b4018172..48d62d395 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -4,7 +4,7 @@ extension LokiAPI { // MARK: Settings private static let targetSnodeCount = 2 - private static let defaultSnodePort: UInt16 = 8080 + private static let defaultSnodePort: UInt32 = 8080 // MARK: Caching private static var swarmCache: [String:[Target]] = [:] diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 6d0769096..c20caa420 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -1,16 +1,18 @@ import PromiseKit @objc public final class LokiAPI : NSObject { - private static let storage = OWSPrimaryStorage.shared() + internal static let storage = OWSPrimaryStorage.shared() // MARK: Settings private static let version = "v1" public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 + internal static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + // MARK: Types internal struct Target : Hashable { let address: String - let port: UInt16 + let port: UInt32 enum Method : String { /// Only applicable to snode targets. @@ -46,10 +48,9 @@ import PromiseKit // MARK: Public API public static func getMessages() -> Promise>> { - let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey - return getTargetSnodes(for: hexEncodedPublicKey).mapValues { targetSnode in + return getTargetSnodes(for: ourHexEncodedPubKey).mapValues { targetSnode in let lastHash = getLastMessageHashValue(for: targetSnode) ?? "" - let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey, "lastHash" : lastHash ] + let parameters: [String:Any] = [ "pubKey" : ourHexEncodedPubKey, "lastHash" : lastHash ] return invoke(.getMessages, on: targetSnode, with: parameters).map { rawResponse in guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } updateLastMessageHashValueIfPossible(for: targetSnode, from: rawMessages) diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h new file mode 100644 index 000000000..e8128e436 --- /dev/null +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(LokiAddressMessage) +@interface LKAddressMessage : TSOutgoingMessage + +- (instancetype)initInThread:(nullable TSThread *)thread + address:(NSString *)address + port:(uint)port; + +@property (nonatomic, readonly) NSString *address; +@property (nonatomic, readonly) uint port; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m new file mode 100644 index 000000000..c28924c90 --- /dev/null +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -0,0 +1,62 @@ +#import "LKAddressMessage.h" +#import "NSDate+OWS.h" +#import "SignalRecipient.h" +#import + +@interface LKAddressMessage () + +@property (nonatomic) NSString *address; +@property (nonatomic) uint port; + +@end + +@implementation LKAddressMessage + +- (instancetype)initAddressMessageInThread:(nullable TSThread *)thread + address:(NSString *)address + port:(uint)port +{ + self = [super initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:nil attachmentIds:[NSMutableArray new] + expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + if (!self) { + return self; + } + + _address = address; + _port = port; + + return self; +} + +- (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient { + SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; + + // Se + SSKProtoLokiAddressMessageBuilder *addressBuilder = SSKProtoLokiAddressMessage.builder; + [addressBuilder setPtpAddress:self.address]; + [addressBuilder setPtpPort:self.port]; + + NSError *error; + SSKProtoLokiAddressMessage *addressMessage = [addressBuilder buildAndReturnError:&error]; + if (error || !addressMessage) { + OWSFailDebug(@"Failed to build lokiAddressMessage for %@: %@", recipient.recipientId, error); + } else { + [contentBuilder setLokiAddressMessage:addressMessage]; + } + + return contentBuilder; +} + +// We don't need to send any data message in this address message +- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId { + return nil; +} + +- (BOOL)shouldBeSaved { return false; } + +- (uint)ttl { + // Address messages should only last 1 minute + return 1 * kMinuteInterval; +} + +@end diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 3e85b5a64..fc9f841be 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1500,6 +1500,12 @@ NS_ASSUME_NONNULL_BEGIN if (existingFriendRequestMessage != nil && existingFriendRequestMessage.isFriendRequest) { [existingFriendRequestMessage saveFriendRequestStatus:TSMessageFriendRequestStatusAccepted withTransaction:transaction]; } + + // Send our p2p details to the other user + LKAddressMessage *_Nullable onlineMessage = [LokiAPI onlineBroadcastMessageForThread:thread]; + if (onlineMessage != nil) { + [self.messageSenderJobQueue addMessage:onlineMessage transaction:transaction]; + } } } From bf1c2f432746082ab3af6eed01897fe6af032881 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 13:36:50 +1000 Subject: [PATCH 02/22] Send to p2p server first before falling back to storage server. --- Pods | 2 +- .../src/Loki/API/LokiAPI+Message.swift | 70 ++++++++++++------- .../src/Loki/API/LokiAPI+P2P.swift | 14 ++++ SignalServiceKit/src/Loki/API/LokiAPI.swift | 45 +++++++++--- .../src/Loki/Utilities/Promise+Wrap.swift | 9 +++ .../src/Messages/OWSMessageManager.m | 5 ++ 6 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift diff --git a/Pods b/Pods index e5dc17be2..04f0c4baf 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit e5dc17be2dc588fde41caa91484aa9e6b9c56ebd +Subproject commit 04f0c4baf6a53b9e2e2f161d5da3574b2dbd333d diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index 021348535..c0f070ddc 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -12,11 +12,42 @@ public extension LokiAPI { /// When the proof of work was calculated, if applicable. /// /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - let timestamp: UInt64? + var timestamp: UInt64? = nil /// The base 64 encoded proof of work, if applicable. - let nonce: String? + var nonce: String? = nil - public init(destination: String, data: LosslessStringConvertible, ttl: UInt64, timestamp: UInt64?, nonce: String?) { + /// Construct a `LokiMessage` from a `SignalMessage`. + /// + /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). + public static func from(signalMessage: SignalMessage, timestamp: UInt64) -> Message? { + // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object + do { + let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) + let data = wrappedMessage.base64EncodedString() + let destination = signalMessage["destination"] as! String + var ttl = LokiAPI.defaultMessageTTL + if let messageTTL = signalMessage["ttl"] as? UInt, messageTTL > 0 { ttl = UInt64(messageTTL) } + return Message(destination: destination, data: data, ttl: ttl, timestamp: nil, nonce: nil) + } catch let error { + Logger.debug("[Loki] Failed to convert Signal message to Loki message: \(signalMessage)") + return nil + } + } + + /// Create a basic loki message. + /// + /// - Parameters: + /// - destination: The destination + /// - data: The data + /// - ttl: The time to live + public init(destination: String, data: LosslessStringConvertible, ttl: UInt64) { + self.destination = destination + self.data = data + self.ttl = ttl + } + + /// Private init for setting proof of work. Use `calculatePoW` to get a message with these fields + private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, timestamp: UInt64?, nonce: String?) { self.destination = destination self.data = data self.ttl = ttl @@ -24,34 +55,19 @@ public extension LokiAPI { self.nonce = nonce } - /// Construct a `LokiMessage` from a `SignalMessage`. + /// Calculate the proof of work for this message /// - /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). - public static func from(signalMessage: SignalMessage, timestamp: UInt64, requiringPoW isPoWRequired: Bool) -> Promise { + /// - Returns: This will return a promise with a new message which contains the proof of work + public func calculatePoW() -> Promise { // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object return Promise { seal in DispatchQueue.global(qos: .default).async { - do { - let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) - let data = wrappedMessage.base64EncodedString() - let destination = signalMessage["destination"] as! String - var ttl = LokiAPI.defaultMessageTTL - if let messageTTL = signalMessage["ttl"] as? UInt, messageTTL > 0 { ttl = UInt64(messageTTL) } - if isPoWRequired { - // The storage server takes a time interval in milliseconds - let now = NSDate.ows_millisecondTimeStamp() - if let nonce = ProofOfWork.calculate(data: data, pubKey: destination, timestamp: now, ttl: ttl) { - let result = Message(destination: destination, data: data, ttl: ttl, timestamp: now, nonce: nonce) - seal.fulfill(result) - } else { - seal.reject(Error.proofOfWorkCalculationFailed) - } - } else { - let result = Message(destination: destination, data: data, ttl: ttl, timestamp: nil, nonce: nil) - seal.fulfill(result) - } - } catch let error { - seal.reject(error) + let now = NSDate.ows_millisecondTimeStamp() + if let nonce = ProofOfWork.calculate(data: self.data as! String, pubKey: self.destination, timestamp: now, ttl: self.ttl) { + let result = Message(destination: self.destination, data: self.data, ttl: self.ttl, timestamp: now, nonce: nonce) + seal.fulfill(result) + } else { + seal.reject(Error.proofOfWorkCalculationFailed) } } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index 1f5c495d2..6be59d91f 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -3,6 +3,20 @@ extension LokiAPI { private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender internal static var ourP2PAddress: Target? = nil + /// This is where we store the p2p details of our contacts + internal static var contactP2PDetails = [String: Target]() + + /// Set the Contact p2p details + /// + /// - Parameters: + /// - pubKey: The public key of the contact + /// - address: The contacts p2p address + /// - port: The contacts p2p port + @objc public static func setContactP2PDetails(forContact pubKey: String, address: String, port: UInt32) { + let target = Target(address: address, port: port) + contactP2PDetails[pubKey] = target + } + /// Set our local P2P address /// /// - Parameter url: The url to our local server diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index c20caa420..a2f6adbae 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -29,9 +29,13 @@ import PromiseKit /// Only applicable to snode targets as proof of work isn't required for P2P messaging. case proofOfWorkCalculationFailed + // Failed to send the message' + case internalError + public var errorDescription: String? { switch self { case .proofOfWorkCalculationFailed: return NSLocalizedString("Failed to calculate proof of work.", comment: "") + case .internalError: return "Failed while trying to send message" } } } @@ -61,18 +65,39 @@ import PromiseKit } 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) + guard let message = Message.from(signalMessage: signalMessage, timestamp: timestamp) else { + return Promise(error: Error.internalError) + } + + // Send message through the storage server + // We put this here because `recover` expects `Promise>>` + let sendThroughStorageServer: () -> Promise>> = { () in + return message.calculatePoW().then { powMessage -> Promise>> in + let snodes = getTargetSnodes(for: powMessage.destination) + return sendMessage(powMessage, targets: snodes) + } + } + + // By default our p2p throws and we recover by sending to storage server + var p2pPromise: Promise>> = Promise(error: Error.internalError) + // TODO: probably only send to p2p if user is online or we are pinging them + // p2pDetails && (isPing || peerIsOnline) + if let p2pDetails = contactP2PDetails[destination] { + p2pPromise = sendMessage(message, targets: [p2pDetails]) + } + + // If we have the p2p details then send message to that + // If that failes then fallback to storage server + return p2pPromise.recover { _ in return sendThroughStorageServer() } } - public static func sendMessage(_ lokiMessage: Message) -> Promise>> { - 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) } - } + internal static func sendMessage(_ lokiMessage: Message, targets: [Target]) -> Promise>> { + return sendMessage(lokiMessage, targets: Promise.wrap(targets)) + } + + internal static func sendMessage(_ lokiMessage: Message, targets: Promise<[Target]>) -> Promise>> { + let parameters = lokiMessage.toJSON() + return targets.mapValues { invoke(.sendMessage, on: $0, with: parameters).recoverNetworkErrorIfNeeded(on: DispatchQueue.global()) }.map { Set($0) } } public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift new file mode 100644 index 000000000..9affd8f8b --- /dev/null +++ b/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift @@ -0,0 +1,9 @@ +import PromiseKit + +public extension Promise { + static func wrap(_ value: T) -> Promise { + return Promise { resolver in + resolver.fulfill(value) + } + } +} diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index fc9f841be..9d9391160 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -432,6 +432,11 @@ NS_ASSUME_NONNULL_BEGIN } [self.primaryStorage setPreKeyBundle:bundle forContact:envelope.source transaction:transaction]; } + + // Loki: Check if we got p2p address + if (contentProto.lokiAddressMessage) { + [LokiAPI setContactP2PDetailsForContact:envelope.source address:contentProto.lokiAddressMessage.ptpAddress port:contentProto.lokiAddressMessage.ptpPort]; + } if (contentProto.syncMessage) { [self throws_handleIncomingEnvelope:envelope From 4412ec8f15cb53a778cbf832a35417ddc8f3734a Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 15:01:01 +1000 Subject: [PATCH 03/22] Made LKAddressMessage a subclass of LKEphemeralMessage. This is because we can't send a body with a nil message :( --- SignalServiceKit/src/Loki/API/LokiAPI.swift | 1 + .../src/Loki/Messages/LKAddressMessage.h | 7 ++----- .../src/Loki/Messages/LKAddressMessage.m | 16 ++++------------ .../src/Loki/Messages/LKEphemeralMessage.h | 2 ++ .../src/Loki/Messages/LKEphemeralMessage.m | 8 ++++++-- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index a2f6adbae..50d19e8f7 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -82,6 +82,7 @@ import PromiseKit var p2pPromise: Promise>> = Promise(error: Error.internalError) // TODO: probably only send to p2p if user is online or we are pinging them // p2pDetails && (isPing || peerIsOnline) + if let p2pDetails = contactP2PDetails[destination] { p2pPromise = sendMessage(message, targets: [p2pDetails]) } diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h index e8128e436..9d928ce1c 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h @@ -1,13 +1,10 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// -#import +#import "LKEphemeralMessage.h" NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(LokiAddressMessage) -@interface LKAddressMessage : TSOutgoingMessage +@interface LKAddressMessage : LKEphemeralMessage - (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m index c28924c90..0e8c80842 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -12,12 +12,11 @@ @implementation LKAddressMessage -- (instancetype)initAddressMessageInThread:(nullable TSThread *)thread - address:(NSString *)address - port:(uint)port +- (instancetype)initInThread:(nullable TSThread *)thread + address:(NSString *)address + port:(uint)port { - self = [super initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:nil attachmentIds:[NSMutableArray new] - expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + self = [super initInThread:thread]; if (!self) { return self; } @@ -47,13 +46,6 @@ return contentBuilder; } -// We don't need to send any data message in this address message -- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId { - return nil; -} - -- (BOOL)shouldBeSaved { return false; } - - (uint)ttl { // Address messages should only last 1 minute return 1 * kMinuteInterval; diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h index 16e318f75..92e67db2d 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h @@ -8,6 +8,8 @@ NS_SWIFT_NAME(EphemeralMessage) /// Used to establish sessions. + (LKEphemeralMessage *)createEmptyOutgoingMessageInThread:(TSThread *)thread; +- (instancetype)initInThread:(nullable TSThread *)thread; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m index 658e9b795..76ef20119 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m @@ -4,8 +4,12 @@ @implementation LKEphemeralMessage + (LKEphemeralMessage *)createEmptyOutgoingMessageInThread:(TSThread *)thread { - return [[LKEphemeralMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] - expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + return [[LKEphemeralMessage alloc] initInThread:thread]; +} + +- (instancetype)initInThread:(nullable TSThread *)thread { + return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] + expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; } - (BOOL)shouldBeSaved { return NO; } From 6337ab076f1d9cc5c10757c1848085ef9300cf96 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 15:23:57 +1000 Subject: [PATCH 04/22] Updated ip. --- SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift | 4 ++-- SignalServiceKit/src/TSConstants.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 48d62d395..e9900f3a6 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -12,7 +12,7 @@ extension LokiAPI { // 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 + seal.fulfill(Target(address: "http://13.236.173.190", port: 8080)) // TODO: For debugging purposes } } @@ -34,7 +34,7 @@ extension LokiAPI { private static func parseTargets(from rawResponse: Any) -> [Target] { // TODO: For debugging purposes // ======== - let target = Target(address: "http://13.238.53.205", port: defaultSnodePort) + let target = Target(address: "http://13.236.173.190", port: defaultSnodePort) return Array(repeating: target, count: 3) // ======== // guard let json = rawResponse as? JSON, let addresses = json["snodes"] as? [String] else { diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h index 10ae5d78f..e2a5b10c5 100644 --- a/SignalServiceKit/src/TSConstants.h +++ b/SignalServiceKit/src/TSConstants.h @@ -27,7 +27,7 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { // Production #define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/" -#define textSecureServerURL @"http://13.238.53.205" // TODO: Temporary +#define textSecureServerURL @"http://13.236.173.190" // TODO: Temporary #define textSecureCDNServerURL @"https://cdn.signal.org" // Use same reflector for service and CDN #define textSecureServiceReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" From 80911787e6f47c3e9baf48e4f438139fe4da20df Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 15:50:37 +1000 Subject: [PATCH 05/22] Fix incorrect ttl. --- SignalServiceKit/src/Loki/API/LokiAPI+Message.swift | 2 +- SignalServiceKit/src/Loki/Messages/LKAddressMessage.m | 2 +- SignalServiceKit/src/Loki/Messages/LKFriendRequestMessage.m | 2 +- SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index c0f070ddc..78b57f1cf 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -7,7 +7,7 @@ public extension LokiAPI { let destination: String /// The content of the message. let data: LosslessStringConvertible - /// The time to live for the message in seconds. + /// The time to live for the message in milliseconds. let ttl: UInt64 /// When the proof of work was calculated, if applicable. /// diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m index 0e8c80842..4a9d85d75 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -48,7 +48,7 @@ - (uint)ttl { // Address messages should only last 1 minute - return 1 * kMinuteInterval; + return 1 * kMinuteInMs; } @end diff --git a/SignalServiceKit/src/Loki/Messages/LKFriendRequestMessage.m b/SignalServiceKit/src/Loki/Messages/LKFriendRequestMessage.m index cddf75886..9897e988e 100644 --- a/SignalServiceKit/src/Loki/Messages/LKFriendRequestMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKFriendRequestMessage.m @@ -8,7 +8,7 @@ - (BOOL)isFriendRequest { return YES; } -- (uint)ttl { return 4 * kDayInterval; } // Friend requests should stay for the longest on the storage server +- (uint)ttl { return 4 * kDayInMs; } // Friend requests should stay for the longest on the storage server - (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient { SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 7c8fd30ff..cc15308e1 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -1157,7 +1157,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt - (uint)ttl { // Time to live for all messages (except friend request messages) should be 1 day // TODO: Change this to return a value that the user chose - return 1 * kDayInterval; + return 1 * kDayInMs; } @end From b15edf0597ec2ea4450297a217b7c8565eff585f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 23 May 2019 16:13:37 +1000 Subject: [PATCH 06/22] Fix incorrect address format being sent. --- SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index 6be59d91f..41bbcf968 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -21,8 +21,8 @@ extension LokiAPI { /// /// - Parameter url: The url to our local server @objc public static func setOurP2PAddress(url: URL) { - guard let port = url.port else { return } - let target = Target(address: url.absoluteString, port: UInt32(port)) + guard let scheme = url.scheme, let host = url.host, let port = url.port else { return } + let target = Target(address: "\(scheme)://\(host)", port: UInt32(port)) ourP2PAddress = target } From b3ef8af4760a9caee04340f1b5a856f0097d0263 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 09:33:41 +1000 Subject: [PATCH 07/22] Fix PoW Calculation. --- SignalServiceKit/src/Loki/API/LokiAPI.swift | 11 ++++------- SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 59a511965..86f30afb6 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -65,18 +65,15 @@ import PromiseKit } } - // By default our p2p throws and we recover by sending to storage server - var p2pPromise: Promise>> = Promise(error: Error.internalError) + // If we have the p2p details then send message to that + // If that failes then fallback to storage server // TODO: probably only send to p2p if user is online or we are pinging them // p2pDetails && (isPing || peerIsOnline) - if let p2pDetails = contactP2PDetails[destination] { - p2pPromise = sendMessage(message, targets: [p2pDetails]) + return sendMessage(message, targets: [p2pDetails]).recover { _ in return sendThroughStorageServer() } } - // If we have the p2p details then send message to that - // If that failes then fallback to storage server - return p2pPromise.recover { _ in return sendThroughStorageServer() } + return sendThroughStorageServer() } internal static func sendMessage(_ lokiMessage: Message, targets: [Target]) -> Promise>> { diff --git a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift index 7d2afd2a2..87b331e05 100644 --- a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift +++ b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift @@ -103,7 +103,8 @@ public enum ProofOfWork { // Do all the calculations let totalLength = UInt64(payloadLength + nonceLength) - let ttlMult = ttl * totalLength + let ttlSeconds = ttl / 1000 + let ttlMult = ttlSeconds * totalLength // UInt64 values let innerFrac = ttlMult / two16 From cead2e3942f647e332992a58e04289057a975742 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 09:44:22 +1000 Subject: [PATCH 08/22] More PoW fixes. --- SignalServiceKit/src/Loki/API/LokiAPI+Message.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index be35dd438..78b57f1cf 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -63,8 +63,7 @@ public extension LokiAPI { return Promise { seal in DispatchQueue.global(qos: .default).async { let now = NSDate.ows_millisecondTimeStamp() - let ttlInSeconds = ttl / 1000 - if let nonce = ProofOfWork.calculate(data: self.data as! String, pubKey: self.destination, timestamp: now, ttl: ttlInSeconds) { + if let nonce = ProofOfWork.calculate(data: self.data as! String, pubKey: self.destination, timestamp: now, ttl: self.ttl) { let result = Message(destination: self.destination, data: self.data, ttl: self.ttl, timestamp: now, nonce: nonce) seal.fulfill(result) } else { From 20f0b239514719ef9ff8283dd7479771e007e9d7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 13:45:38 +1000 Subject: [PATCH 09/22] Refactoring. --- .../src/Loki/API/LokiAPI+P2P.swift | 2 +- SignalServiceKit/src/Loki/API/LokiAPI.swift | 40 +++++++++---------- .../src/Loki/Crypto/ProofOfWork.swift | 2 +- .../src/Loki/Messages/LKEphemeralMessage.m | 1 + 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index 41bbcf968..3c91520c1 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -63,7 +63,7 @@ extension LokiAPI { messageSender.sendPromise(message: message).catch { error in Logger.warn("Failed to send online status to \(thread.contactIdentifier())") - } + }.retainUntilComplete() } @objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread, transaction: YapDatabaseReadWriteTransaction) { diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 1068acca6..b925740d9 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -51,6 +51,23 @@ import PromiseKit }.map { Set($0) } } + public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { + 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, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { Set($0) } + } + } + + // MARK: Public API (Obj-C) + @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: Sending public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, timestamp: UInt64) -> Promise>> { guard let message = Message.from(signalMessage: signalMessage, timestamp: timestamp) else { return Promise(error: Error.internalError) @@ -70,37 +87,18 @@ import PromiseKit // TODO: probably only send to p2p if user is online or we are pinging them // p2pDetails && (isPing || peerIsOnline) if let p2pDetails = contactP2PDetails[destination] { - return sendMessage(message, targets: [p2pDetails]).recover { _ in return sendThroughStorageServer() } + let targets = Promise.wrap([p2pDetails]) + return sendMessage(message, targets: targets).recover { _ in return sendThroughStorageServer() } } return sendThroughStorageServer() } - internal static func sendMessage(_ lokiMessage: Message, targets: [Target]) -> Promise>> { - return sendMessage(lokiMessage, targets: Promise.wrap(targets)) - } - internal static func sendMessage(_ lokiMessage: Message, targets: Promise<[Target]>) -> Promise>> { let parameters = lokiMessage.toJSON() return targets.mapValues { invoke(.sendMessage, on: $0, associatedWith: lokiMessage.destination, parameters: parameters) }.map { Set($0) } } - public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { - 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, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { Set($0) } - } - } - - // MARK: Public API (Obj-C) - @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. diff --git a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift index 2d1c72673..478f37964 100644 --- a/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift +++ b/SignalServiceKit/src/Loki/Crypto/ProofOfWork.swift @@ -63,7 +63,7 @@ public enum ProofOfWork { /// - data: The message data /// - pubKey: The message recipient /// - timestamp: The timestamp - /// - ttl: The message time to live, in **seconds** + /// - ttl: The message time to live in milliseconds /// - Returns: A nonce string or `nil` if it failed public static func calculate(data: String, pubKey: String, timestamp: UInt64, ttl: UInt64) -> String? { let payload = createPayload(pubKey: pubKey, data: data, timestamp: timestamp, ttl: ttl) diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m index 76ef20119..90be9f31c 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m @@ -12,6 +12,7 @@ expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; } +- (BOOL)shouldSyncTranscript { return NO; } - (BOOL)shouldBeSaved { return NO; } @end From 7f679ba5ed76b19cce1f61a0d7683ee2b51450d6 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 15:20:49 +1000 Subject: [PATCH 10/22] Added P2P pinging logic. --- .../src/Loki/API/LokiAPI+P2P.swift | 68 ++++++++++++++++++- SignalServiceKit/src/Loki/API/LokiAPI.swift | 2 +- .../src/Messages/OWSMessageManager.m | 4 +- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index 3c91520c1..00e3453a0 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -1,10 +1,72 @@ extension LokiAPI { + private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender + + /// A p2p state struct + internal struct P2PDetails { + var address: String + var port: UInt32 + var isOnline: Bool + var timerDuration: Double + var pingTimer: WeakTimer? = nil + + var target: Target { + return Target(address: address, port: port) + } + } + internal static var ourP2PAddress: Target? = nil /// This is where we store the p2p details of our contacts - internal static var contactP2PDetails = [String: Target]() + internal static var contactP2PDetails = [String: P2PDetails]() + + /// Handle P2P logic when we receive a `LokiAddressMessage` + /// + /// - Parameters: + /// - pubKey: The other users pubKey + /// - address: The pther users p2p address + /// - port: The other users p2p port + /// - receivedThroughP2P: Wether we received the message through p2p + @objc public static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) { + // Stagger the ping timers so that contacts don't ping each other at the same time + let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval + + // Get out current contact details + let oldContactDetails = contactP2PDetails[pubKey] + + // Set the new contact details + // A contact is always assumed to be offline unless the specific conditions below are met + let details = P2PDetails(address: address, port: port, isOnline: false, timerDuration: timerDuration, pingTimer: nil) + + // Set up our checks + let oldContactExists = oldContactDetails != nil + let wasOnline = oldContactDetails?.isOnline ?? false + let p2pDetailsMatch = oldContactDetails?.address == address && oldContactDetails?.port == port + + /* + We need to check if we should ping the user. + We don't ping the user IF: + - We had old contact details + - We got a P2P message + - The old contact was set as `Online` + - The new p2p details match the old one + */ + if oldContactExists && receivedThroughP2P && wasOnline && p2pDetailsMatch { + // TODO: Set contact to online and start the ping timers + return; + } + + /* + Ping the contact. + This happens in the following scenarios: + 1. We didn't have the contact, we need to ping them to let them know our details. + 2. wasP2PMessage = false, so we assume the contact doesn't have our details. + 3. We had the contact marked as offline, we need to make sure that we can reach their server. + 4. The other contact details have changed, we need to make sure that we can reach their new server. + */ + // TODO: Ping the contact + } /// Set the Contact p2p details /// @@ -13,8 +75,8 @@ extension LokiAPI { /// - address: The contacts p2p address /// - port: The contacts p2p port @objc public static func setContactP2PDetails(forContact pubKey: String, address: String, port: UInt32) { - let target = Target(address: address, port: port) - contactP2PDetails[pubKey] = target + let details = P2PDetails(address: address, port: port, isOnline: false, timerDuration: 10, pingTimer: nil) + contactP2PDetails[pubKey] = details } /// Set our local P2P address diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index b925740d9..e41bb628d 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -87,7 +87,7 @@ import PromiseKit // TODO: probably only send to p2p if user is online or we are pinging them // p2pDetails && (isPing || peerIsOnline) if let p2pDetails = contactP2PDetails[destination] { - let targets = Promise.wrap([p2pDetails]) + let targets = Promise.wrap([p2pDetails.target]) return sendMessage(message, targets: targets).recover { _ in return sendThroughStorageServer() } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 9d9391160..e79b66e4b 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -435,7 +435,9 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Check if we got p2p address if (contentProto.lokiAddressMessage) { - [LokiAPI setContactP2PDetailsForContact:envelope.source address:contentProto.lokiAddressMessage.ptpAddress port:contentProto.lokiAddressMessage.ptpPort]; + NSString *address = contentProto.lokiAddressMessage.ptpAddress; + uint32_t port = contentProto.lokiAddressMessage.ptpPort; + [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage] } if (contentProto.syncMessage) { From 455c8c520bfbd9f04ba88d67393572d577dc356c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 16:07:00 +1000 Subject: [PATCH 11/22] More pinging logic --- .../src/Loki/API/LokiAPI+Message.swift | 17 ++++++++--- .../src/Loki/API/LokiAPI+P2P.swift | 25 ++++++++++++++-- SignalServiceKit/src/Loki/API/LokiAPI.swift | 29 +++++++++---------- .../src/Loki/Messages/LKAddressMessage.h | 6 ++-- .../src/Loki/Messages/LKAddressMessage.m | 3 ++ .../src/Messages/OWSMessageManager.m | 2 +- .../src/Messages/OWSMessageSender.m | 10 +++++-- .../src/Messages/OWSMessageServiceParams.h | 6 +++- .../src/Messages/OWSMessageServiceParams.m | 2 ++ 9 files changed, 72 insertions(+), 28 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index 78b57f1cf..2a9daf067 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -9,6 +9,9 @@ public extension LokiAPI { let data: LosslessStringConvertible /// The time to live for the message in milliseconds. let ttl: UInt64 + /// Wether this message is a ping. + /// This should always be false unless it is from p2p pinging logic. + let isPing: Bool /// When the proof of work was calculated, if applicable. /// /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. @@ -25,9 +28,13 @@ public extension LokiAPI { let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) let data = wrappedMessage.base64EncodedString() let destination = signalMessage["destination"] as! String + var ttl = LokiAPI.defaultMessageTTL if let messageTTL = signalMessage["ttl"] as? UInt, messageTTL > 0 { ttl = UInt64(messageTTL) } - return Message(destination: destination, data: data, ttl: ttl, timestamp: nil, nonce: nil) + + let isPing = signalMessage["isPing"] as! Bool + + return Message(destination: destination, data: data, ttl: ttl, isPing: isPing) } catch let error { Logger.debug("[Loki] Failed to convert Signal message to Loki message: \(signalMessage)") return nil @@ -40,17 +47,19 @@ public extension LokiAPI { /// - destination: The destination /// - data: The data /// - ttl: The time to live - public init(destination: String, data: LosslessStringConvertible, ttl: UInt64) { + public init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool = false) { self.destination = destination self.data = data self.ttl = ttl + self.isPing = isPing } /// Private init for setting proof of work. Use `calculatePoW` to get a message with these fields - private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, timestamp: UInt64?, nonce: String?) { + private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool, timestamp: UInt64?, nonce: String?) { self.destination = destination self.data = data self.ttl = ttl + self.isPing = isPing self.timestamp = timestamp self.nonce = nonce } @@ -64,7 +73,7 @@ public extension LokiAPI { DispatchQueue.global(qos: .default).async { let now = NSDate.ows_millisecondTimeStamp() if let nonce = ProofOfWork.calculate(data: self.data as! String, pubKey: self.destination, timestamp: now, ttl: self.ttl) { - let result = Message(destination: self.destination, data: self.data, ttl: self.ttl, timestamp: now, nonce: nonce) + let result = Message(destination: self.destination, data: self.data, ttl: self.ttl, isPing: self.isPing, timestamp: now, nonce: nonce) seal.fulfill(result) } else { seal.reject(Error.proofOfWorkCalculationFailed) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index 00e3453a0..d6b9ed770 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -1,15 +1,17 @@ extension LokiAPI { - private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender + /// The amount of time before pinging when a user is set to offline + private static let offlinePingTime = 2 * kMinuteInterval + /// A p2p state struct internal struct P2PDetails { var address: String var port: UInt32 var isOnline: Bool var timerDuration: Double - var pingTimer: WeakTimer? = nil + var pingTimer: Timer? = nil var target: Target { return Target(address: address, port: port) @@ -68,6 +70,23 @@ extension LokiAPI { // TODO: Ping the contact } + @objc public static func setOnline(_ isOnline: Bool, forContact pubKey: String) { + guard var details = contactP2PDetails[pubKey] else { return } + + let interval = isOnline ? details.timerDuration : offlinePingTime + + // Setup a new timer + details.pingTimer?.invalidate() + details.pingTimer = WeakTimer.scheduledTimer(timeInterval: interval, target: self, userInfo: nil, repeats: true) { _ in ping(contact: pubKey) } + details.isOnline = isOnline + + contactP2PDetails[pubKey] = details + } + + @objc public static func ping(contact pubKey: String) { + + } + /// Set the Contact p2p details /// /// - Parameters: @@ -109,7 +128,7 @@ extension LokiAPI { return nil } - return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port) + return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: false) } /// Send a `Loki Address` message to the given thread diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index e41bb628d..8096a4ddb 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -51,16 +51,6 @@ import PromiseKit }.map { Set($0) } } - public static func ping(_ hexEncodedPublicKey: String) -> Promise>> { - 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, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { Set($0) } - } - } - // MARK: Public API (Obj-C) @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) } @@ -82,13 +72,22 @@ import PromiseKit } } - // If we have the p2p details then send message to that + // If we have the p2p details and we have marked the user as online OR we are pinging the user, then use peer to peer // If that failes then fallback to storage server - // TODO: probably only send to p2p if user is online or we are pinging them - // p2pDetails && (isPing || peerIsOnline) - if let p2pDetails = contactP2PDetails[destination] { + if let p2pDetails = contactP2PDetails[destination], message.isPing || p2pDetails.isOnline { let targets = Promise.wrap([p2pDetails.target]) - return sendMessage(message, targets: targets).recover { _ in return sendThroughStorageServer() } + return sendMessage(message, targets: targets).recover { error -> Promise>> in + // The user is not online + LokiAPI.setOnline(false, forContact: destination) + + // If it was a ping then don't send to the storage server + if (message.isPing) { + Logger.warn("[Loki] Failed to ping \(destination) - Marking contact as offline.") + throw error + } + + return sendThroughStorageServer() + } } return sendThroughStorageServer() diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h index 9d928ce1c..ab0ffb604 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h @@ -7,11 +7,13 @@ NS_SWIFT_NAME(LokiAddressMessage) @interface LKAddressMessage : LKEphemeralMessage - (instancetype)initInThread:(nullable TSThread *)thread - address:(NSString *)address - port:(uint)port; + address:(NSString *)address + port:(uint)port + isPing:(BOOL)isPing; @property (nonatomic, readonly) NSString *address; @property (nonatomic, readonly) uint port; +@property (nonatomic, readonly) BOOL isPing; @end diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m index 4a9d85d75..e67dae035 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -7,6 +7,7 @@ @property (nonatomic) NSString *address; @property (nonatomic) uint port; +@property (nonatomic) BOOL isPing; @end @@ -15,6 +16,7 @@ - (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address port:(uint)port + isPing:(bool)isPing { self = [super initInThread:thread]; if (!self) { @@ -23,6 +25,7 @@ _address = address; _port = port; + _isPing = isPing; return self; } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index e79b66e4b..88c338db1 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -437,7 +437,7 @@ NS_ASSUME_NONNULL_BEGIN if (contentProto.lokiAddressMessage) { NSString *address = contentProto.lokiAddressMessage.ptpAddress; uint32_t port = contentProto.lokiAddressMessage.ptpPort; - [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage] + [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage]; } if (contentProto.syncMessage) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index c6c66f788..1b00df5de 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -44,6 +44,7 @@ #import "TSSocketManager.h" #import "TSThread.h" #import "LKFriendRequestMessage.h" +#import "LKAddressMessage.h" #import #import #import @@ -1792,7 +1793,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; isSilent:false isOnline:false registrationId:0 - ttl:message.ttl]; + ttl:message.ttl + isPing:false]; NSError *error; NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; @@ -1879,6 +1881,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; BOOL isSilent = message.isSilent; BOOL isOnline = message.isOnline; + + LKAddressMessage *_Nullable addressMessage = [message as:[LKAddressMessage class]]; + BOOL isPing = addressMessage != nil && addressMessage.isPing; OWSMessageServiceParams *messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType recipientId:recipientId @@ -1887,7 +1892,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; isSilent:isSilent isOnline:isOnline registrationId:[cipher throws_remoteRegistrationId:transaction] - ttl:message.ttl]; + ttl:message.ttl + isPing:isPing]; NSError *error; NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h b/SignalServiceKit/src/Messages/OWSMessageServiceParams.h index 3a8dc25c9..83f00ce3f 100644 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h +++ b/SignalServiceKit/src/Messages/OWSMessageServiceParams.h @@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Message ttl @property (nonatomic, readonly) uint ttl; +// Loki: Wether this message is a p2p ping +@property (nonatomic, readonly) BOOL isPing; + - (instancetype)initWithType:(TSWhisperMessageType)type recipientId:(NSString *)destination device:(int)deviceId @@ -34,7 +37,8 @@ NS_ASSUME_NONNULL_BEGIN isSilent:(BOOL)isSilent isOnline:(BOOL)isOnline registrationId:(int)registrationId - ttl:(uint)ttl; + ttl:(uint)ttl + isPing:(BOOL)isPing; @end diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m b/SignalServiceKit/src/Messages/OWSMessageServiceParams.m index cfd88bd3e..d7a0cbf12 100644 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m +++ b/SignalServiceKit/src/Messages/OWSMessageServiceParams.m @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN isOnline:(BOOL)isOnline registrationId:(int)registrationId ttl:(uint)ttl + isPing:(BOOL)isPing { self = [super init]; @@ -38,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN _silent = isSilent; _online = isOnline; _ttl = ttl; + _isPing = isPing; return self; } From af6a96965357cae2e64815a90324598b5827b7a2 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 24 May 2019 16:26:58 +1000 Subject: [PATCH 12/22] Very iffy ping logic - May not work --- .../src/Loki/API/LokiAPI+P2P.swift | 64 +++++++++++-------- SignalServiceKit/src/Loki/API/LokiAPI.swift | 5 +- .../src/Messages/OWSMessageManager.m | 2 +- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift index d6b9ed770..8711fdf7a 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift @@ -30,7 +30,7 @@ extension LokiAPI { /// - address: The pther users p2p address /// - port: The other users p2p port /// - receivedThroughP2P: Wether we received the message through p2p - @objc public static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) { + @objc public static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool, transaction: YapDatabaseReadTransaction) { // Stagger the ping timers so that contacts don't ping each other at the same time let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval @@ -55,7 +55,7 @@ extension LokiAPI { - The new p2p details match the old one */ if oldContactExists && receivedThroughP2P && wasOnline && p2pDetailsMatch { - // TODO: Set contact to online and start the ping timers + setOnline(true, forContact: pubKey) return; } @@ -67,37 +67,45 @@ extension LokiAPI { 3. We had the contact marked as offline, we need to make sure that we can reach their server. 4. The other contact details have changed, we need to make sure that we can reach their new server. */ - // TODO: Ping the contact + ping(contact: pubKey, withTransaction: transaction) } @objc public static func setOnline(_ isOnline: Bool, forContact pubKey: String) { - guard var details = contactP2PDetails[pubKey] else { return } - - let interval = isOnline ? details.timerDuration : offlinePingTime - - // Setup a new timer - details.pingTimer?.invalidate() - details.pingTimer = WeakTimer.scheduledTimer(timeInterval: interval, target: self, userInfo: nil, repeats: true) { _ in ping(contact: pubKey) } - details.isOnline = isOnline - - contactP2PDetails[pubKey] = details + // Make sure we are on the main thread + DispatchQueue.main.async { + guard var details = contactP2PDetails[pubKey] else { return } + + let interval = isOnline ? details.timerDuration : offlinePingTime + + // Setup a new timer + details.pingTimer?.invalidate() + details.pingTimer = WeakTimer.scheduledTimer(timeInterval: interval, target: self, userInfo: nil, repeats: true) { _ in ping(contact: pubKey) } + details.isOnline = isOnline + + contactP2PDetails[pubKey] = details + } } @objc public static func ping(contact pubKey: String) { - + AssertIsOnMainThread() + storage.dbReadConnection.read { transaction in + ping(contact: pubKey, withTransaction: transaction) + } } - /// Set the Contact p2p details - /// - /// - Parameters: - /// - pubKey: The public key of the contact - /// - address: The contacts p2p address - /// - port: The contacts p2p port - @objc public static func setContactP2PDetails(forContact pubKey: String, address: String, port: UInt32) { - let details = P2PDetails(address: address, port: port, isOnline: false, timerDuration: 10, pingTimer: nil) - contactP2PDetails[pubKey] = details + @objc public static func ping(contact pubKey: String, withTransaction transaction: YapDatabaseReadTransaction) { + guard let thread = TSContactThread.fetch(uniqueId: pubKey, transaction: transaction) else { + Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)") + return + } + guard let message = lokiAddressMessage(for: thread, isPing: true) else { + Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)") + return + } + + messageSender.sendPromise(message: message).retainUntilComplete() } - + /// Set our local P2P address /// /// - Parameter url: The url to our local server @@ -123,12 +131,16 @@ extension LokiAPI { /// - Parameter thread: The contact thread. /// - Returns: The `LokiAddressMessage` for that thread. @objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? { + return lokiAddressMessage(for: thread, isPing: false) + } + + private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? { guard let ourAddress = ourP2PAddress else { Logger.error("P2P Address not set") return nil } - - return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: false) + + return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing) } /// Send a `Loki Address` message to the given thread diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 8096a4ddb..706818ed0 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -76,7 +76,10 @@ import PromiseKit // If that failes then fallback to storage server if let p2pDetails = contactP2PDetails[destination], message.isPing || p2pDetails.isOnline { let targets = Promise.wrap([p2pDetails.target]) - return sendMessage(message, targets: targets).recover { error -> Promise>> in + return sendMessage(message, targets: targets).then { result -> Promise>> in + LokiAPI.setOnline(true, forContact: destination) + return Promise.wrap(result) + }.recover { error -> Promise>> in // The user is not online LokiAPI.setOnline(false, forContact: destination) diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 88c338db1..2c21590ce 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -437,7 +437,7 @@ NS_ASSUME_NONNULL_BEGIN if (contentProto.lokiAddressMessage) { NSString *address = contentProto.lokiAddressMessage.ptpAddress; uint32_t port = contentProto.lokiAddressMessage.ptpPort; - [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage]; + [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage transaction: transaction]; } if (contentProto.syncMessage) { From 88afca30c6be8dbf339d1c4df279ad9f4d58fe61 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 27 May 2019 09:50:37 +1000 Subject: [PATCH 13/22] Moved P2P logic to LokiP2PManager. --- Pods | 2 +- Signal/src/AppDelegate.m | 4 +- SignalServiceKit/src/Loki/API/LokiAPI.swift | 9 +- ...LokiAPI+P2P.swift => LokiP2PManager.swift} | 181 ++++++++++-------- .../src/Messages/OWSMessageManager.m | 14 +- 5 files changed, 117 insertions(+), 93 deletions(-) rename SignalServiceKit/src/Loki/API/{LokiAPI+P2P.swift => LokiP2PManager.swift} (61%) diff --git a/Pods b/Pods index 6eacc62ee..334390694 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 6eacc62ee03ae19105782f2ea60ac8ae46814788 +Subproject commit 3343906944c5db1e40599f4d36c35fd6dc75da20 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 6cfb6395b..4ec18a72b 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -320,7 +320,7 @@ static NSTimeInterval launchStartedAt; if (self.lokiP2PServer.isRunning) { break; } BOOL isStarted = [self.lokiP2PServer startOnPort:port.unsignedIntegerValue]; if (isStarted) { - [LokiAPI setOurP2PAddressWithUrl:self.lokiP2PServer.serverURL]; + [LokiP2PManager setOurP2PAddressWithUrl:self.lokiP2PServer.serverURL]; OWSLogInfo(@"[Loki] Started server at %@.", self.lokiP2PServer.serverURL); break; } @@ -753,7 +753,7 @@ static NSTimeInterval launchStartedAt; [Poller.shared startIfNeeded]; // Loki: Tell our friends that we are online - [LokiAPI broadcastOnlineStatus]; + [LokiP2PManager broadcastOnlineStatus]; if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet."); diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 706818ed0..8cd2e3cfe 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -7,7 +7,7 @@ import PromiseKit private static let version = "v1" public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 - internal static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey // MARK: Types public typealias RawResponse = Any @@ -74,18 +74,19 @@ import PromiseKit // If we have the p2p details and we have marked the user as online OR we are pinging the user, then use peer to peer // If that failes then fallback to storage server - if let p2pDetails = contactP2PDetails[destination], message.isPing || p2pDetails.isOnline { + if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), message.isPing || p2pDetails.isOnline { let targets = Promise.wrap([p2pDetails.target]) return sendMessage(message, targets: targets).then { result -> Promise>> in - LokiAPI.setOnline(true, forContact: destination) + LokiP2PManager.setOnline(true, forContact: destination) return Promise.wrap(result) }.recover { error -> Promise>> in // The user is not online - LokiAPI.setOnline(false, forContact: destination) + LokiP2PManager.setOnline(false, forContact: destination) // If it was a ping then don't send to the storage server if (message.isPing) { Logger.warn("[Loki] Failed to ping \(destination) - Marking contact as offline.") + error.isRetryable = false throw error } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift similarity index 61% rename from SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift rename to SignalServiceKit/src/Loki/API/LokiP2PManager.swift index 8711fdf7a..9d5ce3747 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+P2P.swift +++ b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift @@ -1,6 +1,8 @@ -extension LokiAPI { +@objc public class LokiP2PManager : NSObject { + private static let storage = OWSPrimaryStorage.shared() private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender + private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey /// The amount of time before pinging when a user is set to offline private static let offlinePingTime = 2 * kMinuteInterval @@ -13,15 +15,77 @@ extension LokiAPI { var timerDuration: Double var pingTimer: Timer? = nil - var target: Target { - return Target(address: address, port: port) + var target: LokiAPI.Target { + return LokiAPI.Target(address: address, port: port) } } - internal static var ourP2PAddress: Target? = nil + /// Our p2p address + private static var ourP2PAddress: LokiAPI.Target? = nil /// This is where we store the p2p details of our contacts - internal static var contactP2PDetails = [String: P2PDetails]() + private static var contactP2PDetails = [String: P2PDetails]() + + // MARK: - Public functions + + /// Set our local P2P address + /// + /// - Parameter url: The url to our local server + @objc public static func setOurP2PAddress(url: URL) { + guard let scheme = url.scheme, let host = url.host, let port = url.port else { return } + let target = LokiAPI.Target(address: "\(scheme)://\(host)", port: UInt32(port)) + ourP2PAddress = target + } + + /// Ping a contact + /// + /// - Parameter pubKey: The contact hex pubkey + @objc(pingContact:) + public static func ping(contact pubKey: String) { + // Dispatch on the main queue so we escape any transaction blocks + DispatchQueue.main.async { + guard let thread = TSContactThread.fetch(uniqueId: pubKey) else { + Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)") + return + } + guard let message = lokiAddressMessage(for: thread, isPing: true) else { + Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)") + return + } + + messageSender.sendPromise(message: message).retainUntilComplete() + } + } + + /// Broadcash an online message to all our friends. + /// This shouldn't be called inside a transaction. + @objc public static func broadcastOnlineStatus() { + // Escape any transaction blocks + DispatchQueue.main.async { + let friendThreads = getAllFriendThreads() + for thread in friendThreads { + sendOnlineBroadcastMessage(forThread: thread) + } + } + } + + // MARK: - Internal functions + + /// Get the P2P details for the given contact. + /// + /// - Parameter pubKey: The contact hex pubkey + /// - Returns: The P2P Details or nil if they don't exist + internal static func getDetails(forContact pubKey: String) -> P2PDetails? { + return contactP2PDetails[pubKey] + } + + /// Get the `LokiAddressMessage` for the given thread. + /// + /// - Parameter thread: The contact thread. + /// - Returns: The `LokiAddressMessage` for that thread. + @objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? { + return lokiAddressMessage(for: thread, isPing: false) + } /// Handle P2P logic when we receive a `LokiAddressMessage` /// @@ -30,8 +94,9 @@ extension LokiAPI { /// - address: The pther users p2p address /// - port: The other users p2p port /// - receivedThroughP2P: Wether we received the message through p2p - @objc public static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool, transaction: YapDatabaseReadTransaction) { + @objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) { // Stagger the ping timers so that contacts don't ping each other at the same time + let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval // Get out current contact details @@ -49,10 +114,10 @@ extension LokiAPI { /* We need to check if we should ping the user. We don't ping the user IF: - - We had old contact details - - We got a P2P message - - The old contact was set as `Online` - - The new p2p details match the old one + - We had old contact details + - We got a P2P message + - The old contact was set as `Online` + - The new p2p details match the old one */ if oldContactExists && receivedThroughP2P && wasOnline && p2pDetailsMatch { setOnline(true, forContact: pubKey) @@ -62,15 +127,20 @@ extension LokiAPI { /* Ping the contact. This happens in the following scenarios: - 1. We didn't have the contact, we need to ping them to let them know our details. - 2. wasP2PMessage = false, so we assume the contact doesn't have our details. - 3. We had the contact marked as offline, we need to make sure that we can reach their server. - 4. The other contact details have changed, we need to make sure that we can reach their new server. + 1. We didn't have the contact, we need to ping them to let them know our details. + 2. wasP2PMessage = false, so we assume the contact doesn't have our details. + 3. We had the contact marked as offline, we need to make sure that we can reach their server. + 4. The other contact details have changed, we need to make sure that we can reach their new server. */ - ping(contact: pubKey, withTransaction: transaction) + ping(contact: pubKey) } - @objc public static func setOnline(_ isOnline: Bool, forContact pubKey: String) { + /// Mark a contact as online or offline. + /// + /// - Parameters: + /// - isOnline: Whether to set the contact to online or offline. + /// - pubKey: The contact hexh pubKey + @objc internal static func setOnline(_ isOnline: Bool, forContact pubKey: String) { // Make sure we are on the main thread DispatchQueue.main.async { guard var details = contactP2PDetails[pubKey] else { return } @@ -86,67 +156,9 @@ extension LokiAPI { } } - @objc public static func ping(contact pubKey: String) { - AssertIsOnMainThread() - storage.dbReadConnection.read { transaction in - ping(contact: pubKey, withTransaction: transaction) - } - } - - @objc public static func ping(contact pubKey: String, withTransaction transaction: YapDatabaseReadTransaction) { - guard let thread = TSContactThread.fetch(uniqueId: pubKey, transaction: transaction) else { - Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)") - return - } - guard let message = lokiAddressMessage(for: thread, isPing: true) else { - Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)") - return - } - - messageSender.sendPromise(message: message).retainUntilComplete() - } - - /// Set our local P2P address - /// - /// - Parameter url: The url to our local server - @objc public static func setOurP2PAddress(url: URL) { - guard let scheme = url.scheme, let host = url.host, let port = url.port else { return } - let target = Target(address: "\(scheme)://\(host)", port: UInt32(port)) - ourP2PAddress = target - } - - /// Broadcash an online message to all our friends. - /// This shouldn't be called inside a transaction. - @objc public static func broadcastOnlineStatus() { - AssertIsOnMainThread() - - let friendThreads = getAllFriendThreads() - for thread in friendThreads { - sendOnlineBroadcastMessage(forThread: thread) - } - } - - /// Get the `LokiAddressMessage` for the given thread. - /// - /// - Parameter thread: The contact thread. - /// - Returns: The `LokiAddressMessage` for that thread. - @objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? { - return lokiAddressMessage(for: thread, isPing: false) - } + // MARK: - Private functions - private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? { - guard let ourAddress = ourP2PAddress else { - Logger.error("P2P Address not set") - return nil - } - - return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing) - } - - /// Send a `Loki Address` message to the given thread - /// - /// - Parameter thread: The contact thread to send the message to - @objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) { + private static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) { AssertIsOnMainThread() guard let message = onlineBroadcastMessage(forThread: thread) else { @@ -156,14 +168,7 @@ extension LokiAPI { messageSender.sendPromise(message: message).catch { error in Logger.warn("Failed to send online status to \(thread.contactIdentifier())") - }.retainUntilComplete() - } - - @objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread, transaction: YapDatabaseReadWriteTransaction) { - guard let ourAddress = ourP2PAddress else { - owsFailDebug("P2P Address not set") - return - } + }.retainUntilComplete() } private static func getAllFriendThreads() -> [TSContactThread] { @@ -179,4 +184,12 @@ extension LokiAPI { return friendThreadIds.compactMap { TSContactThread.fetch(uniqueId: $0) } } + private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? { + guard let ourAddress = ourP2PAddress else { + Logger.error("P2P Address not set") + return nil + } + + return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing) + } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 2c21590ce..4d6eb034f 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -437,7 +437,7 @@ NS_ASSUME_NONNULL_BEGIN if (contentProto.lokiAddressMessage) { NSString *address = contentProto.lokiAddressMessage.ptpAddress; uint32_t port = contentProto.lokiAddressMessage.ptpPort; - [LokiAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage transaction: transaction]; + [LokiP2PManager didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage]; } if (contentProto.syncMessage) { @@ -1459,6 +1459,16 @@ NS_ASSUME_NONNULL_BEGIN (unsigned long)timestamp); return nil; } + + // Loki + // If we received a message from a contact in the last 2 minues that was not p2p, then we need to ping them. + // We assume this occurred because they don't have our p2p details. + if (!envelope.isPtpMessage && envelope.source != nil) { + uint64_t timestamp = envelope.timestamp; + uint64_t now = NSDate.ows_millisecondTimeStamp; + uint64_t ageInSeconds = (now - timestamp) / 1000; + if (ageInSeconds <= 120) { [LokiP2PManager pingContact:envelope.source]; } + } [self finalizeIncomingMessage:incomingMessage thread:thread @@ -1509,7 +1519,7 @@ NS_ASSUME_NONNULL_BEGIN } // Send our p2p details to the other user - LKAddressMessage *_Nullable onlineMessage = [LokiAPI onlineBroadcastMessageForThread:thread]; + LKAddressMessage *_Nullable onlineMessage = [LokiP2PManager onlineBroadcastMessageForThread:thread]; if (onlineMessage != nil) { [self.messageSenderJobQueue addMessage:onlineMessage transaction:transaction]; } From 8a1ef268c8af3b3bffafb3e5907c40e5fcf27568 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 27 May 2019 09:52:06 +1000 Subject: [PATCH 14/22] Disable deregister after network error. --- SignalServiceKit/src/Network/API/TSNetworkManager.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m index eb69e8be0..1e9802a1c 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.m @@ -496,6 +496,10 @@ dispatch_queue_t NetworkManagerQueue() + (void)deregisterAfterAuthErrorIfNecessary:(NSURLSessionDataTask *)task request:(TSRequest *)request statusCode:(NSInteger)statusCode { + /* Loki Original Code + * - We don't really care about invalid auth + * ------------- + OWSLogVerbose(@"Invalid auth: %@", task.originalRequest.allHTTPHeaderFields); // We only want to de-register for: @@ -522,6 +526,7 @@ dispatch_queue_t NetworkManagerQueue() } else { OWSLogWarn(@"Ignoring %d for URL: %@", (int)statusCode, task.originalRequest.URL.absoluteString); } + */ } + (NSError *)errorWithHTTPCode:(NSInteger)code From 58ead70eb3f7714f861938341a7d790e82a01037 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 27 May 2019 09:54:20 +1000 Subject: [PATCH 15/22] Minor fix. --- SignalServiceKit/src/Loki/API/LokiAPI.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 8cd2e3cfe..52807603e 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -86,8 +86,9 @@ import PromiseKit // If it was a ping then don't send to the storage server if (message.isPing) { Logger.warn("[Loki] Failed to ping \(destination) - Marking contact as offline.") - error.isRetryable = false - throw error + let nserror = error as NSError + nserror.isRetryable = false + throw nserror } return sendThroughStorageServer() From 32b5b43c6e98cd7684e4f45733a76b16a9e83829 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 27 May 2019 10:18:07 +1000 Subject: [PATCH 16/22] Fix fetching incorrect thread. We also forgot to save incoming details. --- SignalServiceKit/src/Loki/API/LokiP2PManager.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift index 9d5ce3747..d0f2828bf 100644 --- a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift @@ -44,10 +44,16 @@ public static func ping(contact pubKey: String) { // Dispatch on the main queue so we escape any transaction blocks DispatchQueue.main.async { - guard let thread = TSContactThread.fetch(uniqueId: pubKey) else { + var contactThread: TSThread? = nil + storage.dbReadConnection.read { transaction in + contactThread = TSContactThread.getWithContactId(pubKey, transaction: transaction) + } + + guard let thread = contactThread else { Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)") return } + guard let message = lokiAddressMessage(for: thread, isPing: true) else { Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)") return @@ -96,7 +102,6 @@ /// - receivedThroughP2P: Wether we received the message through p2p @objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) { // Stagger the ping timers so that contacts don't ping each other at the same time - let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval // Get out current contact details @@ -105,6 +110,7 @@ // Set the new contact details // A contact is always assumed to be offline unless the specific conditions below are met let details = P2PDetails(address: address, port: port, isOnline: false, timerDuration: timerDuration, pingTimer: nil) + contactP2PDetails[pubKey] = details // Set up our checks let oldContactExists = oldContactDetails != nil From 58a1c82bfb3a358abf710312ca871079aef3e2a6 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 27 May 2019 11:26:28 +1000 Subject: [PATCH 17/22] Don't fail debug on unknown content type. The desktop messenger has some messages which will not set anything on the envelope. --- SignalServiceKit/src/Messages/OWSMessageHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.m b/SignalServiceKit/src/Messages/OWSMessageHandler.m index e1de268a3..738891460 100644 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.m +++ b/SignalServiceKit/src/Messages/OWSMessageHandler.m @@ -78,7 +78,7 @@ NSString *envelopeAddress(SSKProtoEnvelope *envelope) } else { // Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of // analytics traffic. - OWSFailDebug(@"Unknown content type."); +// OWSFailDebug(@"Unknown content type."); return @"UnknownContent"; } } From 5351961af7cb1472c01b903a8b58cb4dded60ad6 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 27 May 2019 12:26:37 +1000 Subject: [PATCH 18/22] Clean --- Signal/src/AppDelegate.m | 9 +- .../src/Loki/API/LokiAPI+Message.swift | 64 ++++++--------- .../src/Loki/API/LokiAPI+SwarmAPI.swift | 2 +- .../src/Loki/API/LokiAPI+Target.swift | 4 +- SignalServiceKit/src/Loki/API/LokiAPI.swift | 82 ++++++++----------- .../src/Loki/API/LokiP2PManager.swift | 18 ++-- .../src/Loki/API/TargetWrapper.swift | 4 +- .../src/Loki/Messages/LKAddressMessage.h | 10 +-- .../src/Loki/Messages/LKAddressMessage.m | 28 +++---- .../src/Loki/Messages/LKEphemeralMessage.h | 4 +- .../src/Loki/Messages/LKEphemeralMessage.m | 8 +- .../src/Loki/Utilities/Promise+Wrap.swift | 9 -- .../src/Network/API/TSNetworkManager.m | 10 ++- 13 files changed, 100 insertions(+), 152 deletions(-) delete mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 511082be3..2de5c7df3 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -320,12 +320,13 @@ static NSTimeInterval launchStartedAt; if (self.lokiP2PServer.isRunning) { break; } BOOL isStarted = [self.lokiP2PServer startOnPort:port.unsignedIntegerValue]; if (isStarted) { + NSURL *serverURL = self.lokiP2PServer.serverURL; [LokiP2PManager setOurP2PAddressWithUrl:self.lokiP2PServer.serverURL]; - NSString *serverURL = self.lokiP2PServer.serverURL.absoluteString; - if ([serverURL hasSuffix:@"/"]) { - serverURL = [serverURL substringToIndex:serverURL.length - 1]; + NSString *serverURLDescription = serverURL.absoluteString; + if ([serverURLDescription hasSuffix:@"/"]) { + serverURLDescription = [serverURLDescription substringToIndex:serverURLDescription.length - 1]; } - OWSLogInfo(@"[Loki] Started server at %@.", serverURL); + OWSLogInfo(@"[Loki] Started server at %@.", serverURLDescription); break; } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index 2a9daf067..f3e7b5539 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -9,15 +9,25 @@ public extension LokiAPI { let data: LosslessStringConvertible /// The time to live for the message in milliseconds. let ttl: UInt64 - /// Wether this message is a ping. - /// This should always be false unless it is from p2p pinging logic. + /// Whether this message is a ping. + /// + /// - Note: The concept of pinging only applies to P2P messaging. let isPing: Bool - /// When the proof of work was calculated, if applicable. + /// When the proof of work was calculated, if applicable (P2P messages don't require proof of work). /// /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - var timestamp: UInt64? = nil - /// The base 64 encoded proof of work, if applicable. - var nonce: String? = nil + private(set) var timestamp: UInt64? + /// The base 64 encoded proof of work, if applicable (P2P messages don't require proof of work). + private(set) var nonce: String? + + private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool = false, timestamp: UInt64? = nil, nonce: String? = nil) { + self.destination = destination + self.data = data + self.ttl = ttl + self.isPing = isPing + self.timestamp = timestamp + self.nonce = nonce + } /// Construct a `LokiMessage` from a `SignalMessage`. /// @@ -28,52 +38,28 @@ public extension LokiAPI { let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) let data = wrappedMessage.base64EncodedString() let destination = signalMessage["destination"] as! String - var ttl = LokiAPI.defaultMessageTTL - if let messageTTL = signalMessage["ttl"] as? UInt, messageTTL > 0 { ttl = UInt64(messageTTL) } - + if let messageTTL = signalMessage["ttl"] as! UInt?, messageTTL > 0 { ttl = UInt64(messageTTL) } let isPing = signalMessage["isPing"] as! Bool - return Message(destination: destination, data: data, ttl: ttl, isPing: isPing) } catch let error { - Logger.debug("[Loki] Failed to convert Signal message to Loki message: \(signalMessage)") + Logger.debug("[Loki] Failed to convert Signal message to Loki message: \(signalMessage).") return nil } } - /// Create a basic loki message. + /// Calculate the proof of work for this message. /// - /// - Parameters: - /// - destination: The destination - /// - data: The data - /// - ttl: The time to live - public init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool = false) { - self.destination = destination - self.data = data - self.ttl = ttl - self.isPing = isPing - } - - /// Private init for setting proof of work. Use `calculatePoW` to get a message with these fields - private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool, timestamp: UInt64?, nonce: String?) { - self.destination = destination - self.data = data - self.ttl = ttl - self.isPing = isPing - self.timestamp = timestamp - self.nonce = nonce - } - - /// Calculate the proof of work for this message - /// - /// - Returns: This will return a promise with a new message which contains the proof of work + /// - Returns: The promise of a new message with its `timestamp` and `nonce` set. public func calculatePoW() -> Promise { - // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object return Promise { seal in DispatchQueue.global(qos: .default).async { let now = NSDate.ows_millisecondTimeStamp() - if let nonce = ProofOfWork.calculate(data: self.data as! String, pubKey: self.destination, timestamp: now, ttl: self.ttl) { - let result = Message(destination: self.destination, data: self.data, ttl: self.ttl, isPing: self.isPing, timestamp: now, nonce: nonce) + let dataAsString = self.data as! String // Safe because of the way from(signalMessage:timestamp:) is implemented + if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.destination, timestamp: now, ttl: self.ttl) { + var result = self + result.timestamp = now + result.nonce = nonce seal.fulfill(result) } else { seal.reject(Error.proofOfWorkCalculationFailed) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 673f7f638..516d6f925 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -5,7 +5,7 @@ public extension LokiAPI { // MARK: Settings private static let minimumSnodeCount = 2 // TODO: For debugging purposes private static let targetSnodeCount = 3 // TODO: For debugging purposes - private static let defaultSnodePort: UInt32 = 8080 + private static let defaultSnodePort: UInt16 = 8080 // MARK: Caching private static let swarmCacheKey = "swarmCacheKey" diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift index 06da5fc9a..cd15175f6 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Target.swift @@ -3,9 +3,9 @@ internal extension LokiAPI { internal struct Target : Hashable { internal let address: String - internal let port: UInt32 + internal let port: UInt16 - internal init(address: String, port: UInt32) { + internal init(address: String, port: UInt16) { self.address = address self.port = port } diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 650e509cf..bfd8f7569 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -5,10 +5,8 @@ import PromiseKit // MARK: Settings private static let version = "v1" - public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 private static let maxRetryCount: UInt = 3 - - private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + public static let defaultMessageTTL: UInt64 = 1 * 24 * 60 * 60 * 1000 // MARK: Types public typealias RawResponse = Any @@ -16,14 +14,12 @@ import PromiseKit public enum Error : LocalizedError { /// Only applicable to snode targets as proof of work isn't required for P2P messaging. case proofOfWorkCalculationFailed - - // Failed to send the message' - case internalError + case messageConversionFailed public var errorDescription: String? { switch self { case .proofOfWorkCalculationFailed: return NSLocalizedString("Failed to calculate proof of work.", comment: "") - case .internalError: return "Failed while trying to send message" + case .messageConversionFailed: return "Failed to convert Signal message to Loki message." } } } @@ -41,10 +37,11 @@ import PromiseKit // MARK: Public API public static func getMessages() -> Promise>> { - return getTargetSnodes(for: ourHexEncodedPubKey).mapValues { targetSnode in + let hexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + return getTargetSnodes(for: hexEncodedPublicKey).mapValues { targetSnode in let lastHash = getLastMessageHashValue(for: targetSnode) ?? "" - let parameters: [String:Any] = [ "pubKey" : ourHexEncodedPubKey, "lastHash" : lastHash ] - return invoke(.getMessages, on: targetSnode, associatedWith: ourHexEncodedPubKey, parameters: parameters).map { rawResponse in + let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey, "lastHash" : lastHash ] + return invoke(.getMessages, on: targetSnode, associatedWith: hexEncodedPublicKey, parameters: parameters).map { rawResponse in guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } updateLastMessageHashValueIfPossible(for: targetSnode, from: rawMessages) let newRawMessages = removeDuplicates(from: rawMessages) @@ -53,56 +50,43 @@ import PromiseKit }.retryingIfNeeded(maxRetryCount: maxRetryCount).map { Set($0) } } - // MARK: Public API (Obj-C) - @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: Sending - public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, timestamp: UInt64) -> Promise>> { - guard let message = Message.from(signalMessage: signalMessage, timestamp: timestamp) else { - return Promise(error: Error.internalError) + public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> Promise>> { + guard let lokiMessage = Message.from(signalMessage: signalMessage, timestamp: timestamp) else { return Promise(error: Error.messageConversionFailed) } + let destination = lokiMessage.destination + func sendLokiMessage(_ lokiMessage: Message, to targets: [Target]) -> Promise>> { + let parameters = lokiMessage.toJSON() + return Promise.value(targets).mapValues { invoke(.sendMessage, on: $0, associatedWith: destination, parameters: parameters) }.map { Set($0) } } - - // Send message through the storage server - // We put this here because `recover` expects `Promise>>` - let sendThroughStorageServer: () -> Promise>> = { () in - return message.calculatePoW().then { powMessage -> Promise>> in - let snodes = getTargetSnodes(for: powMessage.destination) - return sendMessage(powMessage, targets: snodes) + func sendLokiMessageUsingSwarmAPI() -> Promise>> { + return lokiMessage.calculatePoW().then { updatedLokiMessage -> Promise>> in + return getTargetSnodes(for: destination).then { sendLokiMessage(updatedLokiMessage, to: $0) } } } - - // If we have the p2p details and we have marked the user as online OR we are pinging the user, then use peer to peer - // If that failes then fallback to storage server - if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), message.isPing || p2pDetails.isOnline { - let targets = Promise.wrap([p2pDetails.target]) - return sendMessage(message, targets: targets).then { result -> Promise>> in + if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), (lokiMessage.isPing || p2pDetails.isOnline) { + return sendLokiMessage(lokiMessage, to: [ p2pDetails.target ]).get { _ in LokiP2PManager.setOnline(true, forContact: destination) - return Promise.wrap(result) }.recover { error -> Promise>> in - // The user is not online LokiP2PManager.setOnline(false, forContact: destination) - - // If it was a ping then don't send to the storage server - if (message.isPing) { - Logger.warn("[Loki] Failed to ping \(destination) - Marking contact as offline.") - let nserror = error as NSError - nserror.isRetryable = false - throw nserror + if lokiMessage.isPing { + Logger.warn("[Loki] Failed to ping \(destination); marking contact as offline.") + if let nsError = error as? NSError { + nsError.isRetryable = false + throw nsError + } else { + throw error + } } - - return sendThroughStorageServer() + return sendLokiMessageUsingSwarmAPI() } + } else { + return sendLokiMessageUsingSwarmAPI() } - - return sendThroughStorageServer() } - internal static func sendMessage(_ lokiMessage: Message, targets: Promise<[Target]>) -> Promise>> { - let parameters = lokiMessage.toJSON() - return targets.mapValues { invoke(.sendMessage, on: $0, associatedWith: lokiMessage.destination, parameters: parameters) }.map { Set($0) } + // MARK: Public API (Obj-C) + @objc public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> AnyPromise { + let promise = sendSignalMessage(signalMessage, to: destination, with: timestamp).mapValues { AnyPromise.from($0) }.map { Set($0) } + return AnyPromise.from(promise) } // MARK: Parsing diff --git a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift index d0f2828bf..a244f025d 100644 --- a/SignalServiceKit/src/Loki/API/LokiP2PManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiP2PManager.swift @@ -1,7 +1,7 @@ @objc public class LokiP2PManager : NSObject { private static let storage = OWSPrimaryStorage.shared() - private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender + private static let messageSender = SSKEnvironment.shared.messageSender private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey /// The amount of time before pinging when a user is set to offline @@ -10,7 +10,7 @@ /// A p2p state struct internal struct P2PDetails { var address: String - var port: UInt32 + var port: UInt16 var isOnline: Bool var timerDuration: Double var pingTimer: Timer? = nil @@ -33,7 +33,7 @@ /// - Parameter url: The url to our local server @objc public static func setOurP2PAddress(url: URL) { guard let scheme = url.scheme, let host = url.host, let port = url.port else { return } - let target = LokiAPI.Target(address: "\(scheme)://\(host)", port: UInt32(port)) + let target = LokiAPI.Target(address: "\(scheme)://\(host)", port: UInt16(port)) ourP2PAddress = target } @@ -50,12 +50,12 @@ } guard let thread = contactThread else { - Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)") + Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey).") return } guard let message = lokiAddressMessage(for: thread, isPing: true) else { - Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)") + Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey).") return } @@ -63,7 +63,7 @@ } } - /// Broadcash an online message to all our friends. + /// Broadcast an online message to all our friends. /// This shouldn't be called inside a transaction. @objc public static func broadcastOnlineStatus() { // Escape any transaction blocks @@ -100,7 +100,7 @@ /// - address: The pther users p2p address /// - port: The other users p2p port /// - receivedThroughP2P: Wether we received the message through p2p - @objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) { + @objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt16, receivedThroughP2P: Bool) { // Stagger the ping timers so that contacts don't ping each other at the same time let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval @@ -127,7 +127,7 @@ */ if oldContactExists && receivedThroughP2P && wasOnline && p2pDetailsMatch { setOnline(true, forContact: pubKey) - return; + return } /* @@ -174,7 +174,7 @@ messageSender.sendPromise(message: message).catch { error in Logger.warn("Failed to send online status to \(thread.contactIdentifier())") - }.retainUntilComplete() + }.retainUntilComplete() } private static func getAllFriendThreads() -> [TSContactThread] { diff --git a/SignalServiceKit/src/Loki/API/TargetWrapper.swift b/SignalServiceKit/src/Loki/API/TargetWrapper.swift index e92bf5703..f8f14fba1 100644 --- a/SignalServiceKit/src/Loki/API/TargetWrapper.swift +++ b/SignalServiceKit/src/Loki/API/TargetWrapper.swift @@ -1,7 +1,7 @@ @objc internal final class TargetWrapper : NSObject, NSCoding { internal let address: String - internal let port: UInt32 + internal let port: UInt16 internal init(from target: LokiAPI.Target) { address = target.address @@ -11,7 +11,7 @@ internal init?(coder: NSCoder) { address = coder.decodeObject(forKey: "address") as! String - port = coder.decodeObject(forKey: "port") as! UInt32 + port = coder.decodeObject(forKey: "port") as! UInt16 super.init() } diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h index ab0ffb604..99d334d13 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.h @@ -1,4 +1,3 @@ - #import "LKEphemeralMessage.h" NS_ASSUME_NONNULL_BEGIN @@ -6,15 +5,12 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(LokiAddressMessage) @interface LKAddressMessage : LKEphemeralMessage -- (instancetype)initInThread:(nullable TSThread *)thread - address:(NSString *)address - port:(uint)port - isPing:(BOOL)isPing; - @property (nonatomic, readonly) NSString *address; -@property (nonatomic, readonly) uint port; +@property (nonatomic, readonly) uint16_t port; @property (nonatomic, readonly) BOOL isPing; +- (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address port:(uint16_t)port isPing:(BOOL)isPing; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m index e67dae035..e82abd15d 100644 --- a/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKAddressMessage.m @@ -6,42 +6,36 @@ @interface LKAddressMessage () @property (nonatomic) NSString *address; -@property (nonatomic) uint port; +@property (nonatomic) uint16_t port; @property (nonatomic) BOOL isPing; @end @implementation LKAddressMessage -- (instancetype)initInThread:(nullable TSThread *)thread - address:(NSString *)address - port:(uint)port - isPing:(bool)isPing +- (instancetype)initInThread:(nullable TSThread *)thread address:(NSString *)address port:(uint16_t)port isPing:(bool)isPing { - self = [super initInThread:thread]; - if (!self) { - return self; + self = [LKEphemeralMessage createEmptyOutgoingMessageInThread:thread]; + if (self) { + _address = address; + _port = port; + _isPing = isPing; } - - _address = address; - _port = port; - _isPing = isPing; - return self; } - (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient { SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; - // Se - SSKProtoLokiAddressMessageBuilder *addressBuilder = SSKProtoLokiAddressMessage.builder; + SSKProtoLokiAddressMessageBuilder *addressBuilder = [SSKProtoLokiAddressMessage builder]; [addressBuilder setPtpAddress:self.address]; - [addressBuilder setPtpPort:self.port]; + uint32_t portAsUInt32 = self.port; + [addressBuilder setPtpPort:portAsUInt32]; NSError *error; SSKProtoLokiAddressMessage *addressMessage = [addressBuilder buildAndReturnError:&error]; if (error || !addressMessage) { - OWSFailDebug(@"Failed to build lokiAddressMessage for %@: %@", recipient.recipientId, error); + OWSFailDebug(@"Failed to build LokiAddressMessage for %@: %@.", recipient.recipientId, error); } else { [contentBuilder setLokiAddressMessage:addressMessage]; } diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h index 92e67db2d..a60c7b1b1 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.h @@ -5,11 +5,9 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(EphemeralMessage) @interface LKEphemeralMessage : TSOutgoingMessage -/// Used to establish sessions. +/// Used for e.g. session initialization. + (LKEphemeralMessage *)createEmptyOutgoingMessageInThread:(TSThread *)thread; -- (instancetype)initInThread:(nullable TSThread *)thread; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m index 90be9f31c..f74ba80c8 100644 --- a/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m +++ b/SignalServiceKit/src/Loki/Messages/LKEphemeralMessage.m @@ -4,12 +4,8 @@ @implementation LKEphemeralMessage + (LKEphemeralMessage *)createEmptyOutgoingMessageInThread:(TSThread *)thread { - return [[LKEphemeralMessage alloc] initInThread:thread]; -} - -- (instancetype)initInThread:(nullable TSThread *)thread { - return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] - expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + return [[LKEphemeralMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] + expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; } - (BOOL)shouldSyncTranscript { return NO; } diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift deleted file mode 100644 index 9affd8f8b..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Promise+Wrap.swift +++ /dev/null @@ -1,9 +0,0 @@ -import PromiseKit - -public extension Promise { - static func wrap(_ value: T) -> Promise { - return Promise { resolver in - resolver.fulfill(value) - } - } -} diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m index 1e9802a1c..57f0b08a4 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.m @@ -496,9 +496,9 @@ dispatch_queue_t NetworkManagerQueue() + (void)deregisterAfterAuthErrorIfNecessary:(NSURLSessionDataTask *)task request:(TSRequest *)request statusCode:(NSInteger)statusCode { - /* Loki Original Code - * - We don't really care about invalid auth - * ------------- + /* Loki: Original code + * We don't really care about invalid auth + * ======== OWSLogVerbose(@"Invalid auth: %@", task.originalRequest.allHTTPHeaderFields); @@ -526,7 +526,9 @@ dispatch_queue_t NetworkManagerQueue() } else { OWSLogWarn(@"Ignoring %d for URL: %@", (int)statusCode, task.originalRequest.URL.absoluteString); } - */ + + * ======== + */ } + (NSError *)errorWithHTTPCode:(NSInteger)code From 055c238baf4e6633a61a1884652ae602ee20650e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 27 May 2019 12:27:50 +1000 Subject: [PATCH 19/22] Update Pods --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index c8f9b28f5..7005b4be3 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit c8f9b28f577a03f9f03a0b2e38e125811785ee29 +Subproject commit 7005b4be3cd1596fcf644356aea526663cd8ac67 From 728a14898081d580db78572e17f42ade06d23e34 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 27 May 2019 12:50:30 +1000 Subject: [PATCH 20/22] Improve performance --- .../src/Loki/API/LokiAPI+Message.swift | 12 +++++------ SignalServiceKit/src/Loki/API/LokiAPI.swift | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift index f3e7b5539..9a5fbf748 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+Message.swift @@ -16,23 +16,21 @@ public extension LokiAPI { /// When the proof of work was calculated, if applicable (P2P messages don't require proof of work). /// /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - private(set) var timestamp: UInt64? + private(set) var timestamp: UInt64? = nil /// The base 64 encoded proof of work, if applicable (P2P messages don't require proof of work). - private(set) var nonce: String? + private(set) var nonce: String? = nil - private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool = false, timestamp: UInt64? = nil, nonce: String? = nil) { + private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool) { self.destination = destination self.data = data self.ttl = ttl self.isPing = isPing - self.timestamp = timestamp - self.nonce = nonce } /// Construct a `LokiMessage` from a `SignalMessage`. /// /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). - public static func from(signalMessage: SignalMessage, timestamp: UInt64) -> Message? { + public static func from(signalMessage: SignalMessage, with timestamp: UInt64) -> Message? { // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object do { let wrappedMessage = try LokiMessageWrapper.wrap(message: signalMessage, timestamp: timestamp) @@ -55,7 +53,7 @@ public extension LokiAPI { return Promise { seal in DispatchQueue.global(qos: .default).async { let now = NSDate.ows_millisecondTimeStamp() - let dataAsString = self.data as! String // Safe because of the way from(signalMessage:timestamp:) is implemented + let dataAsString = self.data as! String // Safe because of how from(signalMessage:with:) is implemented if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.destination, timestamp: now, ttl: self.ttl) { var result = self result.timestamp = now diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index bfd8f7569..fbc042ec3 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -47,23 +47,25 @@ import PromiseKit let newRawMessages = removeDuplicates(from: rawMessages) return parseProtoEnvelopes(from: newRawMessages) } - }.retryingIfNeeded(maxRetryCount: maxRetryCount).map { Set($0) } + }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount) } public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> Promise>> { - guard let lokiMessage = Message.from(signalMessage: signalMessage, timestamp: timestamp) else { return Promise(error: Error.messageConversionFailed) } + guard let lokiMessage = Message.from(signalMessage: signalMessage, with: timestamp) else { return Promise(error: Error.messageConversionFailed) } let destination = lokiMessage.destination - func sendLokiMessage(_ lokiMessage: Message, to targets: [Target]) -> Promise>> { + func sendLokiMessage(_ lokiMessage: Message, to target: Target) -> Promise { let parameters = lokiMessage.toJSON() - return Promise.value(targets).mapValues { invoke(.sendMessage, on: $0, associatedWith: destination, parameters: parameters) }.map { Set($0) } + return invoke(.sendMessage, on: target, associatedWith: destination, parameters: parameters) } func sendLokiMessageUsingSwarmAPI() -> Promise>> { - return lokiMessage.calculatePoW().then { updatedLokiMessage -> Promise>> in - return getTargetSnodes(for: destination).then { sendLokiMessage(updatedLokiMessage, to: $0) } + let powPromise = lokiMessage.calculatePoW() + let swarmPromise = getTargetSnodes(for: destination) + return when(fulfilled: powPromise, swarmPromise).map { lokiMessageWithPoW, swarm in + return Set(swarm.map { sendLokiMessage(lokiMessageWithPoW, to: $0) }) } } if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), (lokiMessage.isPing || p2pDetails.isOnline) { - return sendLokiMessage(lokiMessage, to: [ p2pDetails.target ]).get { _ in + return Promise.value([ p2pDetails.target ]).mapValues { sendLokiMessage(lokiMessage, to: $0) }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount).get { _ in LokiP2PManager.setOnline(true, forContact: destination) }.recover { error -> Promise>> in LokiP2PManager.setOnline(false, forContact: destination) @@ -76,10 +78,10 @@ import PromiseKit throw error } } - return sendLokiMessageUsingSwarmAPI() + return sendLokiMessageUsingSwarmAPI().retryingIfNeeded(maxRetryCount: maxRetryCount) } } else { - return sendLokiMessageUsingSwarmAPI() + return sendLokiMessageUsingSwarmAPI().retryingIfNeeded(maxRetryCount: maxRetryCount) } } From 97d1c16164791f1bdac72560d1625bd4066ef18e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 27 May 2019 12:54:59 +1000 Subject: [PATCH 21/22] Clean up sendSignalMessage(_:to:with:) signature --- SignalServiceKit/src/Loki/API/LokiAPI.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index e100e70d8..c08212d23 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -24,7 +24,8 @@ import PromiseKit } } - public typealias MessagePromise = Promise<[SSKProtoEnvelope]> // To keep the return type of getMessages() readable + public typealias MessagePromise = Promise<[SSKProtoEnvelope]> + public typealias RawResponsePromise = Promise // MARK: Lifecycle override private init() { } @@ -52,14 +53,14 @@ import PromiseKit }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount) } - public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> Promise>> { + public static func sendSignalMessage(_ signalMessage: SignalMessage, to destination: String, with timestamp: UInt64) -> Promise> { guard let lokiMessage = Message.from(signalMessage: signalMessage, with: timestamp) else { return Promise(error: Error.messageConversionFailed) } let destination = lokiMessage.destination - func sendLokiMessage(_ lokiMessage: Message, to target: Target) -> Promise { + func sendLokiMessage(_ lokiMessage: Message, to target: Target) -> RawResponsePromise { let parameters = lokiMessage.toJSON() return invoke(.sendMessage, on: target, associatedWith: destination, parameters: parameters) } - func sendLokiMessageUsingSwarmAPI() -> Promise>> { + func sendLokiMessageUsingSwarmAPI() -> Promise> { let powPromise = lokiMessage.calculatePoW() let swarmPromise = getTargetSnodes(for: destination) return when(fulfilled: powPromise, swarmPromise).map { lokiMessageWithPoW, swarm in @@ -69,7 +70,7 @@ import PromiseKit if let p2pDetails = LokiP2PManager.getDetails(forContact: destination), (lokiMessage.isPing || p2pDetails.isOnline) { return Promise.value([ p2pDetails.target ]).mapValues { sendLokiMessage(lokiMessage, to: $0) }.map { Set($0) }.retryingIfNeeded(maxRetryCount: maxRetryCount).get { _ in LokiP2PManager.setOnline(true, forContact: destination) - }.recover { error -> Promise>> in + }.recover { error -> Promise> in LokiP2PManager.setOnline(false, forContact: destination) if lokiMessage.isPing { Logger.warn("[Loki] Failed to ping \(destination); marking contact as offline.") From 0337695c155619c21148dbca751d1a9255f4b341 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 27 May 2019 12:57:21 +1000 Subject: [PATCH 22/22] Add comment --- SignalServiceKit/src/Messages/OWSMessageHandler.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.m b/SignalServiceKit/src/Messages/OWSMessageHandler.m index 738891460..3caee3738 100644 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.m +++ b/SignalServiceKit/src/Messages/OWSMessageHandler.m @@ -78,7 +78,10 @@ NSString *envelopeAddress(SSKProtoEnvelope *envelope) } else { // Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of // analytics traffic. + // Loki: Original code + // ======== // OWSFailDebug(@"Unknown content type."); + // ======== return @"UnknownContent"; } }