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.
		
		
		
		
		
			
		
			
				
	
	
		
			177 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			177 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Swift
		
	
import SessionUtilitiesKit
 | 
						|
 | 
						|
public enum MessageReceiver {
 | 
						|
    private static var lastEncryptionKeyPairRequest: [String:Date] = [:] 
 | 
						|
 | 
						|
    public enum Error : LocalizedError {
 | 
						|
        case duplicateMessage
 | 
						|
        case invalidMessage
 | 
						|
        case unknownMessage
 | 
						|
        case unknownEnvelopeType
 | 
						|
        case noUserX25519KeyPair
 | 
						|
        case noUserED25519KeyPair
 | 
						|
        case invalidSignature
 | 
						|
        case noData
 | 
						|
        case senderBlocked
 | 
						|
        case noThread
 | 
						|
        case selfSend
 | 
						|
        case decryptionFailed
 | 
						|
        case invalidGroupPublicKey
 | 
						|
        case noGroupKeyPair
 | 
						|
 | 
						|
        public var isRetryable: Bool {
 | 
						|
            switch self {
 | 
						|
            case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType,
 | 
						|
                .invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed: return false
 | 
						|
            default: return true
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public var errorDescription: String? {
 | 
						|
            switch self {
 | 
						|
            case .duplicateMessage: return "Duplicate message."
 | 
						|
            case .invalidMessage: return "Invalid message."
 | 
						|
            case .unknownMessage: return "Unknown message type."
 | 
						|
            case .unknownEnvelopeType: return "Unknown envelope type."
 | 
						|
            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."
 | 
						|
            case .senderBlocked: return "Received a message from a blocked user."
 | 
						|
            case .noThread: return "Couldn't find thread for message."
 | 
						|
            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."
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) {
 | 
						|
        let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
 | 
						|
        let isOpenGroupMessage = (openGroupMessageServerID != nil)
 | 
						|
        // Parse the envelope
 | 
						|
        let envelope = try SNProtoEnvelope.parseData(data)
 | 
						|
        let storage = SNMessagingKitConfiguration.shared.storage
 | 
						|
        // Decrypt the contents
 | 
						|
        guard let ciphertext = envelope.content else { throw Error.noData }
 | 
						|
        var plaintext: Data!
 | 
						|
        var sender: String!
 | 
						|
        var groupPublicKey: String? = nil
 | 
						|
        if isOpenGroupMessage {
 | 
						|
            (plaintext, sender) = (envelope.content!, envelope.source!)
 | 
						|
        } else {
 | 
						|
            switch envelope.type {
 | 
						|
            case .sessionMessage:
 | 
						|
                guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { throw Error.noUserX25519KeyPair }
 | 
						|
                (plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
 | 
						|
            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
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                groupPublicKey = envelope.source
 | 
						|
                try decrypt()
 | 
						|
                /*
 | 
						|
                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)
 | 
						|
                }
 | 
						|
                 */
 | 
						|
            default: throw Error.unknownEnvelopeType
 | 
						|
            }
 | 
						|
        }
 | 
						|
        // Don't process the envelope any further if the sender is blocked
 | 
						|
        guard !isBlocked(sender) else { throw Error.senderBlocked }
 | 
						|
        // Parse the proto
 | 
						|
        let proto: SNProtoContent
 | 
						|
        do {
 | 
						|
            proto = try SNProtoContent.parseData((plaintext as NSData).removePadding())
 | 
						|
        } catch {
 | 
						|
            SNLog("Couldn't parse proto due to error: \(error).")
 | 
						|
            throw error
 | 
						|
        }
 | 
						|
        // Parse the message
 | 
						|
        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 }
 | 
						|
            if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
 | 
						|
            if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage }
 | 
						|
            if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
 | 
						|
            return nil
 | 
						|
        }()
 | 
						|
        if let message = message {
 | 
						|
            // Ignore self sends if needed
 | 
						|
            if !message.isSelfSendValid {
 | 
						|
                guard sender != userPublicKey else { throw Error.selfSend }
 | 
						|
            }
 | 
						|
            // Guard against control messages in open groups
 | 
						|
            if isOpenGroupMessage {
 | 
						|
                guard message is VisibleMessage else { throw Error.invalidMessage }
 | 
						|
            }
 | 
						|
            // Finish parsing
 | 
						|
            message.sender = sender
 | 
						|
            message.recipient = userPublicKey
 | 
						|
            message.sentTimestamp = envelope.timestamp
 | 
						|
            message.receivedTimestamp = NSDate.millisecondTimestamp()
 | 
						|
            if isOpenGroupMessage {
 | 
						|
                message.openGroupServerTimestamp = envelope.serverTimestamp
 | 
						|
            }
 | 
						|
            message.groupPublicKey = groupPublicKey
 | 
						|
            message.openGroupServerMessageID = openGroupMessageServerID
 | 
						|
            // Validate
 | 
						|
            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)
 | 
						|
            }
 | 
						|
            // Return
 | 
						|
            return (message, proto)
 | 
						|
        } else {
 | 
						|
            throw Error.unknownMessage
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |