Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  TaskExecution.swift   Sprache: JAVA

 

import CocoaLumberjackSwift
import Foundation
import PromiseKit
import ThreemaEssentials
import ThreemaProtocols

enum TaskExecutionError: Error {
    case conversationNotFound(for: TaskDefinitionProtocol)
    case createAbstractMessageFailed
    case createReflectedMessageFailed
    case multiDeviceNotRegistered
    case makeBoxOfMessageFailed
    case messageReceiverBlockedOrUnknown
    case messageTypeMismatch(message: String?)
    case missingGroupInformation
    case missingMessageInformation
    case missingMessageNonce
    case processIncomingMessageFailed(message: String?)
    case reflectMessageFailed(message: String?)
    case reflectMessageTimeout(message: String?)
    case reflectMessageInterrupted(message: String?)
    case sendMessageFailed(message: String)
    case sendMessageTimeout(message: String)
    case sendMessageInterrupted(message: String)
    case wrongTaskDefinitionType
    case invalidContact(message: String)
    case ownContact(message: String)
    case multiDeviceNotSupported
    case nonceGenerationFailed
    case taskDropped
    case decryptMessageFailed(reflectID: String)
}

extension TaskDefinition {
    /// Check if task is dropped and throws `TaskExecutionError.taskDropped` if this is the case
    func checkDropping() throws {
        if isDropped {
            throw TaskExecutionError.taskDropped
        }
    }
}

class TaskExecution: NSObject {
    var taskContext: TaskContextProtocol
    var taskDefinition: TaskDefinitionProtocol
    let frameworkInjector: FrameworkInjectorProtocol

    private let responseTimeoutInSeconds = 40
    
    required init(
        taskContext: TaskContextProtocol,
        taskDefinition: TaskDefinitionProtocol,
        backgroundFrameworkInjector: FrameworkInjectorProtocol
    ) {
        self.taskContext = taskContext
        self.taskDefinition = taskDefinition
        self.frameworkInjector = backgroundFrameworkInjector
    }
    
    required convenience init(taskContext: TaskContextProtocol, taskDefinition: TaskDefinitionProtocol) {
        self.init(
            taskContext: taskContext,
            taskDefinition: taskDefinition,
            backgroundFrameworkInjector: BusinessInjector(forBackgroundProcess: true)
        )
    }

    // MARK: Promises
    
    func isMultiDeviceRegistered() -> Guarantee<Bool> {
        Guarantee { $0(frameworkInjector.userSettings.enableMultiDevice) }
    }
    
    /// Reflect message to mediator server.
    /// - Parameters:
    ///     - message: Message to reflect
    ///     - ltReflect: Logging Tag for reflect message
    ///     - ltAck: Logging Tag for ack reflect message
    /// - Returns: Reflected at
    /// - Throws: TaskExecutionError.reflectMessageFailed
    @discardableResult
    func reflectMessage(message: AbstractMessage, ltReflect: LoggingTag, ltAck: LoggingTag) throws -> Date {
        assert(ltReflect != .none && ltAck != .none)

        var envelope: D2d_Envelope

        let body: Data? =
            if let quotedMessage = message as? QuotedMessageProtocol {
                quotedMessage.quotedBody()
            }
            else {
                message.body()
            }

        // Get envelope and its Reflect ID. It's sender me, than must be an outgoing message!
        if message.fromIdentity == frameworkInjector.myIdentityStore.identity {
            if message.flagGroupMessage() {
                guard let groupID = (message as? AbstractGroupMessage)?.groupID,
                      let groupCreator = (message as? AbstractGroupMessage)?.groupCreator else {
                    throw TaskExecutionError.reflectMessageFailed(message: message.loggingDescription)
                }

                envelope = try frameworkInjector.mediatorMessageProtocol.getEnvelopeForOutgoingMessage(
                    type: Int32(message.type()),
                    body: body,
                    messageID: message.messageID.littleEndian(),
                    groupID: groupID.littleEndian(),
                    groupCreatorIdentity: groupCreator,
                    createdAt: message.date,
                    nonces: messageNonces(),
                    deviceID: frameworkInjector.multiDeviceManager.thisDevice.deviceID
                )
            }
            else {
                envelope = try frameworkInjector.mediatorMessageProtocol.getEnvelopeForOutgoingMessage(
                    type: Int32(message.type()),
                    body: body,
                    messageID: message.messageID.littleEndian(),
                    receiverIdentity: message.toIdentity,
                    createdAt: message.date,
                    nonce: messageNonce(for: message.toIdentity),
                    deviceID: frameworkInjector.multiDeviceManager.thisDevice.deviceID
                )
            }
        }
        else {
            envelope = try frameworkInjector.mediatorMessageProtocol.getEnvelopeForIncomingMessage(
                type: Int32(message.type()),
                body: body,
                messageID: message.messageID.littleEndian(),
                senderIdentity: message.fromIdentity,
                createdAt: message.date,
                nonce: messageNonce(for: message.fromIdentity)
            )
        }
        
        return try reflectMessage(envelope: envelope, ltReflect: ltReflect, ltAck: ltAck)
    }

