Send to p2p server first before falling back to storage server.

This commit is contained in:
Mikunj 2019-05-23 13:36:50 +10:00
parent 2694699e4e
commit bf1c2f4327
6 changed files with 107 additions and 38 deletions

2
Pods

@ -1 +1 @@
Subproject commit e5dc17be2dc588fde41caa91484aa9e6b9c56ebd
Subproject commit 04f0c4baf6a53b9e2e2f161d5da3574b2dbd333d

View File

@ -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<Message> {
/// - Returns: This will return a promise with a new message which contains the proof of work
public func calculatePoW() -> Promise<Message> {
// To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object
return Promise<Message> { 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)
}
}
}

View File

@ -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

View File

@ -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<Set<Promise<RawResponse>>> {
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<Set<Promise<RawResponse>>>`
let sendThroughStorageServer: () -> Promise<Set<Promise<RawResponse>>> = { () in
return message.calculatePoW().then { powMessage -> Promise<Set<Promise<RawResponse>>> 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<Set<Promise<RawResponse>>> = 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<Set<Promise<RawResponse>>> {
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<Set<Promise<RawResponse>>> {
return sendMessage(lokiMessage, targets: Promise.wrap(targets))
}
internal static func sendMessage(_ lokiMessage: Message, targets: Promise<[Target]>) -> Promise<Set<Promise<RawResponse>>> {
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<Set<Promise<RawResponse>>> {

View File

@ -0,0 +1,9 @@
import PromiseKit
public extension Promise {
static func wrap(_ value: T) -> Promise<T> {
return Promise<T> { resolver in
resolver.fulfill(value)
}
}
}

View File

@ -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