session-ios/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift

177 lines
9.0 KiB
Swift
Raw Normal View History

2020-11-09 00:58:47 +01:00
import SessionUtilitiesKit
2020-11-05 23:17:05 +01:00
public enum MessageReceiver {
2021-02-09 23:51:28 +01:00
private static var lastEncryptionKeyPairRequest: [String:Date] = [:]
public enum Error : LocalizedError {
2020-11-27 05:13:42 +01:00
case duplicateMessage
2020-11-06 06:31:56 +01:00
case invalidMessage
case unknownMessage
case unknownEnvelopeType
2020-12-10 06:12:22 +01:00
case noUserX25519KeyPair
case noUserED25519KeyPair
case invalidSignature
case noData
2020-11-17 06:23:13 +01:00
case senderBlocked
2020-11-18 05:53:45 +01:00
case noThread
2020-11-23 05:58:48 +01:00
case selfSend
2020-12-10 06:12:22 +01:00
case decryptionFailed
// Shared sender keys
case invalidGroupPublicKey
case noGroupKeyPair
2020-11-06 06:31:56 +01:00
public var isRetryable: Bool {
2020-11-18 05:36:51 +01:00
switch self {
case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend: return false
2020-11-18 05:36:51 +01:00
default: return true
}
}
public var errorDescription: String? {
2020-11-06 06:31:56 +01:00
switch self {
2020-11-27 05:13:42 +01:00
case .duplicateMessage: return "Duplicate message."
2020-11-06 06:31:56 +01:00
case .invalidMessage: return "Invalid message."
case .unknownMessage: return "Unknown message type."
case .unknownEnvelopeType: return "Unknown envelope type."
2020-12-10 06:12:22 +01:00
case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair."
case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair."
case .invalidSignature: return "Invalid message signature."
case .noData: return "Received an empty envelope."
2020-11-17 06:23:13 +01:00
case .senderBlocked: return "Received a message from a blocked user."
2020-11-18 05:53:45 +01:00
case .noThread: return "Couldn't find thread for message."
2020-12-10 06:12:22 +01:00
case .selfSend: return "Message addressed at self."
case .decryptionFailed: return "Decryption failed."
// Shared sender keys
case .invalidGroupPublicKey: return "Invalid group public key."
case .noGroupKeyPair: return "Missing group key pair."
2020-11-06 06:31:56 +01:00
}
}
}
2021-02-09 23:51:28 +01:00
public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) {
2020-12-02 06:25:16 +01:00
let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
2020-11-30 04:20:36 +01:00
let isOpenGroupMessage = (openGroupMessageServerID != nil)
// Parse the envelope
2020-11-17 06:23:13 +01:00
let envelope = try SNProtoEnvelope.parseData(data)
2020-12-02 06:25:16 +01:00
let storage = SNMessagingKitConfiguration.shared.storage
// Decrypt the contents
guard let ciphertext = envelope.content else { throw Error.noData }
var plaintext: Data!
var sender: String!
2020-11-18 05:53:45 +01:00
var groupPublicKey: String? = nil
2020-11-30 01:00:28 +01:00
if isOpenGroupMessage {
(plaintext, sender) = (envelope.content!, envelope.source!)
} else {
switch envelope.type {
2021-03-02 03:12:24 +01:00
case .sessionMessage:
guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { throw Error.noUserX25519KeyPair }
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
2021-03-02 03:12:24 +01:00
case .closedGroupMessage:
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey }
var encryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: hexEncodedGroupPublicKey)
guard !encryptionKeyPairs.isEmpty else { throw Error.noGroupKeyPair }
// Loop through all known group key pairs in reverse order (i.e. try the latest key pair first (which'll more than
// likely be the one we want) but try older ones in case that didn't work)
var encryptionKeyPair = encryptionKeyPairs.removeLast()
func decrypt() throws {
do {
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: encryptionKeyPair)
} catch {
if !encryptionKeyPairs.isEmpty {
encryptionKeyPair = encryptionKeyPairs.removeLast()
try decrypt()
} else {
throw error
}
}
}
2020-11-30 01:00:28 +01:00
groupPublicKey = envelope.source
try decrypt()
2021-05-03 07:13:18 +02:00
/*
do {
try decrypt()
} catch {
do {
let now = Date()
// Don't spam encryption key pair requests
let shouldRequestEncryptionKeyPair = given(lastEncryptionKeyPairRequest[groupPublicKey!]) { now.timeIntervalSince($0) > 30 } ?? true
if shouldRequestEncryptionKeyPair {
try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction)
lastEncryptionKeyPairRequest[groupPublicKey!] = now
}
}
throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one)
}
*/
2020-11-30 01:00:28 +01:00
default: throw Error.unknownEnvelopeType
}
}
2020-11-17 06:23:13 +01:00
// Don't process the envelope any further if the sender is blocked
2020-11-25 06:15:16 +01:00
guard !isBlocked(sender) else { throw Error.senderBlocked }
// Parse the proto
2020-11-06 04:05:45 +01:00
let proto: SNProtoContent
do {
proto = try SNProtoContent.parseData((plaintext as NSData).removePadding())
2020-11-06 04:05:45 +01:00
} catch {
SNLog("Couldn't parse proto due to error: \(error).")
throw error
2020-11-06 04:05:45 +01:00
}
// Parse the message
2020-11-06 06:31:56 +01:00
let message: Message? = {
if let readReceipt = ReadReceipt.fromProto(proto) { return readReceipt }
if let typingIndicator = TypingIndicator.fromProto(proto) { return typingIndicator }
if let closedGroupControlMessage = ClosedGroupControlMessage.fromProto(proto) { return closedGroupControlMessage }
if let dataExtractionNotification = DataExtractionNotification.fromProto(proto) { return dataExtractionNotification }
2020-11-06 06:31:56 +01:00
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
2021-01-13 03:38:07 +01:00
if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage }
2020-11-06 06:31:56 +01:00
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
return nil
}()
if let message = message {
2021-01-13 03:38:07 +01:00
// Ignore self sends if needed
2021-01-14 00:40:58 +01:00
if !message.isSelfSendValid {
2021-01-13 03:38:07 +01:00
guard sender != userPublicKey else { throw Error.selfSend }
}
// Guard against control messages in open groups
2020-11-30 01:00:28 +01:00
if isOpenGroupMessage {
guard message is VisibleMessage else { throw Error.invalidMessage }
}
2021-01-13 03:38:07 +01:00
// Finish parsing
2020-11-17 06:23:13 +01:00
message.sender = sender
2020-11-23 05:58:48 +01:00
message.recipient = userPublicKey
2020-11-19 06:28:30 +01:00
message.sentTimestamp = envelope.timestamp
message.receivedTimestamp = NSDate.millisecondTimestamp()
2021-04-15 02:09:05 +02:00
if isOpenGroupMessage {
message.openGroupServerTimestamp = envelope.serverTimestamp
2021-04-12 08:56:53 +02:00
}
2020-11-18 05:53:45 +01:00
message.groupPublicKey = groupPublicKey
2020-11-30 01:00:28 +01:00
message.openGroupServerMessageID = openGroupMessageServerID
2021-01-13 03:38:07 +01:00
// Validate
2020-11-26 23:07:24 +01:00
var isValid = message.isValid
if message is VisibleMessage && !isValid && proto.dataMessage?.attachments.isEmpty == false {
isValid = true
}
guard isValid else {
throw Error.invalidMessage
}
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
// for this issue.
if let message = message as? ClosedGroupControlMessage, case .new = message.kind {
// Allow duplicates in this case to avoid the following situation:
// The app performed a background poll or received a push notification
// This method was invoked and the received message timestamps table was updated
// Processing wasn't finished
// The user doesn't see the new closed group
} else {
guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage }
storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction)
}
2021-01-13 03:38:07 +01:00
// Return
2020-11-20 01:10:53 +01:00
return (message, proto)
2020-11-06 06:31:56 +01:00
} else {
2020-11-07 23:03:08 +01:00
throw Error.unknownMessage
2020-11-06 06:31:56 +01:00
}
2020-11-06 04:05:45 +01:00
}
2020-11-05 23:17:05 +01:00
}