    /// Reflect message to mediator server.
    /// - Parameters:
    ///     - envelope: Envelope message for sending to mediator
    ///     - ltReflect: Logging Tag for reflect message
    ///     - ltAck: Logging Tag for ack reflect message
    /// - Returns: Reflected at
    /// - Throws: TaskExecutionError.createReflectedMessageFailed, TaskExecutionError.reflectMessageTimeout
    @discardableResult
    func reflectMessage(envelope: D2d_Envelope, ltReflect: LoggingTag, ltAck: LoggingTag) throws -> Date {
        assert(ltReflect != .none && ltAck != .none)

        let reflectData = frameworkInjector.mediatorMessageProtocol.encodeEnvelope(envelope: envelope)

        guard let reflectID = reflectData.reflectID, let reflectMessage = reflectData.reflectMessage else {
            throw TaskExecutionError.createReflectedMessageFailed
        }

        var reflectedAt: Date?

        let loggingMsgInfo = "(Reflect ID: \(reflectID.hexString) \(envelope.loggingDescription))"

        let mediatorMessageAck = DispatchGroup()
        let notificationCenter = NotificationCenter.default
        var mediatorMessageAckObserver: NSObjectProtocol?

        // Add observer to release the waiting of the server ack, if the task is interrupted
        let task = taskDefinition as? TaskDefinition
        let interruptionObserver = observeInterruption(of: task) {
            DDLogWarn("\(ltAck.hexString) \(ltAck) \(loggingMsgInfo) interrupted")
            mediatorMessageAck.leave()
        }

        func removeObservers() {
            if let mediatorMessageAckObserver {
                notificationCenter.removeObserver(mediatorMessageAckObserver)
            }

            if let interruptionObserver {
                interruptionObserver.invalidate()
            }
        }

        mediatorMessageAckObserver = notificationCenter.addObserver(
            forName: TaskManager.mediatorMessageAckObserverName(reflectID: reflectID),
            object: nil,
            queue: OperationQueue.current
        ) { notification in
            // Get reflected-ack ID and reflected-ack sent date
            if let messageAckReflectID = notification.object as? Data {
                if messageAckReflectID.elementsEqual(reflectID) {

                    reflectedAt = notification.userInfo?[reflectID] as? Date

                    DDLogNotice("\(ltAck.hexString) \(ltAck) \(loggingMsgInfo)")
                    removeObservers()

                    mediatorMessageAck.leave()
                }
            }
        }

        mediatorMessageAck.enter()

        DDLogNotice("\(ltReflect.hexString) \(ltReflect) \(loggingMsgInfo)")
        if let error = frameworkInjector.serverConnector.reflectMessage(reflectMessage) {
            removeObservers()
            throw error
        }
        
        guard mediatorMessageAck.wait(timeout: .now() + .seconds(responseTimeoutInSeconds)) == .success else {
            removeObservers()
            throw TaskExecutionError.reflectMessageTimeout(message: loggingMsgInfo)
        }

        removeObservers()

        guard let task, !task.isInterrupted else {
            throw TaskExecutionError.reflectMessageInterrupted(message: loggingMsgInfo)
        }

        guard let reflectedAt else {
            throw TaskExecutionError.reflectMessageFailed(message: "Reflected at is nil for \(loggingMsgInfo)")
        }
        
        return reflectedAt
    }

    /// Send abstract message to chat server.
    /// - Parameters:
    ///     - message: Message to send
    ///     - ltSend: Logging Tag for send message
    ///     - ltAck: Logging Tag for ack send message
    /// - Returns: AbstractMessage if message was sent otherwise nil
    /// - Throws: TaskExecutionError.sendMessageFailed, TaskExecutionError.sendMessageTimeout
    func sendMessage(
        message: AbstractMessage,
        ltSend: LoggingTag,
        ltAck: LoggingTag
    ) -> Promise<AbstractMessage?> {
        // This is implemented as a separated promise step as there was no easy way to implement a way to wait for the
        // fetch of the feature mask to complete, if needed.
        // TODO: (IOS-4348) See if we can optimize that more
        firstly { () -> Promise<Void> in
            Promise { seal in
                self.frameworkInjector.entityManager.perform {
                    guard let toContact = self.frameworkInjector.entityManager.entityFetcher.contactEntity(
                        for: message.toIdentity
                    ) else {
                        return seal.reject(TaskExecutionError.sendMessageFailed(
                            message: "Contact not found for identity \(message.toIdentity ?? "no identity") (\(message.loggingDescription))"
                        ))
                    }
                    
                    // If the contact has a feature mask that is 0 it was probably never fetched. This can happen if the
                    // field changes after an update to 5.9 (IOS-4220). This is a problem, because eligibility for
                    // sending FS messages is determined base on the feature mask. Thus we try to fetch the feature mask
                    // if it is 0.
                    if toContact.featureMask == 0 {
                        let contactIdentity = toContact.threemaIdentity
                        let contactFeatureMask = toContact.featureMask

                        Task {
                            do {
                                DDLogNotice("Fetch feature mask of \(contactIdentity), because it is currently 0.")
                                try await FeatureMask.updateFeatureMask(for: [contactIdentity])
                            }
                            catch {
                                DDLogWarn(
                                    "Failed to update feature mask of \(contactIdentity). Current value \(contactFeatureMask)"
                                )
                            }

                            // This is a best effort, thus we will always succeed
                            seal.fulfill_()
                        }
                    }
                    else {
                        seal.fulfill_()
                    }
                }
            }
        }
        .then { _ in
            self.sendMessageInternal(message: message, ltSend: ltSend, ltAck: ltAck)
        }
    }
    
