Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  MessageProcessor.m  Sprache: unbekannt

 
Spracherkennung für: .m vermutete Sprache: Masm {Masm[80] ABAP[75] Apollo[68]} [Methode: maximale Elemente, drei Dimensionen]

#import <CoreData/CoreData.h>
#import "MessageProcessor.h"
#import "MessageDecoder.h"
#import "BoxTextMessage.h"
#import "BoxImageMessage.h"
#import "BoxVideoMessage.h"
#import "BoxLocationMessage.h"
#import "BoxAudioMessage.h"
#import "BoxedMessage.h"
#import "BoxVoIPCallOfferMessage.h"
#import "BoxVoIPCallAnswerMessage.h"
#import "DeliveryReceiptMessage.h"
#import "TypingIndicatorMessage.h"
#import "GroupCreateMessage.h"
#import "GroupLeaveMessage.h"
#import "GroupRenameMessage.h"
#import "GroupTextMessage.h"
#import "GroupLocationMessage.h"
#import "GroupVideoMessage.h"
#import "GroupImageMessage.h"
#import "GroupAudioMessage.h"
#import "GroupSetPhotoMessage.h"
#import "BoxFileMessage.h"
#import "GroupFileMessage.h"
#import "ContactSetPhotoMessage.h"
#import "ContactDeletePhotoMessage.h"
#import "ContactRequestPhotoMessage.h"
#import "GroupDeletePhotoMessage.h"
#import "UnknownTypeMessage.h"
#import "ContactStore.h"
#import "ThreemaUtilityObjC.h"
#import "ProtocolDefines.h"
#import "UserSettings.h"
#import "MyIdentityStore.h"
#import "BallotMessageDecoder.h"
#import "GroupMessageProcessor.h"
#import "ThreemaError.h"
#import "FileMessageDecoder.h"
#import "BoxVoIPCallIceCandidatesMessage.h"
#import "BoxVoIPCallHangupMessage.h"
#import "BoxVoIPCallRingingMessage.h"
#import "NonceHasher.h"
#import "ServerConnector.h"
#import "ThreemaFramework/ThreemaFramework-Swift.h"
#import "NSString+Hex.h"
#import "NaClCrypto.h"

#ifdef DEBUG
  static const DDLogLevel ddLogLevel = DDLogLevelAll;
#else
  static const DDLogLevel ddLogLevel = DDLogLevelNotice;
#endif

@implementation MessageProcessor {
    id<MessageProcessorDelegate> messageProcessorDelegate;
    int maxBytesToDecrypt;
    int timeoutDownloadThumbnail;
    GroupManager *groupManager;
    EntityManager *entityManager;
    ForwardSecurityMessageProcessor *fsmp;
    id<NonceGuardProtocolObjc> nonceGuard;
}

- (instancetype)initWith:(id<MessageProcessorDelegate>)messageProcessorDelegate groupManager:(NSObject *)groupManagerObject entityManager:(NSObject *)entityManagerObject fsmp:(NSObject*)fsmp nonceGuard:(NSObject*)nonceGuardObject {
    NSAssert([groupManagerObject isKindOfClass:[GroupManager class]], @"Object must be type of GroupManager");
    NSAssert([entityManagerObject isKindOfClass:[EntityManager class]], @"Object must be type of EntityManager");
    NSAssert([fsmp isKindOfClass:[ForwardSecurityMessageProcessor class]], @"Object must be type of ForwardSecurityMessageProcessor");
    NSAssert([nonceGuardObject conformsToProtocol:@protocol(NonceGuardProtocolObjc)], @"Object must implement NonceGuardProtocolObjc");

    self = [super init];
    if (self) {
        self->messageProcessorDelegate = messageProcessorDelegate;
        self->maxBytesToDecrypt = 0;
        self->timeoutDownloadThumbnail = 0;
        self->groupManager = (GroupManager*)groupManagerObject;
        self->entityManager = (EntityManager*)entityManagerObject;
        self->fsmp = (ForwardSecurityMessageProcessor*)fsmp;
        self->nonceGuard = (id<NonceGuardProtocolObjc>)nonceGuardObject;
    }
    return self;
}

- (void)processIncomingBoxedMessage:(BoxedMessage*)boxedMessage receivedAfterInitialQueueSend:(BOOL)receivedAfterInitialQueueSend maxBytesToDecrypt:(int)maxBytesToDecrypt timeoutDownloadThumbnail:(int)timeoutDownloadThumbnail onCompletion:(nonnull void (^)(AbstractMessage * _Nullable message, id _Nullable fsMessageInfo))onCompletion onError:(nonnull void (^)(NSError * _Nonnull error, id _Nullable fsMessageInfo))onError {

    self->maxBytesToDecrypt = maxBytesToDecrypt;
    self->timeoutDownloadThumbnail = timeoutDownloadThumbnail;

    [messageProcessorDelegate beforeDecode];
    
    ContactAcquaintanceLevel acquaintanceLevel = boxedMessage.flags & MESSAGE_FLAG_GROUP ? ContactAcquaintanceLevelGroupOrDeleted : ContactAcquaintanceLevelDirect;

    [[ContactStore sharedContactStore] fetchPublicKeyForIdentity:boxedMessage.fromIdentity acquaintanceLevel:acquaintanceLevel entityManager:entityManager ignoreBlockUnknown:false onCompletion:^(NSData *publicKey) {
        NSAssert(!([NSThread isMainThread] == YES), @"Should not running in main thread");

        [entityManager perform:^{
            AbstractMessage *amsg = [MessageDecoder decodeFromBoxed:boxedMessage withPublicKey:publicKey];

            if (amsg == nil) {
                // Can't process message at this time, try it later
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                
                onError([ThreemaError threemaError:[NSString stringWithFormat:@"Bad message format or decryption error (ID: %@)", amsg.messageId] withCode:ThreemaProtocolErrorBadMessage], nil);
                return;
            }

            if ([amsg isKindOfClass: [UnknownTypeMessage class]]) {
                // Can't process message at this time, try it later
                // Note: We don't get here for `UnknownTypeMessage`s encapsulated in an FS message
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                onError([ThreemaError threemaError:[NSString stringWithFormat:@"Unknown message type (ID: %@)", amsg.messageId] withCode:ThreemaProtocolErrorUnknownMessageType], nil);
                return;
            }

            // 19. If inner-type does not have dedicated blocking exemption steps and is not exempted from blocking, run the Identity Blocked Steps
            //     for sender-identity. If the result indicates that sender-identity is blocked, Acknowledge and discard the message and abort these steps.
            if ([self isBlocked:amsg]) {
                DDLogWarn(@"Ignoring message from blocked ID %@ %@", boxedMessage.fromIdentity, boxedMessage.loggingDescription);

                // Do not process message, send server ack
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                onCompletion(nil, nil);
                return;
            }

            // 20. If sender-identity equals *3MAPUSH:
            //      1. If inner-type is not 0xfe, log a warning, Acknowledge and discard the message and abort these steps.
            //
            // Note that the message type 0xfe is only for Android relevant, in that case we don't process any messages from *3MAPUSH.
            if ([PredefinedContactsObjC is3MAPushWithIdentity:boxedMessage.fromIdentity]) {
                DDLogWarn(@"Discard message (type %d) from %@", amsg.type, boxedMessage.fromIdentity);

                // Do not process message, send server ack
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                onCompletion(nil, nil);
                return;
            }

            // 21. If sender-identity equals *3MAW0RK:
            //      1. If inner-type is not 0xfd, log a warning, Acknowledge and discard the message and abort these steps.
            if ([PredefinedContactsObjC is3MAW0rkWithIdentity:boxedMessage.fromIdentity] && [amsg isKindOfClass:[WorkSyncDeltaMessage class]] == NO) {
                DDLogWarn(@"Discard message (type %d) from %@, because is not a WorkSyncDeltaMessage", amsg.type, boxedMessage.fromIdentity);

                // Do not process message, send server ack
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                onCompletion(nil, nil);
                return;
            }

            // Validation logging
            if ([amsg isContentValid] == NO) {
                NSString *errorDescription = @"Ignore invalid content";
                [DebugLog logAbstractMessage:amsg isIncoming:YES errorDescription:errorDescription];

                // Do not process message, send server ack
                [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                onCompletion(nil, nil);
                return;
            } else {
                if ([nonceGuard isProcessedWithMessage:amsg]) {
                    NSString *errorDescription = @"Nonce already in database";
                    [DebugLog logAbstractMessage:amsg isIncoming:YES errorDescription:errorDescription];

                    // Do not process message, send server ack
                    [messageProcessorDelegate incomingMessageFailed:boxedMessage];
                    onCompletion(nil, nil);
                    return;
                } else {
                    [DebugLog logAbstractMessage:amsg isIncoming:YES];
                }
            }

            amsg.receivedAfterInitialQueueSend = receivedAfterInitialQueueSend;

            [self processIncomingAbstractMessage:amsg senderPublicKey:publicKey onCompletion:^(AbstractMessage *processedMsg, id fsMessageInfo) {
                // Message successfully processed
                onCompletion(processedMsg, fsMessageInfo);
            } onError:^(NSError * _Nullable error,  id _Nullable fsMessageInfo) {
                if (error) {
                    onError(error, fsMessageInfo);
                }
                else {
                    onCompletion(nil, nil);
                }
            }];
        }];
    } onError:^(NSError *error) {
        if (error.code == ThreemaProtocolErrorBlockUnknownContact) {
            // Do not process message, remove initial user notification and send server ack
            [messageProcessorDelegate incomingMessageFailed:boxedMessage];
            onCompletion(nil, nil);
            return;
        }
        
        [DebugLog logBoxedMessage:boxedMessage isIncoming:YES errorDescription:@"PublicKey from Threema-ID not found"];
        // Failed to process message, try it later
        onError(error, nil);
    }];
}

- (void)processIncomingAbstractMessage:(AbstractMessage* _Nonnull)amsg senderPublicKey:(NSData* _Nonnull)senderPublicKey onCompletion:(void(^)(AbstractMessage * _Nullable message, id _Nullable fsMessageInfo))onCompletion onError:(void(^)(NSError * _Nullable error, id _Nullable fsMessageInfo))onError {

    if ([amsg isContentValid] == NO) {
        DDLogInfo(@"Ignore invalid content, message %@ from %@", amsg.loggingDescription, amsg.fromIdentity);
        onCompletion(nil, nil);
        return;
    }
    
    if ([nonceGuard isProcessedWithMessage:amsg]) {
        DDLogWarn(@"Nonce of message %@ already processed", amsg.loggingDescription);
        onCompletion(nil, nil);
        return;
    }
    
    // Note: This is an variable holding a block called after the PFS envelope is removed if there is any
    void(^processAbstractMessageBlock)(AbstractMessage *, id) = ^void(AbstractMessage *amsg, id fsMessageInfo) {
        [messageProcessorDelegate incomingMessageStarted:amsg];
        DDLogVerbose(@"Process incoming message: %@", amsg);

        /* Update public nickname in contact, if necessary */
        if (amsg.allowSendingProfile == YES) {
            [[ContactStore sharedContactStore] updateNickname:amsg.fromIdentity nickname:amsg.pushFromName];
        }
        
        // Process Group Messages
        if ([amsg isKindOfClass:[AbstractGroupMessage class]]) {
            [self processIncomingGroupMessage:(AbstractGroupMessage *)amsg onCompletion:^{
                if (![amsg isKindOfClass:[ForwardSecurityEnvelopeMessage class]]) {
                    if (amsg.forwardSecurityMode == kForwardSecurityModeNone) {
                        ForwardSecurityContact *fsContact = [[ForwardSecurityContact alloc] initWithIdentity:amsg.fromIdentity publicKey:senderPublicKey];
                        // Only warns if FS is required for this message
                        [fsmp warnIfMessageWithoutForwardSecurityReceivedFor:amsg from:fsContact];
                    }
                }
                
                [messageProcessorDelegate incomingMessageFinished:amsg];
                onCompletion(amsg, fsMessageInfo);
            } onError:^(NSError *error) {
                [messageProcessorDelegate incomingMessageFinished:amsg];
                onError(error, fsMessageInfo);
            }];
        // Process Non-Group Messages
        } else {
            [self processIncomingMessage:(AbstractMessage *)amsg onCompletion:^(id<MessageProcessorDelegate> _Nullable delegate) {
                if (![amsg isKindOfClass:[ForwardSecurityEnvelopeMessage class]]) {
                    if (amsg.forwardSecurityMode == kForwardSecurityModeNone) {
                        ForwardSecurityContact *fsContact = [[ForwardSecurityContact alloc] initWithIdentity:amsg.fromIdentity publicKey:senderPublicKey];
                        // Only warns if FS is required for this message
                        [fsmp warnIfMessageWithoutForwardSecurityReceivedFor:amsg from:fsContact];
                    }
                }
                
                if (delegate) {
                    [delegate incomingMessageFinished:amsg];
                }
                else {
                    [messageProcessorDelegate incomingMessageFinished:amsg];
                }
                onCompletion(amsg, fsMessageInfo);
            } onError:^(NSError *error, id<MessageProcessorDelegate> _Nullable delegate) {
                if (delegate) {
                    [delegate incomingMessageFinished:amsg];
                }
                else {
                    [messageProcessorDelegate incomingMessageFinished:amsg];
                }
                onError(error, fsMessageInfo);
            }];
        }
    };
    
    @try {
        if ([amsg isKindOfClass:[ForwardSecurityEnvelopeMessage class]]) {
            if ([ThreemaEnvironment supportsForwardSecurity]) {
                [self processIncomingForwardSecurityMessage:(ForwardSecurityEnvelopeMessage*)amsg senderPublicKey:senderPublicKey onCompletion:^(AbstractMessage *unwrappedMessage, id fsMessageInfo) {

                    if (unwrappedMessage != nil) {
                        // Don't allow double encapsulated forward security messages
                        // This is also checked when decoding messages in MessageDecoder+Swift.swift lines 41ff
                        if ([unwrappedMessage isKindOfClass:[ForwardSecurityEnvelopeMessage class]]) {
                            [messageProcessorDelegate incomingAbstractMessageFailed:amsg]; // Remove notification
                            onError(nil, fsMessageInfo);
                        } else {
                            // Happy path for normal messages
                            processAbstractMessageBlock(unwrappedMessage, fsMessageInfo);
                        }
                    } else {
                        // An auxiliary FS message was successfully processed or a message was rejected
                        // Mark contact and conversation as dirty & remove notification
                        [messageProcessorDelegate incomingForwardSecurityMessageWithNoResultFinished:amsg];
                        onError(nil, fsMessageInfo); // Drop message
                    }
                } onError:^(NSError *error) {
                    if ([error.userInfo objectForKey:@"ShouldRetry"]) {
                        [messageProcessorDelegate incomingMessageFinished:amsg];
                        onError(error, nil);
                    } else {
                        [messageProcessorDelegate incomingAbstractMessageFailed:amsg]; // Remove notification
                        onError(nil, nil); // drop message
                    }
                }];
            } else {
                // No FS support - reject message
                ForwardSecurityContact *fsContact = [[ForwardSecurityContact alloc] initWithIdentity:amsg.fromIdentity publicKey:senderPublicKey];
                [fsmp rejectEnvelopeMessageWithSender:fsContact envelopeMessage:(ForwardSecurityEnvelopeMessage*)amsg];
                [messageProcessorDelegate incomingAbstractMessageFailed:amsg]; // Remove notification
                onError(nil, nil); // drop message
            }
        } else  {
            processAbstractMessageBlock(amsg, nil);
        }
    } @catch (NSException *exception) {
        NSError *error = [ThreemaError threemaError:exception.description withCode:ThreemaProtocolErrorMessageProcessingFailed];
        onError(error, nil);
    } @catch (NSError *error) {
        onError(error, nil);
    }
}

/**
Process incoming message.

@param amsg Incoming abstract message
@param onCompletion Completion handler with parameter of type `MessageProcessorDelegate`
@param onError Error handler with parameter of type `NSError` and `MessageProcessorDelegate`
*/
- (void)processIncomingMessage:(AbstractMessage*)amsg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable delegate))onCompletion onError:(void(^ _Nonnull)(NSError *, id<MessageProcessorDelegate> _Nullable))onError {

    ContactEntity *sender;
    ContactEntity *receiver;
    __block ConversationEntity *conversation;

    // Get and validate sender, receiver and conversation for the incoming message, if is not a special contact
    if (![PredefinedContactsObjC isSpecialContactWithIdentity:amsg.fromIdentity]) {
        conversation = [entityManager existingConversationSenderReceiverFor:amsg sender:&sender ;receiver:&receiver myIdentity:[[MyIdentityStore sharedMyIdentityStore] identity]];

        if (sender == nil) {
            onError([ThreemaError threemaError:@"Sender not found as contact"], nil);
            return;
        }

        if (conversation == nil) {
            [entityManager performAndWaitSave:^{
                conversation = [entityManager conversationForContact:sender createIfNotExisting:[amsg canCreateConversation]];
            }];
        }

        // Set the contact to active if we received a message
        // Automatic sync is only once a day
        [entityManager performAndWait:^{
            if (sender.contactState == ContactStateInactive) {
                [[ContactStore sharedContactStore] updateStateToActiveFor:sender entityManager:entityManager];
            }
        }];

        if ([amsg needsConversation] && conversation == nil) {
            onCompletion(nil);
            return;
        }
    }

    if ([amsg isKindOfClass:[BoxTextMessage class]]) {
        [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxImageMessage class]]) {
        [self processIncomingImageMessage:(BoxImageMessage *)amsg sender:sender conversation:conversation onCompletion:^{
            onCompletion(nil);
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxVideoMessage class]]) {
        [self processIncomingVideoMessage:(BoxVideoMessage*)amsg sender:sender conversation:conversation onCompletion:^{
            onCompletion(nil);
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxLocationMessage class]]) {
        [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
            LocationMessageEntity *locationMessage = (LocationMessageEntity *)message;
            [self finalizeMessage:locationMessage inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                [self resolveAddressFor:locationMessage onCompletion:^{
                    onCompletion(nil);
                }];
            }];
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxAudioMessage class]]) {
        [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[DeliveryReceiptMessage class]]) {
        [self processIncomingDeliveryReceipt:(DeliveryReceiptMessage*)amsg onCompletion:^{
            onCompletion(nil);
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[TypingIndicatorMessage class]]) {
        [self processIncomingTypingIndicator:(TypingIndicatorMessage*)amsg];
        onCompletion(nil);
    } else if ([amsg isKindOfClass:[BoxBallotCreateMessage class]]) {
        BallotMessageDecoder *decoder = [[BallotMessageDecoder alloc] initWith:entityManager];
        [decoder decodeCreateBallotFromBox:(BoxBallotCreateMessage *)amsg sender:sender conversation:conversation onCompletion:^(NSObject *messageObject) {
            NSAssert(messageObject == nil || [messageObject isKindOfClass:[BallotMessageEntity class]], @"Parameter messageObject must be type of BallotMessageEntity");
            BallotMessageEntity *message = (BallotMessageEntity*)messageObject;

            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxBallotVoteMessage class]]) {
        // TODO: This could be generate duplicate system messages, if message will processed multiple (race condition)
        [self processIncomingBallotVoteMessage:(BoxBallotVoteMessage*)amsg onCompletion:^{
            onCompletion(nil);
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[BoxFileMessage class]]) {
        [FileMessageDecoder decodeMessageFromBox:(BoxFileMessage *)amsg sender:sender conversation:conversation isReflectedMessage:NO timeoutDownloadThumbnail:timeoutDownloadThumbnail entityManager:entityManager onCompletion:^(NSObject *messageObject) {
            NSAssert(messageObject == nil || [messageObject isKindOfClass:[BaseMessageEntity class]], @"Parameter messageObject must be type of BaseMessageEntity");
            BaseMessageEntity *message = (BaseMessageEntity*)messageObject;

            // Do not download blob when message will processed via Notification Extension,
            // to keep notifications fast and because option automatically save to photos gallery
            // doesn't work from Notification Extension
            if ([AppGroup getCurrentType] != AppGroupTypeNotificationExtension) {
                [self conditionallyStartLoadingFileFromMessage:(FileMessageEntity *)message];
            }
            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[ContactSetPhotoMessage class]]) {
        [self processIncomingContactSetPhotoMessage:(ContactSetPhotoMessage *)amsg onCompletion:^{
            onCompletion(nil);
        } onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
    } else if ([amsg isKindOfClass:[ContactDeletePhotoMessage class]]) {
        [self processIncomingContactDeletePhotoMessage:(ContactDeletePhotoMessage *)amsg onCompletion:^{
            onCompletion(nil);
        }];
    } else if ([amsg isKindOfClass:[ContactRequestPhotoMessage class]]) {
        [self processIncomingContactRequestPhotoMessage:(ContactRequestPhotoMessage *)amsg onCompletion:^{
            onCompletion(nil);
        }];
    } else if ([amsg isKindOfClass:[BoxVoIPCallOfferMessage class]]) {
        [self processIncomingVoIPCallOfferMessage:(BoxVoIPCallOfferMessage *)amsg onCompletion:onCompletion onError:onError];
    } else if ([amsg isKindOfClass:[BoxVoIPCallAnswerMessage class]]) {
        [self processIncomingVoIPCallAnswerMessage:(BoxVoIPCallAnswerMessage *)amsg onCompletion:onCompletion onError:onError];
    } else if ([amsg isKindOfClass:[BoxVoIPCallIceCandidatesMessage class]]) {
        [self processIncomingVoIPCallIceCandidatesMessage:(BoxVoIPCallIceCandidatesMessage *)amsg onCompletion:onCompletion onError:onError];
    } else if ([amsg isKindOfClass:[BoxVoIPCallHangupMessage class]]) {
        [self processIncomingVoIPCallHangupMessage:(BoxVoIPCallHangupMessage *)amsg onCompletion:onCompletion onError:onError];
    } else if ([amsg isKindOfClass:[BoxVoIPCallRingingMessage class]]) {
        [self processIncomingVoipCallRingingMessage:(BoxVoIPCallRingingMessage *)amsg onCompletion:onCompletion onError:onError];
    } else if ([amsg isKindOfClass:[BoxEmptyMessage class]]) {
        // Noting todo. Just move on...
        // Android basically handles it the same way as an `UnknownTypeMessage`. On iOS this is too complicated for now.
        onCompletion(nil);
    } else if ([amsg isKindOfClass:[UnknownTypeMessage class]]) {
        onError([ThreemaError threemaError:[NSString stringWithFormat:@"Unknown message type (ID: %@)", amsg.messageId] withCode:ThreemaProtocolErrorUnknownMessageType], nil);
    } else if ([amsg isKindOfClass:[DeleteMessage class]]) {
        // Get related objectID's to mark as dirty objects
        NSSet<NSManagedObjectID *> *relatedObjectIDs = [entityManager relatedObjectIDsForAbstractDeleteMessage:(DeleteMessage *)amsg in:conversation];

        // On error case `onError` will be called and message is `nil`
        BaseMessageEntity *message = [entityManager deleteMessageFor:amsg conversation:conversation myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
        if (message) {
            for (NSManagedObjectID *objectID in relatedObjectIDs) {
                [messageProcessorDelegate changedManagedObjectID:objectID];
            }

            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        }
    } else if ([amsg isKindOfClass:[EditMessage class]]) {
        // On error case `onError` will be called and message is `nil`
        BaseMessageEntity *message = [entityManager editMessageFor:amsg conversation:conversation myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onError:^(NSError * _Nonnull error) {
            onError(error, nil);
        }];
        if (message) {
            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        }
    } else if ([amsg isKindOfClass:[ReactionMessage class]]) {
        ReactionMessage *newMsg = (ReactionMessage *) amsg;
        ReactionsMessageProcessor *manager = [[ReactionsMessageProcessor alloc]initWithEntityManager:entityManager messageProcessorDelegate:messageProcessorDelegate];
        BaseMessageEntity *message = [manager handleMessageWithAbstractMessage: newMsg conversation:conversation];

        if (message) {
            [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                onCompletion(nil);
            }];
        }
        else {
            onCompletion(nil);
        }
    } else if ([amsg isKindOfClass:[WebSessionResumeMessage class]]) {
        if ([PredefinedContactsObjC is3MAPushWithIdentity:amsg.fromIdentity] == NO) {
            DDLogError(@"[MessageProcessor] Received web session message from wrong identity: %@", amsg.fromIdentity);
            onError(nil, nil);
            return;
        }
        onCompletion(nil);
    } else if ([amsg isKindOfClass:[WorkSyncDeltaMessage class]]) {
        // 1. If not Work flavour, log a warning, discard the message and abort these steps.
        if (TargetManagerObjC.isWork == false) {
            DDLogError(@"[MessageProcessor] Received work sync delta message but we are not a work app.");
            onError(nil, nil);
            return;
        }
        
        // 2. If the sender is not `*3MAW0RK`, discard the message and abort these steps.
        if ([PredefinedContactsObjC is3MAW0rkWithIdentity:amsg.fromIdentity] == NO) {
            DDLogError(@"[MessageProcessor] Received work sync delta message from wrong identity: %@", amsg.fromIdentity);
            onError(nil, nil);
            return;
        }

        WorkSyncDeltaMessageProcessor *workSyncDeltaMessageProcessor = [[WorkSyncDeltaMessageProcessor alloc] initWithAppGroupType:[AppGroup getActiveType] entityManager:self->entityManager messageProcessorDelegate:self->messageProcessorDelegate];
        [workSyncDeltaMessageProcessor handleWithMessage:(WorkSyncDeltaMessage*)amsg completionHandler:^(NSError * _Nullable error) {
            if (error) {
                onError(error, nil);
                return;
            }

            onCompletion(nil);
        }];
    } else {
        // Do not Ack message, try process this message later because of protocol changes
        onError([ThreemaError threemaError:@"Invalid message class"], nil);
    }
}

- (void)processIncomingGroupMessage:(AbstractGroupMessage * _Nonnull)amsg onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {
    
    GroupMessageProcessor *groupProcessor = [[GroupMessageProcessor alloc] initWithMessage:amsg myIdentityStore:[MyIdentityStore sharedMyIdentityStore] userSettings:[UserSettings sharedUserSettings] groupManager:groupManager entityManager:entityManager nonceGuard:(NSObject*)nonceGuard];
    [groupProcessor handleMessageOnCompletion:^(BOOL didHandleMessage) {
        if (didHandleMessage) {
            onCompletion();
            return;
        }

        // messages not handled by GroupProcessor, e.g. messages that can be processed after delayed group create
        ContactEntity *sender;
        ContactEntity *receiver;
        ConversationEntity *conversation = [entityManager existingConversationSenderReceiverFor:amsg sender:&sender receiver:&receiver myIdentity:[[MyIdentityStore sharedMyIdentityStore] identity]];

        if (sender == nil) {
            onError([ThreemaError threemaError:@"Sender not found as contact"]);
            return;
        }

        // Conversation must be there at this time, should be created with creation of the group
        if (conversation == nil) {
            onCompletion();
            return;
        }

        // Set the contact to active if we received a message
        // Automatic sync is only once a day
        [entityManager performAndWait:^{
            if (sender.contactState == ContactStateInactive) {
                [[ContactStore sharedContactStore] updateStateToActiveFor:sender entityManager:entityManager];
            }

            DDLogNotice(@"Received group message %@ with %ld members",
                        amsg.loggingDescription,
                        conversation.members != nil ? conversation.members.count : 0);
        }];

        if ([amsg isKindOfClass:[GroupRenameMessage class]]) {
            [groupManager setNameObjcWithGroupID:amsg.groupId creator:amsg.groupCreator name:((GroupRenameMessage *)amsg).name systemMessageDate:amsg.date send:YES completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    [self changedConversationAndGroupEntityWithGroupID:amsg.groupId groupCreatorIdentity:amsg.groupCreator];
                    onCompletion();
                }
                else {
                    onError(error);
                }
            }];
        } else if ([amsg isKindOfClass:[GroupSetPhotoMessage class]]) {
            [self processIncomingGroupSetPhotoMessage:(GroupSetPhotoMessage*)amsg onCompletion:onCompletion onError:onError];
        } else if ([amsg isKindOfClass:[GroupDeletePhotoMessage class]]) {
            [groupManager deletePhotoObjcWithGroupID:amsg.groupId creator:amsg.groupCreator sentDate:[amsg date] send:NO completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    [self changedConversationAndGroupEntityWithGroupID:amsg.groupId groupCreatorIdentity:amsg.groupCreator];
                    onCompletion();
                }
                else {
                    onError(error);
                }
            }];
        } else if ([amsg isKindOfClass:[GroupTextMessage class]]) {
            [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            } onError:onError];
        } else if ([amsg isKindOfClass:[GroupLocationMessage class]]) {
            [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:^{
                    [self resolveAddressFor:(LocationMessageEntity*)message onCompletion:onCompletion];
                }];
            } onError:onError];
        } else if ([amsg isKindOfClass:[GroupImageMessage class]]) {
            [self processIncomingImageMessage:(GroupImageMessage *)amsg sender:sender conversation:conversation onCompletion:onCompletion onError:onError];
        } else if ([amsg isKindOfClass:[GroupVideoMessage class]]) {
            [self processIncomingVideoMessage:(GroupVideoMessage*)amsg sender:sender conversation:conversation onCompletion:onCompletion onError:onError];
        } else if ([amsg isKindOfClass:[GroupAudioMessage class]]) {
            [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            } onError:onError];
        } else if ([amsg isKindOfClass:[GroupBallotCreateMessage class]]) {
            BallotMessageDecoder *decoder = [[BallotMessageDecoder alloc] initWith:entityManager];
            [decoder decodeCreateBallotFromGroupBox:(GroupBallotCreateMessage *)amsg sender:sender conversation:conversation onCompletion:^(NSObject *messageObject) {
                NSAssert(messageObject == nil || [messageObject isKindOfClass:[BallotMessageEntity class]], @"Parameter messageObject must be type of BallotMessageEntity");
                BallotMessageEntity *message = (BallotMessageEntity*)messageObject;

                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            } onError:^(NSError *error) {
                onError(error);
            }];
        } else if ([amsg isKindOfClass:[GroupBallotVoteMessage class]]) {
            // TODO: This could be generate duplicate system messages, if message will processed multiple (race condition)
            [self processIncomingGroupBallotVoteMessage:(GroupBallotVoteMessage*)amsg onCompletion:onCompletion onError:onError];
        } else if ([amsg isKindOfClass:[GroupFileMessage class]]) {
            [FileMessageDecoder decodeGroupMessageFromBox:(GroupFileMessage *)amsg sender:sender conversation:conversation isReflectedMessage:NO timeoutDownloadThumbnail:timeoutDownloadThumbnail entityManager:entityManager onCompletion:^(NSObject *messageObject) {
                NSAssert(messageObject == nil || [messageObject isKindOfClass:[BaseMessageEntity class]], @"Parameter messageObject must be type of BaseMessageEntity");
                BaseMessageEntity *message = (BaseMessageEntity*)messageObject;

                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            } onError:^(NSError *error) {
                onError(error);
            }];
        } else if ([amsg isKindOfClass:[GroupDeliveryReceiptMessage class]]) {
            [self processIncomingGroupDeliveryReceipt:(GroupDeliveryReceiptMessage*)amsg onCompletion:onCompletion onError:onError];
        } else if ([amsg isKindOfClass:[GroupCallStartMessage class]]) {
            GroupCallStartMessage *newMsg = (GroupCallStartMessage *) amsg;
            [[GlobalGroupCallManagerSingleton shared] handleMessageWithRawMessage:newMsg.decodedObj from:newMsg.fromIdentity in:conversation.objectID receiveDate:newMsg.date completionHandler:^(BOOL callMightBeRunning){
                // Handling the message peeks the SFU. Remember the verdict, it is used to suppress the user
                // notification for a call that already ended.
                newMsg.callEnded = !callMightBeRunning;

                dispatch_async(dispatch_get_main_queue(), ^{
                    DDLogNotice(@"[GroupCall] [DB] Completion handler called");
                    onCompletion();
                });
            }];
        } else if ([amsg isKindOfClass:[DeleteGroupMessage class]]) {
            // Get related objectID's to mark as dirty objects
            NSSet<NSManagedObjectID *> *relatedObjectIDs = [entityManager relatedObjectIDsForAbstractDeleteMessage:(DeleteGroupMessage *)amsg in:conversation];

            // On error case `onError` will be called and message is `nil`
            BaseMessageEntity *message = [entityManager deleteMessageFor:amsg conversation:conversation myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onError:onError];
            if (message) {
                for (NSManagedObjectID *objectID in relatedObjectIDs) {
                    [messageProcessorDelegate changedManagedObjectID:objectID];
                }

                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            }
        } else if ([amsg isKindOfClass:[EditGroupMessage class]]) {
            // On error case `onError` will be called and message is `nil`
            BaseMessageEntity *message = [entityManager editMessageFor:amsg conversation:conversation myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onError:onError];
            if (message) {
                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            }
        } else if ([amsg isKindOfClass:[GroupReactionMessage class]]) {
            GroupReactionMessage *newMsg = (GroupReactionMessage *) amsg;
            ReactionsMessageProcessor *manager = [[ReactionsMessageProcessor alloc]initWithEntityManager:entityManager messageProcessorDelegate:messageProcessorDelegate];
            BaseMessageEntity *message = [manager handleGroupMessageWithAbstractMessage:newMsg conversation:conversation];
            if (message) {
                [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
            } else {
                onCompletion();
            }
        } else {
            onError([ThreemaError threemaError:@"Invalid message class"]);
        }
    } onError:^(NSError *error) {
        onError(error);
    }];
}

- (void)processIncomingForwardSecurityMessage:(ForwardSecurityEnvelopeMessage * _Nonnull)amsg senderPublicKey:(NSData*)senderPublicKey onCompletion:(void(^ _Nonnull)(AbstractMessage *unwrappedMessage, id fsMessageInfo))onCompletion onError:(void(^ _Nonnull)(NSError * error))onError {
    ForwardSecurityContact *fsContact = [[ForwardSecurityContact alloc] initWithIdentity:amsg.fromIdentity publicKey:senderPublicKey];

    [fsmp processEnvelopeMessageObjCWithSender:fsContact envelopeMessage:amsg completionHandler:^(AbstractMessageAndFSMessageInfo * _Nullable unwrappedMessageAndFSMessageInfo, NSError * _Nullable error) {
        
        if (error != nil) {
            DDLogError(@"Processing forward security message failed: %@", error);
            onError(error);
            return;
        }
        
        onCompletion(unwrappedMessageAndFSMessageInfo.message, unwrappedMessageAndFSMessageInfo.fsMessageInfo);
    }];
}

- (void)finalizeMessage:(BaseMessageEntity*)message inConversation:(ConversationEntity*)conversation fromBoxMessage:(AbstractMessage*)boxMessage onCompletion:(void(^_Nonnull)(void))onCompletion {
    [messageProcessorDelegate incomingMessageChanged:boxMessage baseMessageEntity:message];
    onCompletion();
}

- (void)conditionallyStartLoadingFileFromMessage:(FileMessageEntity*)message {
    BlobManagerObjCWrapper *manager = [[BlobManagerObjCWrapper alloc] init];
    [manager autoSyncBlobsFor:message.objectID];
}

- (void)processIncomingImageMessage:(nonnull AbstractMessage *)amsg sender:(nonnull ContactEntity *)sender conversation:(nonnull ConversationEntity *)conversation onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {
    
    NSAssert([amsg isKindOfClass:[BoxImageMessage class]] || [amsg isKindOfClass:[GroupImageMessage class]], @"Abstract message type should be BoxImageMessage or GroupImageMessage");
    
    if ([amsg isKindOfClass:[BoxImageMessage class]] == NO && [amsg isKindOfClass:[GroupImageMessage class]] == NO) {
        onError([ThreemaError threemaError:@"Wrong message type, must be BoxImageMessage or GroupImageMessage"]);
        return;
    }

    BOOL isGroupMessage = [amsg isKindOfClass:[GroupImageMessage class]];
    [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:nil myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
        ImageMessageEntity *imageMessageEntity = (ImageMessageEntity*)message;

        [messageProcessorDelegate incomingMessageChanged:amsg baseMessageEntity:imageMessageEntity];

        dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        // An ImageMessage never has a local blob because all note group cabable devices send everything as FileMessage
        BlobURL *blobUrl = [[BlobURL alloc] initWithServerConnector:[ServerConnector sharedServerConnector] userSettings:[UserSettings sharedUserSettings] queue:downloadQueue];
        BlobDownloader *blobDownloader = [[BlobDownloader alloc] initWithBlobURL:blobUrl queue:downloadQueue];
        ImageMessageProcessor *processor = [[ImageMessageProcessor alloc] initWithBlobDownloader:blobDownloader myIdentityStore:[MyIdentityStore sharedMyIdentityStore] userSettings:[UserSettings sharedUserSettings] entityManager:entityManager];
        [processor downloadImageWithImageMessageID:imageMessageEntity.id in:conversation.objectID imageBlobID:imageMessageEntity.imageBlobId origin:BlobOriginPublic imageBlobEncryptionKey:imageMessageEntity.encryptionKey imageBlobNonce:imageMessageEntity.imageNonce senderPublicKey:sender.publicKey maxBytesToDecrypt:self->maxBytesToDecrypt timeoutDownloadThumbnail:timeoutDownloadThumbnail completion:^(NSError *error) {

            if (error != nil) {
                DDLogError(@"Could not process image message %@", error);
            }

            [self finalizeMessage:imageMessageEntity inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
        }];
    } onError:onError];
}

- (void)processIncomingVideoMessage:(nonnull AbstractMessage *)amsg sender:(nonnull ContactEntity *)sender conversation:(nonnull ConversationEntity *)conversation onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {
    
    NSAssert([amsg isKindOfClass:[BoxVideoMessage class]] || [amsg isKindOfClass:[GroupVideoMessage class]], @"Abstract message type should be BoxVideoMessage or GroupVideoMessage");
    
    if ([amsg isKindOfClass:[BoxVideoMessage class]] == NO && [amsg isKindOfClass:[GroupVideoMessage class]] == NO) {
        onError([ThreemaError threemaError:@"Wrong message type, must be BoxVideoMessage or GroupVideoMessage"]);
        return;
    }

    [entityManager getOrCreateMessageFor:amsg sender:sender conversation:conversation thumbnail:[UIImage imageNamed:@"threema.video.fill"] myIdentity: [MyIdentityStore.sharedMyIdentityStore identity] onCompletion:^(BaseMessageEntity *message) {
        VideoMessageEntity *videoMessageEntity = (VideoMessageEntity *)message;

        [messageProcessorDelegate incomingMessageChanged:amsg baseMessageEntity:videoMessageEntity];

        BOOL isGroupMessage = [amsg isKindOfClass:[GroupVideoMessage class]];

        NSData *thumbnailBlobId;
        if (isGroupMessage == NO) {
            BoxVideoMessage *videoMessage = (BoxVideoMessage *)amsg;
            thumbnailBlobId = [videoMessage thumbnailBlobId];
        }
        else {
            GroupVideoMessage *videoMessage = (GroupVideoMessage *)amsg;
            thumbnailBlobId = [videoMessage thumbnailBlobId];
        }

        dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        // A VideoMessage never has a local blob because all note group capable devices send everything as FileMessage
        BlobURL *blobUrl = [[BlobURL alloc] initWithServerConnector:[ServerConnector sharedServerConnector] userSettings:[UserSettings sharedUserSettings] queue:downloadQueue];
        BlobDownloader *blobDownloader = [[BlobDownloader alloc] initWithBlobURL:blobUrl queue:downloadQueue];
        VideoMessageProcessor *processor = [[VideoMessageProcessor alloc] initWithBlobDownloader:blobDownloader userSettings:[UserSettings sharedUserSettings] entityManager:entityManager];
        [processor downloadVideoThumbnailWithVideoMessageID:videoMessageEntity.id in:conversation.objectID thumbnailBlobID:thumbnailBlobId origin:BlobOriginPublic maxBytesToDecrypt:self->maxBytesToDecrypt timeoutDownloadThumbnail:self->timeoutDownloadThumbnail completion:^(NSError *error) {

            if (error != nil) {
                DDLogError(@"Error while downloading video thumbnail: %@", error);
            }

            [self finalizeMessage:videoMessageEntity inConversation:conversation fromBoxMessage:amsg onCompletion:onCompletion];
        }];
    } onError:onError];
}

- (void)resolveAddressFor:(LocationMessageEntity*)message onCompletion:(void(^ _Nonnull)(void))onCompletion {
    __block BOOL hasExistingPOIAddress = false;
    
    [entityManager performAndWait:^{
        hasExistingPOIAddress = (message.poiAddress != nil);
    }];
    
    // Reverse geocoding (only necessary if there is no POI address)
    if (hasExistingPOIAddress) {
        onCompletion();
        return;
    }

    // It should not result in a different address if we initialize the location with accuracies or not
    __block CLLocation *location = [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(message.latitude.doubleValue, message.longitude.doubleValue) altitude:0 horizontalAccuracy:message.accuracy.doubleValue verticalAccuracy:-1 timestamp:[NSDate date]];

    [ThreemaUtility fetchAddressObjcFor:location completionHandler:^(NSString * _Nonnull address) {
        [entityManager performSave:^{
            if (![message wasDeleted]) {
                message.poiAddress = address;
            }
            
            onCompletion();
        }];
    }];
}

- (void)processIncomingDeliveryReceipt:(DeliveryReceiptMessage*)msg onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {
    [entityManager performSave:^{
        ConversationEntity *conversation = [[entityManager entityFetcher] conversationEntityFor:[msg fromIdentity]];

        for (NSData *receiptMessageId in msg.receiptMessageIds) {
            if (conversation == nil) {
                DDLogWarn(@"Cannot find conversation for message ID %@ (delivery receipt from %@)", receiptMessageId, msg.fromIdentity);
                continue;
            }

            /* Fetch message from DB */
            BaseMessageEntity *dbmsg = [entityManager.entityFetcher ownMessageWith: receiptMessageId in:conversation];
            if (dbmsg == nil) {
                /* This can happen if the user deletes the message before the receipt comes in */
                DDLogError(@"Cannot find message ID %@ (delivery receipt from %@)", receiptMessageId, msg.fromIdentity);
                continue;
            }
            
            if (msg.receiptType == ReceiptTypeReceived) {
                DDLogNotice(@"Message ID %@ has been received by recipient", [NSString stringWithHexData:receiptMessageId]);
                dbmsg.deliveryDate = msg.date;
                dbmsg.delivered = [NSNumber numberWithBool:YES];
            } else if (msg.receiptType == ReceiptTypeRead) {
                DDLogNotice(@"Message ID %@ has been read by recipient", [NSString stringWithHexData:receiptMessageId]);
                if (!dbmsg.delivered)
                    dbmsg.delivered = [NSNumber numberWithBool:YES];
                dbmsg.readDate = msg.date;
                dbmsg.read = [NSNumber numberWithBool:YES];
            } else if (msg.receiptType == ReceiptTypeAck || msg.receiptType == ReceiptTypeDecline) {
                ReactionsMessageProcessor * reactionProcessor = [[ReactionsMessageProcessor alloc]initWithEntityManager:entityManager messageProcessorDelegate:messageProcessorDelegate];
                NSError *error = nil;
                [reactionProcessor handleLegacyReactionWithAck:msg.receiptType == ReceiptTypeAck date:msg.date messageID:dbmsg.objectID sender:msg.fromIdentity error: &error];
                if(error) {
                    onError(error);
                    return;
                }
            } else {
                DDLogWarn(@"Unknown delivery receipt type %d with message ID %@", msg.receiptType, [NSString stringWithHexData:receiptMessageId]);
            }

            [messageProcessorDelegate changedManagedObjectID:dbmsg.objectID];
        }
        
        onCompletion();
    }];
}

