mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
4.7 KiB
Swift
108 lines
4.7 KiB
Swift
5 years ago
|
import PromiseKit
|
||
|
import SessionSnodeKit
|
||
|
import SessionUtilities
|
||
5 years ago
|
|
||
5 years ago
|
// TODO: Notify PN server
|
||
|
|
||
5 years ago
|
public enum MessageSender {
|
||
5 years ago
|
|
||
|
public enum Error : LocalizedError {
|
||
5 years ago
|
case invalidMessage
|
||
5 years ago
|
case protoConversionFailed
|
||
5 years ago
|
case proofOfWorkCalculationFailed
|
||
5 years ago
|
case noUserPublicKey
|
||
5 years ago
|
|
||
|
public var errorDescription: String? {
|
||
|
switch self {
|
||
5 years ago
|
case .invalidMessage: return "Invalid message."
|
||
5 years ago
|
case .protoConversionFailed: return "Couldn't convert message to proto."
|
||
5 years ago
|
case .proofOfWorkCalculationFailed: return "Proof of work calculation failed."
|
||
5 years ago
|
case .noUserPublicKey: return "Couldn't find user key pair."
|
||
5 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
5 years ago
|
public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise<Void> {
|
||
|
// Validate the message
|
||
5 years ago
|
guard message.isValid else { return Promise(error: Error.invalidMessage) }
|
||
5 years ago
|
// Convert it to protobuf
|
||
5 years ago
|
guard let proto = message.toProto() else { return Promise(error: Error.protoConversionFailed) }
|
||
5 years ago
|
// Serialize the protobuf
|
||
5 years ago
|
let plaintext: Data
|
||
5 years ago
|
do {
|
||
5 years ago
|
plaintext = try proto.serializedData()
|
||
5 years ago
|
} catch {
|
||
|
SNLog("Couldn't serialize proto due to error: \(error).")
|
||
5 years ago
|
return Promise(error: error)
|
||
|
}
|
||
5 years ago
|
// Encrypt the serialized protobuf
|
||
|
if case .contact(_) = destination {
|
||
|
DispatchQueue.main.async {
|
||
|
NotificationCenter.default.post(name: .encryptingMessage, object: NSNumber(value: message.sentTimestamp!))
|
||
|
}
|
||
|
}
|
||
5 years ago
|
let ciphertext: Data
|
||
|
do {
|
||
|
switch destination {
|
||
|
case .contact(let publicKey): ciphertext = try encryptWithSignalProtocol(plaintext, for: publicKey, using: transaction)
|
||
|
case .closedGroup(let groupPublicKey): ciphertext = try encryptWithSharedSenderKeys(plaintext, for: groupPublicKey, using: transaction)
|
||
|
case .openGroup(_, _): fatalError("Not implemented.")
|
||
|
}
|
||
|
} catch {
|
||
|
SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).")
|
||
|
return Promise(error: error)
|
||
5 years ago
|
}
|
||
5 years ago
|
// Calculate proof of work
|
||
|
if case .contact(_) = destination {
|
||
|
DispatchQueue.main.async {
|
||
|
NotificationCenter.default.post(name: .calculatingMessagePoW, object: NSNumber(value: message.sentTimestamp!))
|
||
|
}
|
||
|
}
|
||
5 years ago
|
let recipient = message.recipient!
|
||
|
let base64EncodedData = ciphertext.base64EncodedString()
|
||
5 years ago
|
guard let (timestamp, nonce) = ProofOfWork.calculate(ttl: ttl, publicKey: recipient, data: base64EncodedData) else {
|
||
|
SNLog("Proof of work calculation failed.")
|
||
|
return Promise(error: Error.proofOfWorkCalculationFailed)
|
||
|
}
|
||
5 years ago
|
// Send the result
|
||
|
if case .contact(_) = destination {
|
||
|
DispatchQueue.main.async {
|
||
|
NotificationCenter.default.post(name: .messageSending, object: NSNumber(value: message.sentTimestamp!))
|
||
|
}
|
||
|
}
|
||
5 years ago
|
let snodeMessage = SnodeMessage(recipient: recipient, data: base64EncodedData, ttl: ttl, timestamp: timestamp, nonce: nonce)
|
||
5 years ago
|
let (promise, seal) = Promise<Void>.pending()
|
||
|
SnodeAPI.sendMessage(snodeMessage).done(on: Threading.workQueue) { promises in
|
||
|
var isSuccess = false
|
||
|
let promiseCount = promises.count
|
||
|
var errorCount = 0
|
||
|
promises.forEach {
|
||
|
let _ = $0.done(on: Threading.workQueue) { _ in
|
||
|
guard !isSuccess else { return } // Succeed as soon as the first promise succeeds
|
||
|
isSuccess = true
|
||
|
seal.fulfill(())
|
||
|
}
|
||
|
$0.catch(on: Threading.workQueue) { error in
|
||
|
errorCount += 1
|
||
|
guard errorCount == promiseCount else { return } // Only error out if all promises failed
|
||
|
seal.reject(error)
|
||
|
}
|
||
|
}
|
||
|
}.catch(on: Threading.workQueue) { error in
|
||
|
SNLog("Couldn't send message due to error: \(error).")
|
||
|
seal.reject(error)
|
||
|
}
|
||
5 years ago
|
let _ = promise.done(on: DispatchQueue.main) {
|
||
|
if case .contact(_) = destination {
|
||
|
NotificationCenter.default.post(name: .messageSent, object: NSNumber(value: message.sentTimestamp!))
|
||
|
}
|
||
|
}
|
||
|
let _ = promise.catch(on: DispatchQueue.main) { _ in
|
||
|
if case .contact(_) = destination {
|
||
|
NotificationCenter.default.post(name: .messageSendingFailed, object: NSNumber(value: message.sentTimestamp!))
|
||
|
}
|
||
|
}
|
||
5 years ago
|
return promise
|
||
5 years ago
|
}
|
||
5 years ago
|
}
|