    /// Internal part of `sendMessage(message:ltSend:ltAck:)`
    ///
    /// This is left as is to reduce the changes introduced with the feature mask check in
    /// `sendMessage(message:ltSend:ltAck:)` with IOS-4475
    /// TODO: (IOS-4348) Try to clean this up
    private func sendMessageInternal(
        message: AbstractMessage,
        ltSend: LoggingTag,
        ltAck: LoggingTag
    ) -> Promise<AbstractMessage?> {
        Promise { seal in
            assert(ltSend != .none && ltAck != .none)
            
            guard message.toIdentity != self.frameworkInjector.myIdentityStore.identity else {
                seal.reject(TaskExecutionError.sendMessageFailed(
                    message: "Do not sending message to own identity \(String(describing: message.toIdentity)) (\(String(describing: message.loggingDescription)))"
                ))
                return
            }

            frameworkInjector.entityManager.perform {
                guard let toContact = self.frameworkInjector.entityManager.entityFetcher
                    .contactEntity(for: message.toIdentity) else {
                    seal.reject(TaskExecutionError.sendMessageFailed(
                        message: "Contact not found for identity \(message.toIdentity ?? "no identity") (\(message.loggingDescription))"
                    ))
                    return
                }

                guard toContact.isValid else {
                    let msg =
                        "Do not sending message to invalid identity \(String(describing: message.toIdentity)) (\(String(describing: message.loggingDescription)))"
                    guard message.flagGroupMessage() else {
                        seal.reject(TaskExecutionError.invalidContact(message: msg))
                        return
                    }
                    DDLogWarn("\(msg)")

                    seal.fulfill(nil)
                    return
                }

                if self.taskDefinition is TaskDefinitionSendMessageProtocol,
                   let toIdentity = message.toIdentity {
                    guard !self.isMessageAlreadySentTo(identity: toIdentity) else {
                        DDLogWarn(
                            "Message already sent to \(toIdentity)) (\(String(describing: message.loggingDescription)))"
                        )
                        seal.fulfill(message)
                        return
                    }
                }

                DebugLog.log(message, isIncoming: false)

                var auxMessage: ForwardSecurityEnvelopeMessage?
                var messageToSend = message

                // Check whether the message is not already an FS message and the destination contact supports forward
                // security (Common Send Steps (6.1))
                if ThreemaEnvironment.supportsForwardSecurity,
                   !(message is ForwardSecurityEnvelopeMessage),
                   toContact.isForwardSecurityAvailable {
                    do {
                        let fsContact = ForwardSecurityContact(
                            identity: toContact.identity,
                            publicKey: toContact.publicKey
                        )
                        
                        (auxMessage, messageToSend) = try self.frameworkInjector.fsmp.makeMessage(
                            receiver: fsContact,
                            innerMessage: message
                        )
                    }
                    catch {
                        seal.reject(error)
                        return
                    }
                }
                else {
                    if !ThreemaEnvironment.supportsForwardSecurity {
                        DDLogDebug(
                            "[ForwardSecurity] Don't try sending \(message.loggingDescription) with FS, because FS is not supported"
                        )
                    }
                    else if message is ForwardSecurityEnvelopeMessage {
                        DDLogDebug(
                            "[ForwardSecurity] Don't try sending \(message.loggingDescription) with FS, because it is already an FS message"
                        )
                    }
                    else if !toContact.isForwardSecurityAvailable {
                        DDLogNotice(
                            "[ForwardSecurity] Don't try sending \(message.loggingDescription) with FS, because \(toContact.identity) doesn't support it: featureMask=\(toContact.featureMask)"
                        )
                    }
                }

                // TODO: (IOS-4348) Optimize messages sending
                // Sending can be optimized, because the messages need to be sent in order, but we only need to wait at
                // the end for all server acks. One possible implementation is to do the sending in order an then return
                // a promise that resolves when the server ack is received. So after sending all the messages we wait
                // for all the server acks.
                
                // Send message in own thread, because of possible network latency
                DispatchQueue.global().async {
                    if let auxMessage {
                        
                        let failure = {
                            let err = TaskExecutionError.sendMessageFailed(message: auxMessage.loggingDescription)
                            seal.reject(err)
                        }
                        
                        guard let nonce = NaClCrypto.shared().randomBytes(Int32(ThreemaProtocol.nonceLength)) else {
                            seal.reject(TaskExecutionError.nonceGenerationFailed)
                            return
                        }
                        
                        // We have an auxiliary (control) message to send before the actual message
                        guard let boxAuxMsg = self.frameworkInjector.entityManager.performAndWait({
                            auxMessage.makeBox(
                                toContact,
                                myIdentityStore: self.frameworkInjector.myIdentityStore,
                                nonce: nonce
                            )
                        }) else {
                            failure()
                            return
                        }
                        
                        // TODO: (IOS-4751) Check as described in protocol, see ticket for more information
                        if !auxMessage.flagDontQueue() {
                            do {
                                try self.frameworkInjector.nonceGuard.processed(boxedMessage: boxAuxMsg)
                            }
                            catch {
                                seal.reject(error)
                                return
                            }
                        }

                        do {
                            try self.sendAndWait(
                                abstractMessage: auxMessage,
                                boxMessage: boxAuxMsg,
                                ltSend: ltSend,
                                ltAck: ltAck,
                                isAuxMessage: true
                            )
                        }
                        catch {
                            failure()
                            return
                        }
                    }
                    
                    self.frameworkInjector.entityManager.perform {
                        var nonce: Data
                        do {
                            nonce = try self.messageNonce(for: toContact.identity)
                        }
                        catch {
                            seal.reject(
                                TaskExecutionError.sendMessageFailed(message: "\(error) \(message.loggingDescription)")
                            )
                            return
                        }

                        guard let boxMsg = messageToSend.makeBox(
                            toContact,
                            myIdentityStore: self.frameworkInjector.myIdentityStore,
                            nonce: nonce
                        ) else {
                            seal.reject(TaskExecutionError.sendMessageFailed(message: message.loggingDescription))
                            return
                        }

                        // TODO: (IOS-4751) Check as described in protocol, see ticket for more information
                        if !message.flagDontQueue() {
                            do {
                                try self.frameworkInjector.nonceGuard.processed(boxedMessage: boxMsg)
                            }
                            catch {
                                seal.reject(error)
                                return
                            }
                        }

                        // TODO: (IOS-4348) Why is this `.utility`, but aux `.default`?
                        DispatchQueue.global(qos: .utility).async {
                            do {
                                try self.sendAndWait(
                                    abstractMessage: messageToSend,
                                    boxMessage: boxMsg,
                                    ltSend: ltSend,
                                    ltAck: ltAck,
                                    isAuxMessage: false
                                )
                            }
                            catch {
                                seal.reject(error)
                                return
                            }
                            
                            // Commit new session (if needed) and update last message sent date if any message was an FS
                            // message.
                            // We do these checks independent of the `ForwardSecurityMessageProcessor` call because a
                            // message passed to `sendMessage(message:ltSend:ltAck:)` (most likely an FS control
                            // message) might already be of type `ForwardSecurityEnvelopeMessage`.
                            if let auxMessage {
                                self.commitNewSessionAndUpdateLastMessageSentDate(for: auxMessage)
                            }
                            else if let sentMessage = messageToSend as? ForwardSecurityEnvelopeMessage {
                                self.commitNewSessionAndUpdateLastMessageSentDate(for: sentMessage)
                            }

                            // We now know the FS mode so we also set it for the "inner" message
                            message.forwardSecurityMode = messageToSend.forwardSecurityMode
                            seal.fulfill(message)
                        }
                    }
                }
            }
        }
    }
    