- (void)processIncomingGroupDeliveryReceipt:(GroupDeliveryReceiptMessage*)msg onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {
    [entityManager performSave:^{
        ConversationEntity *conversation = [entityManager.entityFetcher groupConversationEntityFor:msg.groupId creatorID:msg.groupCreator myIdentity:[[MyIdentityStore sharedMyIdentityStore] identity]];

        for (NSData *receiptMessageId in msg.receiptMessageIds) {
            if (conversation == nil) {
                DDLogWarn(@"Cannot find conversation for message ID %@ (group delivery receipt from %@)", receiptMessageId, msg.fromIdentity);
                continue;
            }

            /* Fetch message from DB */
            BaseMessageEntity *dbmsg = [entityManager.entityFetcher messageObjCWith:receiptMessageId in:conversation];
            if (dbmsg == nil) {
                /* This can happen if the user deletes the message before the receipt comes in */
                DDLogWarn(@"Cannot find message ID %@ (delivery receipt from %@)", receiptMessageId, msg.fromIdentity);
                continue;
            }
            
            if (msg.receiptType == ReceiptTypeAck || msg.receiptType == ReceiptTypeDecline) {
                ReactionsMessageProcessor * reactionProcessor = [[ReactionsMessageProcessor alloc] initWithEntityManager:entityManager messageProcessorDelegate:messageProcessorDelegate];
                NSError *error = nil;
                [reactionProcessor handleLegacyReactionWithAck:msg.receiptType == ReceiptTypeAck date:msg.date messageID:dbmsg.objectID sender:msg.fromIdentity error: &error];
                
                if(error) {
                    onError(error);
                    return;
                }
            }
            else {
                DDLogWarn(@"Unknown group delivery receipt type %d with message ID %@", msg.receiptType, [NSString stringWithHexData:receiptMessageId]);
            }
            
            [messageProcessorDelegate changedManagedObjectID:dbmsg.objectID];
        }
        
        onCompletion();
    }];
}

- (void)processIncomingTypingIndicator:(TypingIndicatorMessage*)msg {
    [messageProcessorDelegate processTypingIndicator:msg]; 
}

- (void)processIncomingGroupSetPhotoMessage:(GroupSetPhotoMessage*)msg onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
    
    Group *group = [groupManager getGroup:msg.groupId creator:msg.groupCreator];
    if (group == nil) {
        DDLogInfo(@"Group ID %@ from %@ not found", msg.groupId, msg.groupCreator);
        onCompletion();
        return;
    }

    // Download profile picture blob
    [self syncLoadBlobID:msg.blobId encryptionKey:msg.encryptionKey origin:BlobOriginPublic onCompletion:^(NSData * _Nonnull imageData) {
        [groupManager setPhotoObjcWithGroupID:msg.groupId creator:msg.groupCreator imageData:imageData sentDate:msg.date send:NO completionHandler:^(NSError * _Nullable error) {
            if (error == nil) {
                [self changedConversationAndGroupEntityWithGroupID:msg.groupId groupCreatorIdentity:msg.groupCreator];
                onCompletion();
            }
            else {
                onError(error);
            }
        }];
    } onError:^(NSError *error) {
        if (error.code == 404) {
            onCompletion();
        }
        else {
            onError(error);
        }
    }];
}

- (void)processIncomingGroupBallotVoteMessage:(GroupBallotVoteMessage*)msg onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
    // TODO: (IOS-4254) Remove once resolved
    DDLogNotice(@"[Ballot] Start Processing GroupBallotVoteMessage in ballot with ID: %@.", [NSString stringWithHexData:msg.ballotId]);
    /* Create Message in DB */
    BallotMessageDecoder *decoder = [[BallotMessageDecoder alloc] initWith:entityManager];
    
    [entityManager performSave:^{
        if ([decoder decodeVoteFromGroupBox: msg] == NO) {
            onError([ThreemaError threemaError:[NSString stringWithFormat: @"[Ballot] Error processing ballot vote in ballot with ID: %@.", [NSString stringWithHexData:msg.ballotId]]]);
            return;
        }

        [self changedBallotWithID:msg.ballotId];

        onCompletion();
    }];
}

- (void)processIncomingBallotVoteMessage:(BoxBallotVoteMessage*)msg onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError * _Nonnull))onError {
    // TODO: (IOS-4254) Remove once resolved
    DDLogInfo(@"[Ballot] Start Processing BoxBallotVoteMessage in ballot with ID %@.", [NSString stringWithHexData:msg.ballotId]);
    /* Create Message in DB */
    BallotMessageDecoder *decoder = [[BallotMessageDecoder alloc] initWith:entityManager];
    [entityManager performSave:^{
        
        if ([decoder decodeVoteFromBox: msg] == NO) {
            onError([ThreemaError threemaError:[NSString stringWithFormat: @"[Ballot] Error parsing json for ballot vote in ballot with ID: %@.", [NSString stringWithHexData:msg.ballotId]]]);
            return;
        }
                
        [self changedBallotWithID:msg.ballotId];
        
        onCompletion();
    }];
}

- (void)processIncomingContactSetPhotoMessage:(ContactSetPhotoMessage *)msg onCompletion:(void(^ _Nonnull)(void))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull))onError {

    // Download profile picture blob
    [self syncLoadBlobID:msg.blobId encryptionKey:msg.encryptionKey origin:BlobOriginPublic onCompletion:^(NSData *imageData) {
        // Update profile picture of contact
        NSError *updateProfilePictureError;
        [[ContactStore sharedContactStore] updateProfilePicture:msg.fromIdentity imageData:imageData shouldReflect:YES blobID:msg.blobId encryptionKey:msg.encryptionKey didFailWithError:&updateProfilePictureError];
        if (updateProfilePictureError) {
            onError(updateProfilePictureError);
            return;
        }

        [self changedContactWithIdentity:msg.fromIdentity];
        onCompletion();
    } onError:^(NSError *error) {
        if (error.code == 404) {
            onCompletion();
        }
        else {
            onError(error);
        }
    }];
}

- (void)processIncomingContactDeletePhotoMessage:(ContactDeletePhotoMessage *)msg onCompletion:(void(^ _Nonnull)(void))onCompletion {
    [[ContactStore sharedContactStore] deleteProfilePicture:msg.fromIdentity shouldReflect:YES];
    [self changedContactWithIdentity:msg.fromIdentity];
    onCompletion();
}

- (void)processIncomingContactRequestPhotoMessage:(ContactRequestPhotoMessage *)msg onCompletion:(void(^ _Nonnull)(void))onCompletion {
    [[ContactStore sharedContactStore] removeProfilePictureFlagForIdentity:msg.fromIdentity];
    onCompletion();
}


