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