    /// Send an `empty` message in the passed FS session
    ///
    /// Only use if you specifically want to send an `empty` message to enforce an session upgrade if possible. In
    /// general use `sendMessage()`.
    ///
    /// - Parameter session: Session to send `empty` message in
    /// - Returns: Promise that fulfills if sending completed with server ack and updated own ratchets, last sent and
    /// versions in `session`. Throws various errors if validation or sending fails
    func sendEmptyFSMessage(in session: DHSession) -> Promise<Void> {
        Promise { seal in
            let message: ForwardSecurityEnvelopeMessage
            do {
                message = try self.frameworkInjector.fsmp.makeEmptyMessage(for: session)
            }
            catch {
                seal.reject(error)
                return
            }
            
            guard message.toIdentity != self.frameworkInjector.myIdentityStore.identity else {
                seal.reject(TaskExecutionError.sendMessageFailed(
                    message: "Do not sending message to own identity \(message.toIdentity ?? "no identity") (\(message.loggingDescription))"
                ))
                return
            }
                
            // TODO: (IOS-4348) Maybe optimize messages sending here, too
            
            // Send message in own thread, because of possible network latency
            DispatchQueue.global().async {
                self.frameworkInjector.entityManager.perform {
                    guard let toContact = self.frameworkInjector.entityManager.entityFetcher
                        .contactEntity(for: message.toIdentity) else {
                        seal.reject(TaskExecutionError.sendMessageFailed(
                            message: "Contact not found for identity \(message.toIdentity ?? "no identity") (\(message.loggingDescription))"
                        ))
                        return
                    }
                    
                    guard toContact.isValid else {
                        let msg =
                            "Do not sending message to invalid identity \(message.toIdentity ?? "no identity") (\(message.loggingDescription))"
                        seal.reject(TaskExecutionError.invalidContact(message: msg))
                        return
                    }
                    
                    guard let nonce = NaClCrypto.shared().randomBytes(Int32(ThreemaProtocol.nonceLength)) else {
                        
                        seal.reject(TaskExecutionError.nonceGenerationFailed)
                        return
                    }

                    guard let boxMsg = message.makeBox(
                        toContact,
                        myIdentityStore: self.frameworkInjector.myIdentityStore,
                        nonce: nonce
                    ) else {
                        seal.reject(TaskExecutionError.sendMessageFailed(message: message.loggingDescription))
                        return
                    }
                    
                    // TODO: (IOS-4751) Check as described in protocol, see ticket for more information
                    if !message.flagDontQueue() {
                        do {
                            try self.frameworkInjector.nonceGuard.processed(boxedMessage: boxMsg)
                        }
                        catch {
                            seal.reject(error)
                            return
                        }
                    }
                    
                    do {
                        try self.sendAndWait(
                            abstractMessage: message,
                            boxMessage: boxMsg,
                            ltSend: .sendOutgoingMessageToChat,
                            ltAck: .receiveOutgoingMessageAckFromChat,
                            isAuxMessage: true
                        )
                    }
                    catch {
                        seal.reject(error)
                        return
                    }
                    
                    // Commit update last message sent date and versions
                    do {
                        session.lastMessageSent = .now
                        try self.frameworkInjector.dhSessionStore
                            .updateNewSessionCommitLastMessageSentDateAndVersions(session: session)
                    }
                    catch {
                        DDLogError(
                            "[ForwardSecrecy] Unable to persist send date and versions for sent empty message"
                        )
                    }
                    
                    seal.fulfill_()
                }
            }
        }
    }

    private func sendAndWait(
        abstractMessage: AbstractMessage,
        boxMessage: BoxedMessage,
        ltSend: LoggingTag,
        ltAck: LoggingTag,
        isAuxMessage: Bool
    ) throws {
        assert(abstractMessage.fromIdentity == nil || abstractMessage.fromIdentity == boxMessage.fromIdentity)
        assert(abstractMessage.toIdentity == boxMessage.toIdentity)
        
        let chatMessageAck = DispatchGroup()
        let notificationCenter = NotificationCenter.default
        var messageAckObserver: NSObjectProtocol?

        // Add observer to release the waiting of the server ack, if the task is interrupted
        let task = taskDefinition as? TaskDefinition
        let interruptionObserver = observeInterruption(of: task) {
            DDLogWarn("\(ltAck.hexString) \(ltAck) \(abstractMessage.loggingDescription) interrupted")
            chatMessageAck.leave()
        }

        func removeObservers() {
            if let messageAckObserver {
                notificationCenter.removeObserver(messageAckObserver)
            }

            if let interruptionObserver {
                interruptionObserver.invalidate()
            }
        }

        messageAckObserver = notificationCenter.addObserver(
            forName: TaskManager.chatMessageAckObserverName(
                messageID: abstractMessage.messageID,
                toIdentity: abstractMessage.toIdentity
            ),
            object: nil,
            queue: OperationQueue.current
        ) { _ in
            DDLogNotice(
                "\(ltAck.hexString) \(ltAck) \(abstractMessage.loggingDescription) from \(abstractMessage.toIdentity ?? "?")"
            )
            removeObservers()
            chatMessageAck.leave()
        }
        
        chatMessageAck.enter()
        
        DDLogNotice(
            "\(ltSend.hexString) \(ltSend) \(abstractMessage.loggingDescription) to \(abstractMessage.toIdentity ?? "?")"
        )
        if frameworkInjector.serverConnector.send(boxMessage) {
            if abstractMessage.flagDontAck() {
                removeObservers()
            }
            else {
                let result = chatMessageAck.wait(timeout: .now() + .seconds(responseTimeoutInSeconds))
                if result == .success {
                    removeObservers()

                    guard let task, !task.isInterrupted else {
                        throw TaskExecutionError.sendMessageInterrupted(message: abstractMessage.loggingDescription)
                    }

                    if !isAuxMessage, let toIdentity = abstractMessage.toIdentity {
                        // Only non-AuxMessages need their nonces to be reflected and are thus stored
                        try messageAlreadySentTo(identity: toIdentity, nonce: messageNonce(for: toIdentity))
                    }
                }
                else {
                    removeObservers()
                    throw TaskExecutionError.sendMessageTimeout(message: abstractMessage.loggingDescription)
                }
            }
        }
        else {
            removeObservers()
            throw TaskExecutionError.sendMessageFailed(message: abstractMessage.loggingDescription)
        }
    }

    private func commitNewSessionAndUpdateLastMessageSentDate(for message: ForwardSecurityEnvelopeMessage) {
        do {
            if let session = try frameworkInjector.dhSessionStore.exactDHSession(
                myIdentity: frameworkInjector.myIdentityStore.identity,
                peerIdentity: message.toIdentity,
                sessionID: message.data.sessionID
            ) {
                assert(
                    session.newSessionCommitted || message.data is ForwardSecurityDataInit,
                    "If the session was not committed before we should have sent out an Init"
                )

                // At this point we commit the session
                session.newSessionCommitted = true
                session.lastMessageSent = .now
                
                try frameworkInjector.dhSessionStore.updateNewSessionCommitLastMessageSentDateAndVersions(
                    session: session
                )
            }
            else {
                DDLogError("No session found for DH session with ID \(message.data.sessionID)")
            }
        }
        catch {
            DDLogError("Unable to commit and update session last message state: \(error)")
        }
    }
    