- (void)processIncomingVoIPCallOfferMessage:(BoxVoIPCallOfferMessage *)msg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nullable delegateError))onError {
    VoIPCallOfferMessage *message = [VoIPCallMessageDecoder decodeVoIPCallOfferFrom:msg receivedAsReflected:NO isOutgoing:NO];
    if (message == nil) {
        onError([ThreemaError threemaError:@"Error parsing json for voip call offer"], nil);
        return;
    }
    
    [messageProcessorDelegate processVoIPCall:message identity:msg.fromIdentity onCompletion:onCompletion onError:onError];
}

- (void)processIncomingVoIPCallAnswerMessage:(BoxVoIPCallAnswerMessage *)msg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nullable))onError {
    VoIPCallAnswerMessage *message = [VoIPCallMessageDecoder decodeVoIPCallAnswerFrom:msg receivedAsReflected:NO isOutgoing:NO];
    if (message == nil) {
        onError([ThreemaError threemaError:@"Error parsing json for ballot vote"], nil);
        return;
    }

    [messageProcessorDelegate processVoIPCall:message identity:msg.fromIdentity onCompletion:onCompletion onError:onError];
}

- (void)processIncomingVoIPCallIceCandidatesMessage:(BoxVoIPCallIceCandidatesMessage *)msg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nullable))onError {
    VoIPCallIceCandidatesMessage *message = [VoIPCallMessageDecoder decodeVoIPCallIceCandidatesFrom:msg receivedAsReflected:NO isOutgoing:NO];
    if (message == nil) {
        onError([ThreemaError threemaError:@"Error parsing json for ice candidates"], nil);
        return;
    }

    [messageProcessorDelegate processVoIPCall:message identity:msg.fromIdentity onCompletion:onCompletion onError:onError];
}

- (void)processIncomingVoIPCallHangupMessage:(BoxVoIPCallHangupMessage *)msg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nullable))onError {
    VoIPCallHangupMessage *message = [VoIPCallMessageDecoder decodeVoIPCallHangupFrom:msg contactIdentity:msg.fromIdentity receivedAsReflected:NO isOutgoing:NO];
    
    if (message == nil) {
        onError([ThreemaError threemaError:@"Error parsing json for hangup"], nil);
        return;
    }
    
    [messageProcessorDelegate processVoIPCall:message identity:msg.fromIdentity onCompletion:onCompletion onError:onError];
}

- (void)processIncomingVoipCallRingingMessage:(BoxVoIPCallRingingMessage *)msg onCompletion:(void(^ _Nonnull)(id<MessageProcessorDelegate> _Nullable delegate))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nullable))onError {
    VoIPCallRingingMessage *message = [VoIPCallMessageDecoder decodeVoIPCallRingingFrom:msg contactIdentity:msg.fromIdentity receivedAsReflected:NO isOutgoing:NO];

    if (message == nil) {
        onError([ThreemaError threemaError:@"Error parsing json for ringing"], nil);
        return;
    }
    
    [messageProcessorDelegate processVoIPCall:message identity:msg.fromIdentity onCompletion: onCompletion onError:onError];
}


#pragma private methods

/// Check is the sender identity is blocked.
/// If identity is a Special Contact, return that it is not blocked.
/// If identity is explicitly blocked (black list), return that it is blocked.
/// If contact is part of a group that is not marked as left, return that it is not blocked.
///
/// @param amsg Decoded abstract message
- (BOOL)isBlocked:(AbstractMessage *)amsg {
    if ([PredefinedContactsObjC isSpecialContactWithIdentity:amsg.fromIdentity]) {
        return NO;
    }

    if ([[UserSettings sharedUserSettings].blacklist containsObject:amsg.fromIdentity]) {
        if ([amsg isKindOfClass:[AbstractGroupMessage class]]) {
            AbstractGroupMessage *groupMessage = (AbstractGroupMessage *)amsg;
            Group *group = [groupManager getGroup:groupMessage.groupId creator:groupMessage.groupCreator];
            
            // If this group is active and the message is a group control message (create, leave, requestSync, Rename, SetPhoto, DeletePhoto)
            if (group.isSelfMember && [groupMessage isGroupControlMessage]) {
                    return false;
            }
        }
        
        return true;
    }
    return false;
}

- (void)syncLoadBlobID:(NSData *)blobID encryptionKey:(NSData *)encryptionKey origin:(BlobOrigin)origin onCompletion:(void(^ _Nonnull)(NSData * _Nonnull imageData))onCompletion onError:(void(^ _Nonnull)(NSError *error))onError {
    // Download blob image
    dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    BlobURL *blobUrl = [[BlobURL alloc] initWithServerConnector:[ServerConnector sharedServerConnector] userSettings:[UserSettings sharedUserSettings] queue:downloadQueue];
    BlobDownloader *blobDownloader = [[BlobDownloader alloc] initWithBlobURL:blobUrl queue:downloadQueue];
    [blobDownloader downloadWithBlobID:blobID origin:BlobOriginPublic completion:^(NSData * _Nullable data, NSError * _Nullable error) {
        if (error) {
            onError(error);
            return;
        }

        // Decrypt blob data
        NSData *imageData = [[NaClCrypto sharedCrypto] symmetricDecryptData:data withKey:encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_1 length:sizeof(kNonce_1) freeWhenDone:NO]];
        if (imageData == nil) {
            onError([ThreemaError threemaError:@"Image blob decryption failed" withCode:ThreemaProtocolErrorMessageBlobDecryptionFailed]);
            return;
        }

        if ([[UserSettings sharedUserSettings] enableMultiDevice]) {
            [blobDownloader markDownloadDoneFor:blobID origin:BlobOriginLocal];
        }

        onCompletion(imageData);
    }];
}

-  (void)changedBallotWithID:(NSData * _Nonnull)ID {
    [entityManager performAndWait:^{
        BallotEntity *ballot = [[entityManager entityFetcher] ballotEntityFor:ID];
        if (ballot) {
            [messageProcessorDelegate changedManagedObjectID:ballot.objectID];
        }
    }];
}

- (void)changedContactWithIdentity:(NSString * _Nonnull)identity {
    [entityManager performAndWait:^{
        ContactEntity *contact = [entityManager.entityFetcher contactEntityFor:identity];
        if (contact) {
            [messageProcessorDelegate changedManagedObjectID:contact.objectID];
        }
    }];
}

- (void)changedConversationAndGroupEntityWithGroupID:(NSData * _Nonnull)groupID groupCreatorIdentity:(NSString * _Nonnull)groupCreatorIdentity {
    [entityManager performAndWait:^{
        ConversationEntity *conversation = [entityManager.entityFetcher groupConversationEntityFor:groupID creatorID:groupCreatorIdentity myIdentity:[[MyIdentityStore sharedMyIdentityStore] identity]];
        if (conversation) {
            [messageProcessorDelegate changedManagedObjectID:conversation.objectID];

            GroupEntity *groupEntity = [[entityManager entityFetcher] groupEntityFor:conversation];
            if (groupEntity) {
                [messageProcessorDelegate changedManagedObjectID:groupEntity.objectID];
            }
        }
    }];
}

@end

[Dauer der Verarbeitung: 0.41 Sekunden]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104852
#Domains=417171