|
|
|
|
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] | | |