    private func isMessageAlreadySentTo(identity: String) -> Bool {
        guard let task = taskDefinition as? TaskDefinitionSendMessageProtocol else {
            return false
        }

        var isMessageAlreadySentTo = false
        task.messageAlreadySentToQueue.sync {
            isMessageAlreadySentTo = task.messageAlreadySentTo.map(\.key).contains(identity)
        }
        return isMessageAlreadySentTo
    }

    private func messageAlreadySentTo(identity: String, nonce: Data) {
        guard var task = taskDefinition as? TaskDefinitionSendMessageProtocol else {
            return
        }

        task.messageAlreadySentToQueue.sync {
            task.messageAlreadySentTo[identity] = nonce
        }
    }

    /// Generates new message nonces for the message receivers of the task.
    ///
    /// - Parameter task: Task must implement `TaskDefinitionSendMessageNonceProtocol`
    /// - Throws: `TaskExecutionError.missingMessageNonce` if task doesn't support nonces
    func generateMessageNonces(for task: TaskDefinitionProtocol) throws {
        guard task is TaskDefinitionSendMessageNonceProtocol else {
            throw TaskExecutionError.missingMessageNonce
        }

        // Get message receivers for this task
        var identities: Set<String>
        if let task = task as? TaskDefinitionSendAbstractMessage {
            identities = Set<String>([task.message.toIdentity])
        }
        else if let task = task as? TaskDefinitionSendCallOfferAnswerMessage {
            identities = Set<String>([task.message.toIdentity])
        }
        else if let task = task as? TaskDefinitionSendDeliveryReceiptsMessage {
            identities = Set<String>([task.toIdentity])
        }
        else if let task = task as? TaskDefinitionGroupDissolve {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupCallStartMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupCreateMessage {
            identities = Set(task.toMembers)
            if let removeMembers = task.removedMembers {
                for member in removeMembers {
                    identities.insert(member)
                }
            }
        }
        else if let task = task as? TaskDefinitionSendGroupDeletePhotoMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupDeliveryReceiptsMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupLeaveMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupRenameMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendGroupSetPhotoMessage {
            identities = Set<String>(task.toMembers)
        }
        else if let task = task as? TaskDefinitionSendMessage {
            if task.isGroupMessage {
                guard let members = task.receivingGroupMembers else {
                    throw TaskExecutionError.missingGroupInformation
                }
                identities = members
            }
            else {
                guard let identity = try frameworkInjector.entityManager.performAndWait({
                    try self.getConversation(for: task).contact?.identity
                }) else {
                    throw TaskExecutionError.missingMessageInformation
                }
                identities = [identity]
            }
        }
        else if let task = task as? TaskDefinitionRunForwardSecurityRefreshSteps {
            identities = Set(task.contactIdentities.map(\.rawValue))
        }
        else {
            throw TaskExecutionError.missingMessageNonce
        }

        DDLogNotice("\(task) generate nonces")
        try generateMessageNonces(for: identities)
    }

    /// Generates new message nonces to encrypt message for receivers (Threema ID). If is the message for the receiver
    /// already sent, using stored nonce instead.
    ///
    /// - Parameter identities: Message nonces for receivers
    /// - Throws: `TaskExecutionError.missingMessageNonce` if task doesn't support nonces
    private func generateMessageNonces(for identities: Set<String>) throws {
        guard var taskNonce = taskDefinition as? TaskDefinitionSendMessageNonceProtocol else {
            throw TaskExecutionError.missingMessageNonce
        }

        taskNonce.nonces.removeAll()

        var excludeReceivers = TaskReceiverNonce()
        if let taskSend = taskDefinition as? TaskDefinitionSendMessageProtocol, !taskSend.messageAlreadySentTo.isEmpty {
            DDLogVerbose(
                "\(taskSend) message already sent to \(taskSend.messageAlreadySentTo.map(\.key).joined(separator: ","))"
            )
            excludeReceivers = taskSend.messageAlreadySentTo
        }

        for (key, value) in excludeReceivers {
            taskNonce.nonces[key] = value
        }

        for identity in identities
            .filter({
                $0 != self.frameworkInjector.myIdentityStore.identity && !excludeReceivers.map(\.key).contains($0) }) {
            taskNonce.nonces[identity] = NaClCrypto.shared().randomBytes(Int32(ThreemaProtocol.nonceLength))
        }
    }

    /// Get all message nonces.
    /// - Returns: All nonces of this task
    /// - Throws: `TaskExecutionError.missingMessageNonce` if task doesn't support nonces
    private func messageNonces() throws -> [Data] {
        guard let taskNonce = taskDefinition as? TaskDefinitionSendMessageNonceProtocol else {
            throw TaskExecutionError.missingMessageNonce
        }

        return taskNonce.nonces.map(\.value)
    }

    /// Get message nonce for receiver.
    /// - Parameter identity: Receiver of the message
    /// - Returns: Message nonce for a receiver
    /// - Throws: `TaskExecutionError.missingMessageNonce` if task doesn't support nonces
    private func messageNonce(for identity: String) throws -> Data {
        guard let taskNonce = taskDefinition as? TaskDefinitionSendMessageNonceProtocol else {
            throw TaskExecutionError.missingMessageNonce
        }

        guard let nonce = taskNonce.nonces[identity] else {
            throw TaskExecutionError.missingMessageNonce
        }

        return nonce
    }

    /// If group creator an gateway id and receiver and store-incoming-message is not set (group name without ☁), then
    /// the message (except leave and request sync) will not be send.
    ///
    /// - Parameter groupCreatorIdentity: Group creator identity
    /// - Parameter groupName: Group name
    /// - Parameter message: Message send to group
    /// - Returns: True send message, false not
    func canSendGroupMessageToGatewayID(
        groupCreatorIdentity: String,
        groupName: String?,
        message: AbstractMessage
    ) -> Bool {
        if groupCreatorIdentity.hasPrefix("*"), groupCreatorIdentity.elementsEqual(message.toIdentity),
           !(groupName?.hasPrefix(Constants.messageStoringGatewayGroupPrefix) ?? false),
           !(message is GroupLeaveMessage),
           !(message is GroupRequestSyncMessage) {
            DDLogWarn("Drop message to gateway id without store-incoming-message")

            return false
        }

        return true
    }

    /// Get conversation for TaskDefinitionSendBaseMessage, TaskDefinitionSendBallotVoteMessage,
    /// TaskDefinitionReflectIncomingMessage or TaskDefinitionSendAbstractMessage.
    ///
    /// - Parameter task: Task definition
    /// - Returns: Conversation or nil
    func getConversation(for task: TaskDefinitionProtocol) throws -> ConversationEntity {
        var conversation: ConversationEntity?

        if let task = task as? TaskDefinitionSendBaseMessage,
           let groupCreatorIdentity = task.groupCreatorIdentity ?? frameworkInjector.myIdentityStore.identity {
            conversation = task.isGroupMessage ? frameworkInjector.entityManager.entityFetcher
                .conversationEntity(
                    for: GroupIdentity(id: task.groupID!, creatorID: groupCreatorIdentity),
                    myIdentity: frameworkInjector.myIdentityStore.identity
                ) : frameworkInjector
                .entityManager.entityFetcher
                .conversationEntity(for: task.receiverIdentity!)
        }
        if let task = task as? TaskDefinitionSendReactionMessage,
           let groupCreatorIdentity = task.groupCreatorIdentity ?? frameworkInjector.myIdentityStore.identity {
            conversation = task.isGroupMessage ? frameworkInjector.entityManager.entityFetcher
                .conversationEntity(
                    for: GroupIdentity(id: task.groupID!, creatorID: groupCreatorIdentity),
                    myIdentity: frameworkInjector.myIdentityStore.identity
                ) : frameworkInjector
                .entityManager.entityFetcher
                .conversationEntity(for: task.receiverIdentity!)
        }
        else if let task = task as? TaskDefinitionSendDeleteEditMessage,
                let groupCreatorIdentity = task.groupCreatorIdentity ?? frameworkInjector.myIdentityStore.identity {
            conversation = task.isGroupMessage ? frameworkInjector.entityManager.entityFetcher
                .conversationEntity(
                    for: GroupIdentity(id: task.groupID!, creatorID: groupCreatorIdentity),
                    myIdentity: frameworkInjector.myIdentityStore.identity
                )
                : frameworkInjector.entityManager.entityFetcher
                .conversationEntity(for: task.receiverIdentity!)
        }
        else if let task = task as? TaskDefinitionSendBallotVoteMessage {
            conversation = frameworkInjector.entityManager.entityFetcher
                .ballotEntity(for: task.ballotID)?.conversation
        }
        else if let task = task as? TaskDefinitionReflectIncomingMessage {
            conversation = frameworkInjector.entityManager.conversation(
                forMessage: task.message,
                myIdentity: frameworkInjector.myIdentityStore.identity
            )
        }
        else if let task = task as? TaskDefinitionSendAbstractMessage {
            conversation = frameworkInjector.entityManager.conversation(
                forMessage: task.message,
                myIdentity: frameworkInjector.myIdentityStore.identity
            )
        }
        else if let task = task as? TaskDefinitionSendDeliveryReceiptsMessage {
            if task.toIdentity != frameworkInjector.myIdentityStore.identity {
                conversation = frameworkInjector.entityManager.entityFetcher
                    .conversationEntity(for: task.toIdentity)
            }
            else if task.fromIdentity != frameworkInjector.myIdentityStore.identity {
                conversation = frameworkInjector.entityManager.entityFetcher
                    .conversationEntity(for: task.fromIdentity)
            }
        }
        else if let task = task as? TaskDefinitionSendGroupDeliveryReceiptsMessage,
                let groupID = task.groupID, let creator = task.groupCreatorIdentity {
            conversation = frameworkInjector.entityManager.entityFetcher.conversationEntity(
                for: GroupIdentity(id: groupID, creatorID: creator),
                myIdentity: frameworkInjector.myIdentityStore.identity
            )
        }

        guard let conversation else {
            throw TaskExecutionError.conversationNotFound(for: task)
        }

        return conversation
    }
    
    // MARK: Abstract Message Types
    
    /// Create abstract message for base message (Core Data Entity).
    ///
    /// - Parameter task: Task (TaskDefinitionSendBaseMessage or TaskDefinitionSendBallotVoteMessage) with Core Data
    ///                   Entity
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Returns: Abstract message or nil if could not create
    func getAbstractMessage(
        _ task: TaskDefinitionProtocol,
        _ fromIdentity: String,
        _ toIdentity: String
    ) -> AbstractMessage? {
        
        if let task = task as? TaskDefinitionSendBaseMessage,
           let conversation = task.isGroupMessage ? frameworkInjector.entityManager.entityFetcher
           .conversationEntity(
               for: GroupIdentity(id: task.groupID!, creatorID: task.groupCreatorIdentity!),
               myIdentity: frameworkInjector.myIdentityStore.identity
           ) : frameworkInjector
           .entityManager.entityFetcher
           .conversationEntity(for: task.receiverIdentity!),
           let message = frameworkInjector.entityManager.entityFetcher.message(
               with: task.messageID,
               in: conversation
           ) {
            task.messageType = "\(type(of: message))"

            if let message = message as? TextMessageEntity {
                let msg: AbstractMessage = task.isGroupMessage ? GroupTextMessage() : BoxTextMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.messageID = message.id
                msg.date = message.date

                if task.isGroupMessage,
                   let msg = msg as? GroupTextMessage {
                    
                    msg.text = message.text
                    msg.quotedMessageID = message.quotedMessageID
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                }
                else if let msg = msg as? BoxTextMessage {
                    msg.text = message.text
                    msg.quotedMessageID = message.quotedMessageID
                }
                return msg
            }
            else if let message = message as? LocationMessageEntity {
                let msg: AbstractMessage = task.isGroupMessage ? GroupLocationMessage() : BoxLocationMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.messageID = message.id
                msg.date = message.date
                if task.isGroupMessage,
                   let msg = msg as? GroupLocationMessage {
                    
                    msg.latitude = message.latitude as! Double
                    msg.longitude = message.longitude as! Double
                    msg.accuracy = message.accuracy as! Double
                    msg.poiName = message.poiName
                    if let task = task as? TaskDefinitionSendLocationMessage {
                        msg.poiAddress = task.poiAddress
                    }
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                }
                else if let msg = msg as? BoxLocationMessage {
                    msg.latitude = message.latitude as! Double
                    msg.longitude = message.longitude as! Double
                    msg.accuracy = message.accuracy as! Double
                    msg.poiName = message.poiName
                    if let task = task as? TaskDefinitionSendLocationMessage {
                        msg.poiAddress = task.poiAddress
                    }
                }
                return msg
            }
            else if let message = message as? BallotMessageEntity, let ballot = message.ballot {
                let msg: BoxBallotCreateMessage = BallotMessageEncoder.encodeCreateMessage(forBallot: ballot)
                msg.messageID = message.id

                guard let groupID = task.groupID,
                      let groupCreatorIdentity = task.groupCreatorIdentity,
                      frameworkInjector.groupManager
                      .getConversation(for: GroupIdentity(
                          id: groupID,
                          creator: ThreemaIdentity(groupCreatorIdentity)
                      )) != nil else {
                    msg.fromIdentity = fromIdentity
                    msg.toIdentity = toIdentity
                    msg.date = message.date
                    return msg
                }

                let groupMsg: GroupBallotCreateMessage = BallotMessageEncoder.groupBallotCreateMessage(
                    from: msg,
                    groupID: groupID,
                    groupCreatorIdentity: groupCreatorIdentity
                )
                groupMsg.fromIdentity = fromIdentity
                groupMsg.toIdentity = toIdentity
                groupMsg.date = message.date
                return groupMsg
            }
            else if let message = message as? ImageMessageEntity {
                let msg: AbstractMessage = task.isGroupMessage ? GroupImageMessage() : BoxImageMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.messageID = message.id
                msg.date = message.date
                if task.isGroupMessage,
                   let msg = msg as? GroupImageMessage {
                    msg.blobID = message.imageBlobID
                    msg.encryptionKey = message.encryptionKey
                    msg.size = UInt32(exactly: message.imageSize!)!
                    
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                }
                else if let msg = msg as? BoxImageMessage {
                    msg.blobID = message.imageBlobID
                    msg.imageNonce = message.imageNonce
                    msg.size = UInt32(exactly: message.imageSize!)!
                }
                return msg
            }
            else if let message = message as? AudioMessageEntity {
                let msg: AbstractMessage = task.isGroupMessage ? GroupAudioMessage() : BoxAudioMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.messageID = message.id
                msg.date = message.date
                if task.isGroupMessage,
                   let msg = msg as? GroupAudioMessage {
                    msg.audioBlobID = message.audioBlobID
                    msg.encryptionKey = message.encryptionKey
                    msg.audioSize = UInt32(exactly: message.audioSize!)!
                    msg.duration = UInt16(message.duration.floatValue)

                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                }
                else if let msg = msg as? BoxAudioMessage {
                    msg.audioBlobID = message.audioBlobID
                    msg.encryptionKey = message.encryptionKey
                    msg.audioSize = UInt32(exactly: message.audioSize!)!
                    msg.duration = UInt16(message.duration.floatValue)
                }
                return msg
            }
            else if let message = message as? FileMessageEntity {
                if task.isGroupMessage {
                    let msg: GroupFileMessage = FileMessageEncoder.encodeGroupFileMessageEntity(message)
                    msg.fromIdentity = fromIdentity
                    msg.toIdentity = toIdentity
                    msg.messageID = message.id
                    msg.date = message.date
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                    return msg
                }
                else {
                    let msg: BoxFileMessage = FileMessageEncoder.encodeFileMessageEntity(message)
                    msg.fromIdentity = fromIdentity
                    msg.toIdentity = toIdentity
                    msg.messageID = message.id
                    msg.date = message.date
                    return msg
                }
            }
            else if let message = message as? VideoMessageEntity {
                let msg: AbstractMessage = task.isGroupMessage ? GroupVideoMessage() : BoxVideoMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.messageID = message.id
                msg.date = message.date
                if task.isGroupMessage,
                   let msg = msg as? GroupVideoMessage {
                    msg.videoBlobID = message.videoBlobID
                    msg.encryptionKey = message.encryptionKey
                    msg.videoSize = UInt32(exactly: message.videoSize!)!
                    msg.duration = UInt16(message.duration.floatValue)
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                }
                else if let msg = msg as? BoxVideoMessage {
                    msg.videoBlobID = message.videoBlobID
                    msg.encryptionKey = message.encryptionKey
                    msg.videoSize = UInt32(exactly: message.videoSize!)!
                    msg.duration = UInt16(message.duration.floatValue)
                }
                return msg
            }
        }
        else if let task = task as? TaskDefinitionSendBallotVoteMessage,
                let ballot = frameworkInjector.entityManager.entityFetcher
                .ballotEntity(for: task.ballotID) {

            let msg: BoxBallotVoteMessage = BallotMessageEncoder.encodeVoteMessage(forBallot: ballot)
            guard let groupID = task.groupID,
                  let groupCreatorIdentity = task.groupCreatorIdentity,
                  frameworkInjector.groupManager
                  .getConversation(for: GroupIdentity(
                      id: groupID,
                      creator: ThreemaIdentity(groupCreatorIdentity)
                  )) !=
                  nil else {
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                return msg
            }

            let groupMsg: GroupBallotVoteMessage = BallotMessageEncoder.groupBallotVoteMessage(
                from: msg,
                groupID: groupID,
                groupCreatorIdentity: groupCreatorIdentity
            )
            groupMsg.fromIdentity = fromIdentity
            groupMsg.toIdentity = toIdentity
            return groupMsg
        }
        else if let task = task as? TaskDefinitionSendReactionMessage {
            guard let groupID = task.groupID,
                  let groupCreatorIdentity = task.groupCreatorIdentity,
                  frameworkInjector.groupManager
                  .getConversation(for: GroupIdentity(
                      id: groupID,
                      creator: ThreemaIdentity(groupCreatorIdentity)
                  )) !=
                  nil else {
                let msg = ReactionMessage()
                msg.fromIdentity = fromIdentity
                msg.toIdentity = toIdentity
                msg.decoded = task.reaction
                
                return msg
            }

            let groupMsg = GroupReactionMessage()
            groupMsg.fromIdentity = fromIdentity
            groupMsg.toIdentity = toIdentity
            groupMsg.decoded = task.reaction
            groupMsg.groupID = task.groupID
            groupMsg.groupCreator = task.groupCreatorIdentity
            
            return groupMsg
        }
        else if let task = task as? TaskDefinitionSendDeleteEditMessage {
            assert(task.deleteMessage != nil || task.editMessage != nil)

            if let groupID = task.groupID,
               let groupCreatorIdentity = task.groupCreatorIdentity,
               frameworkInjector.groupManager
               .getConversation(for: GroupIdentity(
                   id: groupID,
                   creator: ThreemaIdentity(groupCreatorIdentity)
               )) !=
               nil {

                if let deleteMessage = task.deleteMessage {
                    let msg = DeleteGroupMessage()
                    msg.fromIdentity = frameworkInjector.myIdentityStore.identity
                    msg.toIdentity = toIdentity
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                    msg.decoded = deleteMessage
                    return msg
                }
                else if let editMessage = task.editMessage {
                    let msg = EditGroupMessage()
                    msg.fromIdentity = frameworkInjector.myIdentityStore.identity
                    msg.toIdentity = toIdentity
                    msg.groupID = task.groupID
                    msg.groupCreator = task.groupCreatorIdentity
                    msg.decoded = editMessage
                    return msg
                }
            }
            else if let receiverIdentity = task.receiverIdentity {
                if let deleteMessage = task.deleteMessage {
                    let msg = DeleteMessage()
                    msg.fromIdentity = frameworkInjector.myIdentityStore.identity
                    msg.toIdentity = receiverIdentity
                    msg.decoded = deleteMessage
                    return msg
                }
                else if let editMessage = task.editMessage {
                    let msg = EditMessage()
                    msg.fromIdentity = frameworkInjector.myIdentityStore.identity
                    msg.toIdentity = receiverIdentity
                    msg.decoded = editMessage
                    return msg
                }
            }
        }

        return nil
    }

    /// Create abstract message for delivery receipt.
    ///
    /// - Parameters:
    ///   - fromIdentity: Message sender identity
    ///   - toIdentity: Message receiver identity
    ///   - receiptType: Receipt type
    ///   - receiptMessageIDs: Receipt for message IDs
    /// - Returns: Abstract message
    func getDeliveryReceiptMessage(
        _ fromIdentity: String,
        _ toIdentity: String,
        _ receiptType: ReceiptType,
        _ receiptMessageIDs: [Data]
    ) -> DeliveryReceiptMessage {
        let msg = DeliveryReceiptMessage()
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toIdentity
        msg.receiptType = receiptType
        msg.receiptMessageIDs = receiptMessageIDs
        return msg
    }

    /// Create abstract message for group create.
    ///
    /// - Parameter groupID: ID of the group
    /// - Parameter groupCreator: Creator of the group
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Parameter groupMembers: Array of group member identities
    /// - Returns: Abstract message
    func getGroupCreateMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toIdentity: String,
        _ groupMembers: [String]
    ) -> GroupCreateMessage {
        
        let msg = GroupCreateMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toIdentity
        msg.groupMembers = groupMembers
        return msg
    }
    
