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

173 lines
8.4 KiB
Swift
Raw Normal View History

2020-11-09 00:58:47 +01:00
import SessionUtilitiesKit
2020-11-05 23:17:05 +01:00
internal enum MessageReceiver {
2020-11-24 10:09:23 +01:00
// MARK: Error
internal enum Error : LocalizedError {
2020-11-06 06:31:56 +01:00
case invalidMessage
case unknownMessage
case unknownEnvelopeType
case noUserPublicKey
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
// Shared sender keys
case invalidGroupPublicKey
case noGroupPrivateKey
case sharedSecretGenerationFailed
2020-11-06 06:31:56 +01:00
2020-11-18 05:36:51 +01:00
internal var isRetryable: Bool {
switch self {
case .invalidMessage, .unknownMessage, .unknownEnvelopeType, .noData, .senderBlocked, .selfSend: return false
default: return true
}
}
internal var errorDescription: String? {
2020-11-06 06:31:56 +01:00
switch self {
case .invalidMessage: return "Invalid message."
case .unknownMessage: return "Unknown message type."
case .unknownEnvelopeType: return "Unknown envelope type."
case .noUserPublicKey: return "Couldn't find user key pair."
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."
// Shared sender keys
case .invalidGroupPublicKey: return "Invalid group public key."
case .noGroupPrivateKey: return "Missing group private key."
case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret."
case .selfSend: return "Message addressed at self."
2020-11-06 06:31:56 +01:00
}
}
}
2020-11-24 10:09:23 +01:00
// MARK: Parsing
2020-11-20 01:10:53 +01:00
internal static func parse(_ data: Data, messageServerID: UInt64?, using transaction: Any) throws -> (Message, SNProtoContent) {
2020-11-23 05:58:48 +01:00
let userPublicKey = Configuration.shared.storage.getUserPublicKey()
// Parse the envelope
2020-11-17 06:23:13 +01:00
let envelope = try SNProtoEnvelope.parseData(data)
// Decrypt the contents
let plaintext: Data
2020-11-17 06:23:13 +01:00
let sender: String
2020-11-18 05:53:45 +01:00
var groupPublicKey: String? = nil
switch envelope.type {
2020-11-17 06:23:13 +01:00
case .unidentifiedSender: (plaintext, sender) = try decryptWithSignalProtocol(envelope: envelope, using: transaction)
2020-11-18 05:53:45 +01:00
case .closedGroupCiphertext:
(plaintext, sender) = try decryptWithSharedSenderKeys(envelope: envelope, using: transaction)
groupPublicKey = envelope.source
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-18 05:53:45 +01:00
guard !Configuration.shared.messageReceiverDelegate.isBlocked(sender) else { throw Error.senderBlocked }
2020-11-23 05:58:48 +01:00
// Ignore self sends
guard sender != userPublicKey else { throw Error.selfSend }
// 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 closedGroupUpdate = ClosedGroupUpdate.fromProto(proto) { return closedGroupUpdate }
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
return nil
}()
if let message = message {
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
2020-11-10 05:48:47 +01:00
message.receivedTimestamp = NSDate.millisecondTimestamp()
2020-11-18 05:53:45 +01:00
message.groupPublicKey = groupPublicKey
message.openGroupServerMessageID = messageServerID
guard message.isValid else { throw Error.invalidMessage }
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-17 06:23:13 +01:00
2020-11-24 10:09:23 +01:00
// MARK: Handling
2020-11-20 01:10:53 +01:00
internal static func handle(_ message: Message, associatedWithProto proto: SNProtoContent, using transaction: Any) throws {
2020-11-17 06:23:13 +01:00
switch message {
case let message as ReadReceipt: handleReadReceipt(message, using: transaction)
case let message as TypingIndicator: handleTypingIndicator(message, using: transaction)
case let message as ClosedGroupUpdate: handleClosedGroupUpdate(message, using: transaction)
case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction)
2020-11-20 01:10:53 +01:00
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, using: transaction)
2020-11-17 06:23:13 +01:00
default: fatalError()
}
}
private static func handleReadReceipt(_ message: ReadReceipt, using transaction: Any) {
2020-11-18 05:53:45 +01:00
Configuration.shared.messageReceiverDelegate.markMessagesAsRead(message.timestamps!, from: message.sender!, at: message.receivedTimestamp!)
}
private static func handleTypingIndicator(_ message: TypingIndicator, using transaction: Any) {
2020-11-18 05:53:45 +01:00
let delegate = Configuration.shared.messageReceiverDelegate
switch message.kind! {
2020-11-18 05:53:45 +01:00
case .started: delegate.showTypingIndicatorIfNeeded(for: message.sender!)
case .stopped: delegate.hideTypingIndicatorIfNeeded(for: message.sender!)
}
}
private static func handleClosedGroupUpdate(_ message: ClosedGroupUpdate, using transaction: Any) {
2020-11-18 05:53:45 +01:00
let delegate = Configuration.shared.messageReceiverDelegate
switch message.kind! {
case .new: delegate.handleNewGroup(message, using: transaction)
case .info: delegate.handleGroupUpdate(message, using: transaction)
case .senderKeyRequest: delegate.handleSenderKeyRequest(message, using: transaction)
case .senderKey: delegate.handleSenderKey(message, using: transaction)
}
}
private static func handleExpirationTimerUpdate(_ message: ExpirationTimerUpdate, using transaction: Any) {
2020-11-18 05:53:45 +01:00
let delegate = Configuration.shared.messageReceiverDelegate
2020-11-18 05:36:51 +01:00
if message.duration! > 0 {
2020-11-18 05:53:45 +01:00
delegate.setExpirationTimer(to: message.duration!, for: message.sender!, groupPublicKey: message.groupPublicKey, using: transaction)
2020-11-18 05:36:51 +01:00
} else {
2020-11-18 05:53:45 +01:00
delegate.disableExpirationTimer(for: message.sender!, groupPublicKey: message.groupPublicKey, using: transaction)
2020-11-18 05:36:51 +01:00
}
}
2020-11-20 01:10:53 +01:00
private static func handleVisibleMessage(_ message: VisibleMessage, associatedWithProto proto: SNProtoContent, using transaction: Any) throws {
2020-11-18 05:53:45 +01:00
let delegate = Configuration.shared.messageReceiverDelegate
2020-11-20 01:10:53 +01:00
let storage = Configuration.shared.storage
2020-11-23 00:24:40 +01:00
// Parse & persist attachments
2020-11-20 04:04:56 +01:00
let attachments: [VisibleMessage.Attachment] = proto.dataMessage!.attachments.compactMap { proto in
guard let attachment = VisibleMessage.Attachment.fromProto(proto) else { return nil }
return attachment.isValid ? attachment : nil
}
2020-11-23 00:24:40 +01:00
let attachmentIDs = storage.persist(attachments, using: transaction)
2020-11-20 04:04:56 +01:00
message.attachmentIDs = attachmentIDs
2020-11-23 00:24:40 +01:00
// Update profile if needed
if let profile = message.profile {
delegate.updateProfile(for: message.sender!, from: profile, using: transaction)
}
// Persist the message
guard let (threadID, tsIncomingMessageID) = storage.persist(message, groupPublicKey: message.groupPublicKey, using: transaction) else { throw Error.noThread }
message.threadID = threadID
// Start attachment downloads if needed
2020-11-20 04:04:56 +01:00
storage.withAsync({ transaction in
attachmentIDs.forEach { attachmentID in
2020-11-23 00:24:40 +01:00
let downloadJob = AttachmentDownloadJob(attachmentID: attachmentID, tsIncomingMessageID: tsIncomingMessageID)
2020-11-20 04:04:56 +01:00
if CurrentAppContext().isMainAppAndActive {
JobQueue.shared.add(downloadJob, using: transaction)
} else {
JobQueue.shared.addWithoutExecuting(downloadJob, using: transaction)
}
}
}, completion: { })
2020-11-17 06:23:13 +01:00
// Cancel any typing indicators
2020-11-18 05:53:45 +01:00
delegate.cancelTypingIndicatorsIfNeeded(for: message.sender!)
2020-11-17 06:23:13 +01:00
// Notify the user if needed
2020-11-23 00:24:40 +01:00
delegate.notifyUserIfNeeded(forMessageWithID: tsIncomingMessageID, threadID: threadID)
2020-11-17 06:23:13 +01:00
}
2020-11-05 23:17:05 +01:00
}