    /// Create abstract message for group leave.
    ///
    /// - Parameter groupID: ID of the group
    /// - Parameter groupCreator: Creator of the group
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Returns: Abstract message
    func getGroupLeaveMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toMember: String
    ) -> GroupLeaveMessage {
        let msg = GroupLeaveMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toMember
        return msg
    }
    
    /// Create abstract message for group rename.
    ///
    /// - Parameter groupID: ID of the group
    /// - Parameter groupCreator: Creator of the group
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Parameter name: Name of the group
    /// - Returns: Abstract message
    func getGroupRenameMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toMember: String,
        _ name: String?
    ) -> GroupRenameMessage {
        let msg = GroupRenameMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toMember
        msg.name = name
        return msg
    }
    
    /// Create abstract message for group photo.
    ///
    /// - Parameter groupID: ID of the group
    /// - Parameter groupCreator: Creator of the group
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Parameter size: Image size
    /// - Parameter blobID: Image Blob ID
    /// - Parameter encryptionKey: Encryption key to decrypting blob
    /// - Returns: Abstract message
    func getGroupSetPhotoMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toMember: String,
        _ size: UInt32,
        _ blobID: Data?,
        _ encryptionKey: Data?
    ) -> GroupSetPhotoMessage {
        let msg = GroupSetPhotoMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toMember
        msg.size = size
        msg.blobID = blobID
        msg.encryptionKey = encryptionKey
        return msg
    }
    
    /// Create abstract message for group delete photo.
    ///
    /// - Parameter groupID: ID of the group
    /// - Parameter groupCreatorIdentity: Creator of the group
    /// - Parameter fromIdentity: Message sender identity
    /// - Parameter toIdentity: Message receiver identity
    /// - Returns: Abstract message
    func getGroupDeletePhotoMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toMember: String
    ) -> GroupDeletePhotoMessage {
        let msg = GroupDeletePhotoMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toMember
        return msg
    }
    
    /// Create abstract message for group delivery receipt.
    ///
    /// - Parameters:
    ///   - groupID: ID of the group
    ///   - groupCreatorIdentity: Creator of the group
    ///   - fromIdentity: Message sender identity
    ///   - toIdentity: Message receiver identity
    ///   - receiptType: Receipt type for group can be read (reflect only), ack and decline
    ///   - receiptMessageIDs: Receipt for message IDs
    /// - Returns: Abstract message
    func getGroupDeliveryReceiptMessage(
        _ groupID: Data,
        _ groupCreatorIdentity: String,
        _ fromIdentity: String,
        _ toIdentity: String,
        _ receiptType: ReceiptType,
        _ receiptMessageIDs: [Data]
    ) -> GroupDeliveryReceiptMessage {
        assert(receiptType == .read || receiptType == .ack || receiptType == .decline)

        let msg = GroupDeliveryReceiptMessage()
        msg.groupID = groupID
        msg.groupCreator = groupCreatorIdentity
        msg.fromIdentity = fromIdentity
        msg.toIdentity = toIdentity
        msg.receiptType = receiptType.rawValue
        msg.receiptMessageIDs = receiptMessageIDs
        return msg
    }

    /// Observe task interruption.
    ///
    /// - Parameters:
    ///   - task: Task to observe
    ///   - action: Called when the task is interrupted
    func observeInterruption(of task: TaskDefinition?, action: @escaping () -> Void) -> NSKeyValueObservation? {
        if let task {
            task.observe(\.isInterrupted) { task, _ in
                guard task.isInterrupted else {
                    return
                }
                action()
            }
        }
        else {
            nil
        }
    }
}

Messung
CHZG
85959993

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417349