12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2012-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- #import <CoreData/CoreData.h>
- #import "Threema-Swift.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 "GroupRequestSyncMessage.h"
- #import "LocationMessage.h"
- #import "TextMessage.h"
- #import "ImageMessage.h"
- #import "VideoMessage.h"
- #import "AudioMessage.h"
- #import "BoxFileMessage.h"
- #import "GroupFileMessage.h"
- #import "ContactSetPhotoMessage.h"
- #import "ContactDeletePhotoMessage.h"
- #import "ContactRequestPhotoMessage.h"
- #import "GroupDeletePhotoMessage.h"
- #import "UnknownTypeMessage.h"
- #import "Contact.h"
- #import "Conversation.h"
- #import "ImageData.h"
- #import "AppDelegate.h"
- #import "Utils.h"
- #import "ProtocolDefines.h"
- #import "UserSettings.h"
- #import "TypingIndicatorManager.h"
- #import "MyIdentityStore.h"
- #import "NSString+Hex.h"
- #import "ImageMessageLoader.h"
- #import "AnimGifMessageLoader.h"
- #import "ContactGroupPhotoLoader.h"
- #import "NaClCrypto.h"
- #import "PinnedHTTPSURLLoader.h"
- #import "ValidationLogger.h"
- #import "BallotMessageDecoder.h"
- #import "BlobUtil.h"
- #import "GroupProxy.h"
- #import "EntityManager.h"
- #import "MessageSender.h"
- #import "GroupMessageProcessor.h"
- #import "ThreemaError.h"
- #import "DatabaseManager.h"
- #import "FileMessageDecoder.h"
- #import "UTIConverter.h"
- #import "DocumentManager.h"
- #import "VoIPCallMessageDecoder.h"
- #import "BoxVoIPCallIceCandidatesMessage.h"
- #import "BoxVoIPCallHangupMessage.h"
- #import "BoxVoIPCallRingingMessage.h"
- #import "NotificationManager.h"
- #import "PushSetting.h"
- #import "NonceHasher.h"
- #import "UIDefines.h"
- #import "ServerConnector.h"
- #import "AppGroup.h"
- #ifdef DEBUG
- static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
- #else
- static const DDLogLevel ddLogLevel = DDLogLevelWarning;
- #endif
- @interface MessageProcessor () <GroupMessageProcessorDelegate>
- @property EntityManager *entityManager;
- @property NSMutableOrderedSet *pendingGroupMessages;
- @end
- @implementation MessageProcessor
- + (MessageProcessor*)sharedMessageProcessor {
- static MessageProcessor *instance;
-
- @synchronized (self) {
- if (!instance) {
- instance = [[MessageProcessor alloc] init];
- }
- }
-
- return instance;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _pendingGroupMessages = [[NSMutableOrderedSet alloc] init];
-
- _entityManager = [[EntityManager alloc] init];
- }
- return self;
- }
- - (void)processIncomingMessage:(BoxedMessage*)boxmsg receivedAfterInitialQueueSend:(BOOL)receivedAfterInitialQueueSend onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- /* blacklisted? */
- if ([[UserSettings sharedUserSettings].blacklist containsObject:boxmsg.fromIdentity]) {
- DDLogWarn(@"Ignoring message from blocked ID %@", boxmsg.fromIdentity);
- onError([ThreemaError threemaError:@"Message received from blocked contact" withCode:kBlockUnknownContactErrorCode]);
- return;
- }
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [[ValidationLogger sharedValidationLogger] logString:@"Threema Web: processIncomingMessage --> connect all running sessions"];
- [[WCSessionManager shared] connectAllRunningSessions];
- [MessageDecoder decodeFromBoxed:boxmsg isIncomming:YES onCompletion:^(AbstractMessage *amsg) {
- if (amsg == nil) {
- onError([ThreemaError threemaError:@"Bad message format or decryption error" withCode:kBadMessageErrorCode]);
- return;
- }
-
- if ([amsg isKindOfClass: [UnknownTypeMessage class]]) {
- onError([ThreemaError threemaError:@"Unknown message type" withCode:kUnknownMessageTypeErrorCode]);
- return;
- }
-
- // Validation logging
- if ([amsg isContentValid] == NO) {
- if ([amsg isKindOfClass:[BoxTextMessage class]] || [amsg isKindOfClass:[GroupTextMessage class]]) {
- [[ValidationLogger sharedValidationLogger] logBoxedMessage:boxmsg isIncoming:YES description:@"Ignore invalid content"];
- } else {
- [[ValidationLogger sharedValidationLogger] logSimpleMessage:amsg isIncoming:YES description:@"Ignore invalid content"];
- }
- } else {
- if ([_entityManager.entityFetcher isMessageAlreadyInDb:amsg]) {
- if ([amsg isKindOfClass:[BoxTextMessage class]] || [amsg isKindOfClass:[GroupTextMessage class]]) {
- [[ValidationLogger sharedValidationLogger] logBoxedMessage:boxmsg isIncoming:YES description:@"Message already in database"];
- } else {
- [[ValidationLogger sharedValidationLogger] logSimpleMessage:amsg isIncoming:YES description:@"Message already in database"];
- }
- } else {
- if ([_entityManager.entityFetcher isNonceAlreadyInDb:amsg]) {
- if ([amsg isKindOfClass:[BoxTextMessage class]] || [amsg isKindOfClass:[GroupTextMessage class]]) {
- [[ValidationLogger sharedValidationLogger] logBoxedMessage:boxmsg isIncoming:YES description:@"Nonce already in database"];
- } else {
- [[ValidationLogger sharedValidationLogger] logSimpleMessage:amsg isIncoming:YES description:@"Nonce already in database"];
- }
- } else {
- if ([amsg isKindOfClass:[BoxTextMessage class]] || [amsg isKindOfClass:[GroupTextMessage class]]) {
- [[ValidationLogger sharedValidationLogger] logBoxedMessage:boxmsg isIncoming:YES description:nil];
- } else {
- [[ValidationLogger sharedValidationLogger] logSimpleMessage:amsg isIncoming:YES description:nil];
- }
- }
- }
- }
-
- amsg.receivedAfterInitialQueueSend = receivedAfterInitialQueueSend;
-
- [self processIncomingAbstractMessage:amsg onCompletion:onCompletion onError:onError];
- } onError:^(NSError *err) {
- onError(err);
- }];
- });
- }
- - (void)processIncomingAbstractMessage:(AbstractMessage*)amsg onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- if ([amsg isContentValid] == NO) {
- DDLogInfo(@"Ignore invalid content, message ID %@ from %@", amsg.messageId, amsg.fromIdentity);
- onCompletion();
- return;
- }
-
- if ([_entityManager.entityFetcher isMessageAlreadyInDb:amsg]) {
- DDLogInfo(@"Message ID %@ from %@ already in database", amsg.messageId, amsg.fromIdentity);
- onCompletion();
- return;
- }
-
- if ([_entityManager.entityFetcher isNonceAlreadyInDb:amsg]) {
- DDLogInfo(@"Message nonce from %@ already in database", amsg.fromIdentity);
- onCompletion();
- return;
- }
-
- /* Find contact for message */
- Contact *contact = [_entityManager.entityFetcher contactForId: amsg.fromIdentity];
- if (contact == nil) {
- /* This should never happen, as without an entry in the contacts database, we wouldn't have
- been able to decrypt this message in the first place (no sender public key) */
- DDLogWarn(@"Identity %@ not in local contacts database - cannot process message", amsg.fromIdentity);
- NSError *error = [ThreemaError threemaError:[NSString stringWithFormat:@"Identity %@ not in local contacts database - cannot process message", amsg.fromIdentity]];
- onError(error);
- return;
- }
-
- /* Update public nickname in contact, if necessary */
- if (![amsg isKindOfClass:[DeliveryReceiptMessage class]] && (contact.publicNickname == nil || ![contact.publicNickname isEqualToString:amsg.pushFromName])) {
- contact.publicNickname = amsg.pushFromName;
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationRefreshContactSortIndices object:nil];
- }
-
- DDLogVerbose(@"processIncomingMessage: %@", amsg);
-
- [[PendingMessagesManager shared] pendingMessageWithSenderId:nil messageId:nil abstractMessage:amsg threemaDict:nil completion:^(PendingMessage *pendingMessage) {
- @try {
- pendingMessage.isPendingGroupMessages = false;
- if ([amsg isKindOfClass:[AbstractGroupMessage class]]) {
- [self processIncomingGroupMessage:(AbstractGroupMessage *)amsg pendingMessage:pendingMessage onCompletion:^{
- [_entityManager performSyncBlockAndSafe:^{
- [_entityManager.entityCreator nonceWithData:[NonceHasher hashedNonce:amsg.nonce]];
- }];
- onCompletion();
- } onError:onError];
- } else {
- [self processIncomingMessage:(AbstractMessage *)amsg pendingMessage:pendingMessage onCompletion:^{
- if (!amsg.immediate) {
- [_entityManager performSyncBlockAndSafe:^{
- [_entityManager.entityCreator nonceWithData:[NonceHasher hashedNonce:amsg.nonce]];
- }];
- }
- onCompletion();
- } onError:onError];
- }
- } @catch (NSException *exception) {
- NSError *error = [ThreemaError threemaError:exception.description withCode:kMessageProcessingErrorCode];
- onError(error);
- } @catch (NSError *error) {
- onError(error);
- }
- }];
- }
- - (void)processIncomingMessage:(AbstractMessage*)amsg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- Conversation *conversation = [self preprocessStorableMessage:amsg];
- if ([amsg needsConversation] && conversation == nil) {
- [pendingMessage finishedProcessing];
- onCompletion();
-
- return;
- }
-
-
- BOOL ackNow = YES;
-
- if ([amsg isKindOfClass:[BoxTextMessage class]]) {
- TextMessage *message = [_entityManager.entityCreator textMessageFromBox: amsg];
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:nil];
- } else if ([amsg isKindOfClass:[BoxImageMessage class]]) {
- ImageMessage *message = [_entityManager.entityCreator imageMessageFromBox:(BoxImageMessage*)amsg];
-
- NSData *fileData = nil;
- NSData *decryptedData = nil;
- NSString *filePath = [self filePath:amsg];
- if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
- fileData = [[NSFileManager defaultManager] contentsAtPath:filePath];
- }
-
- if (fileData) {
- if ([message wasDeleted]) {
- [pendingMessage finishedProcessing];
- return;
- }
- message.conversation = conversation;
-
- [self decryptImageFile:fileData message:message onCompletion:^(NSData *decrypted) {
- NSError *error;
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
-
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:^{
- if (!decrypted)
- [self startLoadingImageFromMessage:message boxMessage:amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- }];
- }];
- } else {
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:^{
- if (!decryptedData)
- [self startLoadingImageFromMessage:message boxMessage:amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- }];
- }
- } else if ([amsg isKindOfClass:[BoxVideoMessage class]]) {
- [self processIncomingVideoMessage:(BoxVideoMessage*)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO; // Only ACK video message once thumbnail has been downloaded, otherwise a failed blob download will lead to a missing message
-
- } else if ([amsg isKindOfClass:[BoxLocationMessage class]]) {
- LocationMessage *message = [_entityManager.entityCreator locationMessageFromBox:(BoxLocationMessage*)amsg];
- [self startReverserGeocodingForMessage:message];
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:nil];
-
- } else if ([amsg isKindOfClass:[BoxAudioMessage class]]) {
- AudioMessage *message = [_entityManager.entityCreator audioMessageFromBox:(BoxAudioMessage*) amsg];
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:nil];
-
- } else if ([amsg isKindOfClass:[DeliveryReceiptMessage class]]) {
- [self processIncomingDeliveryReceipt:(DeliveryReceiptMessage*)amsg pendingMessage:pendingMessage];
-
- } else if ([amsg isKindOfClass:[TypingIndicatorMessage class]]) {
- [self processIncomingTypingIndicator:(TypingIndicatorMessage*)amsg];
- [pendingMessage finishedProcessing];
- } else if ([amsg isKindOfClass:[BoxBallotCreateMessage class]]) {
- BallotMessageDecoder *decoder = [BallotMessageDecoder messageDecoder];
- BallotMessage *ballotMessage = [decoder decodeCreateBallotFromBox:(BoxBallotCreateMessage *)amsg forConversation:conversation];
- if (ballotMessage == nil) {
- NSError *error = [ThreemaError threemaError:@"Error parsing json for ballot create"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- [self finalizeMessage:ballotMessage inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:nil];
- } else if ([amsg isKindOfClass:[BoxBallotVoteMessage class]]) {
- [self processIncomingBallotVoteMessage:(BoxBallotVoteMessage*)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO;
-
- } else if ([amsg isKindOfClass:[BoxFileMessage class]]) {
- [FileMessageDecoder decodeMessageFromBox:(BoxFileMessage *)amsg forConversation:conversation onCompletion:^(BaseMessage *message) {
- [self conditionallyStartLoadingFileFromMessage:(FileMessage *)message];
- [self finalizeMessage:message inConversation:conversation fromBoxMessage:amsg pendingMessage:pendingMessage finalizeCompletion:nil];
- } onError:^(NSError *err) {
- [pendingMessage finishedProcessing];
- onError(err);
- }];
- } else if ([amsg isKindOfClass:[ContactSetPhotoMessage class]]) {
- [self processIncomingContactSetPhotoMessage:(ContactSetPhotoMessage *)amsg conversation:(Conversation *)conversation pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO; // Only ACK message (not blob) once contact photo has been downloaded, otherwise a failed blob download will lead to a missing message
- } else if ([amsg isKindOfClass:[ContactDeletePhotoMessage class]]) {
- [self processIncomingContactDeletePhotoMessage:(ContactDeletePhotoMessage *)amsg conversation:(Conversation *)conversation pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- } else if ([amsg isKindOfClass:[ContactRequestPhotoMessage class]]) {
- [self processIncomingContactRequestPhotoMessage:(ContactRequestPhotoMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion];
- } else if ([amsg isKindOfClass:[BoxVoIPCallOfferMessage class]]) {
- [self processIncomingVoIPCallOfferMessage:(BoxVoIPCallOfferMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- } else if ([amsg isKindOfClass:[BoxVoIPCallAnswerMessage class]]) {
- [self processIncomingVoIPCallAnswerMessage:(BoxVoIPCallAnswerMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- } else if ([amsg isKindOfClass:[BoxVoIPCallIceCandidatesMessage class]]) {
- [self processIncomingVoIPCallIceCandidatesMessage:(BoxVoIPCallIceCandidatesMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- } else if ([amsg isKindOfClass:[BoxVoIPCallHangupMessage class]]) {
- [self processIncomingVoIPCallHangupMessage:(BoxVoIPCallHangupMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- } else if ([amsg isKindOfClass:[BoxVoIPCallRingingMessage class]]) {
- [self processIncomingVoipCallRingingMessage:(BoxVoIPCallRingingMessage *)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- }
- else {
- DDLogError(@"Invalid message class");
- [pendingMessage finishedProcessing];
- return;
- }
-
- if (ackNow) {
- onCompletion();
- }
- }
- - (Conversation*)preprocessStorableMessage:(AbstractMessage*)msg {
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
-
- /* Try to find an existing Conversation for the same contact */
- // check if type allow to create the conversation
- Conversation *conversation = [_entityManager conversationForContact: contact createIfNotExisting:[msg canCreateConversation]];
-
- return conversation;
- }
- - (void)processIncomingGroupMessage:(AbstractGroupMessage*)amsg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- GroupMessageProcessor *groupProcessor = [GroupMessageProcessor groupMessageProcessorForMessage:amsg];
- groupProcessor.delegate = self;
- [groupProcessor handleMessageOnCompletion:^(BOOL didHandleMessage) {
- if (didHandleMessage) {
- Conversation *conversation = [_entityManager.entityFetcher conversationForGroupMessage:amsg];
- if (groupProcessor.addToPendingMessages) {
- [_pendingGroupMessages addObject:amsg];
- pendingMessage.isPendingGroupMessages = true;
- [pendingMessage finishedProcessing];
- [[NotificationManager sharedInstance] updateUnreadMessagesCount:NO];
- onError(nil);
- return;
- } else {
- if (groupProcessor.isNewGroup || !conversation) {
- /* process any pending group messages that could not be processed before this create */
- [self processPendingGroupMessages];
- }
- }
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- }
- // messages not handled by GroupProcessor, e.g. messages that can be processed after delayed group create
- Conversation *conversation = groupProcessor.conversation;
- if (conversation == nil) {
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- }
-
- Contact *sender = [_entityManager.entityFetcher contactForId: amsg.fromIdentity];
-
- BOOL ackNow = YES;
- if ([amsg isKindOfClass:[GroupRenameMessage class]]) {
- GroupProxy *group = [GroupProxy groupProxyForConversation:conversation];
- [group setName: ((GroupRenameMessage *)amsg).name remoteSentDate:amsg.date];
- [pendingMessage finishedProcessing];
- } else if ([amsg isKindOfClass:[GroupSetPhotoMessage class]]) {
- [self processIncomingGroupSetPhotoMessage:(GroupSetPhotoMessage*)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO; // Only ACK message once group photo has been downloaded, otherwise a failed blob download will lead to a missing message
- } else if ([amsg isKindOfClass:[GroupDeletePhotoMessage class]]) {
- [self processIncomingGroupDeletePhotoMessage:(GroupDeletePhotoMessage*)amsg pendingMessage:pendingMessage onCompletion:onCompletion];
- } else if ([amsg isKindOfClass:[GroupTextMessage class]]) {
- TextMessage *message = [_entityManager.entityCreator textMessageFromGroupBox: (GroupTextMessage *)amsg];
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:nil];
-
- } else if ([amsg isKindOfClass:[GroupLocationMessage class]]) {
- LocationMessage *message = [_entityManager.entityCreator locationMessageFromGroupBox:(GroupLocationMessage *)amsg];
- [self startReverserGeocodingForMessage:message];
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:nil];
-
- } else if ([amsg isKindOfClass:[GroupImageMessage class]]) {
- ImageMessage *message = [_entityManager.entityCreator imageMessageFromGroupBox:(GroupImageMessage *)amsg];
- NSData *fileData = nil;
- NSData *decryptedData = nil;
- NSString *filePath = [self filePath:amsg];
- if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
- fileData = [[NSFileManager defaultManager] contentsAtPath:filePath];
- }
-
- if (fileData) {
- if ([message wasDeleted]) {
- return;
- }
- message.conversation = conversation;
-
- [self decryptImageFile:fileData message:message onCompletion:^(NSData *decrypted) {
- NSError *error;
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
-
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:^{
- if (!decrypted)
- [self startLoadingImageFromMessage:message boxMessage:amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- }];
- }];
- } else {
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:^{
- if (!decryptedData)
- [self startLoadingImageFromMessage:message boxMessage:amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- }];
- }
- } else if ([amsg isKindOfClass:[GroupVideoMessage class]]) {
- [self processIncomingGroupVideoMessage:(GroupVideoMessage*)amsg conversation:conversation pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO; // Only ACK video message once thumbnail has been downloaded, otherwise a failed blob download will lead to a missing message
-
- } else if ([amsg isKindOfClass:[GroupAudioMessage class]]) {
- AudioMessage *message = [_entityManager.entityCreator audioMessageFromGroupBox:(GroupAudioMessage *)amsg];
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:nil];
-
- } else if ([amsg isKindOfClass:[GroupBallotCreateMessage class]]) {
- BallotMessageDecoder *decoder = [BallotMessageDecoder messageDecoder];
- BallotMessage *message = [decoder decodeCreateBallotFromGroupBox:(GroupBallotCreateMessage *)amsg forConversation:conversation];
- if (message == nil) {
- NSError *error = [ThreemaError threemaError:@"Error parsing json for ballot create"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:nil];
- } else if ([amsg isKindOfClass:[GroupBallotVoteMessage class]]) {
- [self processIncomingGroupBallotVoteMessage:(GroupBallotVoteMessage*)amsg pendingMessage:pendingMessage onCompletion:onCompletion onError:onError];
- ackNow = NO;
-
- } else if ([amsg isKindOfClass:[GroupFileMessage class]]) {
- [FileMessageDecoder decodeGroupMessageFromBox:(GroupFileMessage *)amsg forConversation:conversation onCompletion:^(BaseMessage *message) {
- [self finalizeGroupMessage:message inConversation:conversation fromBoxMessage:amsg sender:sender pendingMessage:pendingMessage finalizeCompletion:nil];
- } onError:^(NSError *err) {
- [pendingMessage finishedProcessing];
- onError(err);
- }];
- } else {
- DDLogError(@"Invalid message class");
- [pendingMessage finishedProcessing];
- }
-
- if (ackNow) {
- onCompletion();
- }
- } onError:^(NSError *error) {
- onError(error);
- }];
- }
- - (void)appendNewMessage:(BaseMessage *)message toConversation:(Conversation *)conversation {
- [_entityManager performSyncBlockAndSafe:^{
- message.conversation = conversation;
- if (message != nil) {
- conversation.lastMessage = message;
- }
- conversation.unreadMessageCount = [NSNumber numberWithInt:[[conversation unreadMessageCount] intValue] + 1];
- }];
- }
- - (void)finalizeMessage:(BaseMessage*)message inConversation:(Conversation*)conversation fromBoxMessage:(AbstractMessage*)boxMessage pendingMessage:(PendingMessage *)pendingMessage finalizeCompletion:(void (^) (void))finalizeCompletion {
-
- if (boxMessage.delivered && boxMessage.deliveryDate != nil) {
- message.delivered = boxMessage.delivered;
- message.deliveryDate = boxMessage.deliveryDate;
- } else {
- /* Send delivery receipt */
- message.delivered = [NSNumber numberWithBool:YES];
- message.deliveryDate = [NSDate date];
- [MessageSender sendDeliveryReceiptForMessage:message fromIdentity:boxMessage.fromIdentity];
- }
-
- [self appendNewMessage:message toConversation:conversation];
- [pendingMessage addBaseMessageWithMessage:message];
-
- if (boxMessage.userAck && boxMessage.sendUserAck) {
- if (message.userackDate == nil || message.userack.boolValue != boxMessage.userAck.boolValue) {
- [_entityManager performSyncBlockAndSafe:^{
- @try {
- message.read = [NSNumber numberWithBool:YES];
- message.readDate = [NSDate date];
- message.conversation.unreadMessageCount = [NSNumber numberWithInt:[[message.conversation unreadMessageCount] intValue] - 1];
- }
- @catch (NSException *exception) {
- // intended to catch NSObjectInaccessibleException, which may happen
- // if the message has been deleted in the meantime
- DDLogError(@"Exception while marking message as read: %@", exception);
- }
- }];
-
- if ([ServerConnector sharedServerConnector].connectionState == ConnectionStateLoggedIn) {
- [MessageSender sendReadReceiptForMessages:@[message] toIdentity:message.conversation.contact.identity async:NO quickReply:NO];
- }
-
- [_entityManager performSyncBlockAndSafe:^{
- if (boxMessage.userAck.boolValue) {
- [MessageSender sendUserAckForMessages:@[message] toIdentity:message.conversation.contact.identity async:NO quickReply:YES];
- message.userack = [NSNumber numberWithBool:YES];
- } else {
- [MessageSender sendUserDeclineForMessages:@[message] toIdentity:message.conversation.contact.identity async:NO quickReply:YES];
- message.userack = [NSNumber numberWithBool:NO];
- }
-
- message.userackDate = [NSDate date];
- }];
- }
- }
- if (![message isKindOfClass:[ImageMessage class]] && ![message isKindOfClass:[VideoMessage class]]) {
- [pendingMessage finishedProcessing];
- }
-
- if (finalizeCompletion != nil) {
- finalizeCompletion();
- }
- }
- - (void)finalizeGroupMessage:(BaseMessage*)message inConversation:(Conversation*)conversation fromBoxMessage:(AbstractGroupMessage*)boxMessage sender:(Contact *)sender pendingMessage:(PendingMessage *)pendingMessage finalizeCompletion:(void (^) (void))finalizeCompletion {
- message.sender = sender;
- [self appendNewMessage:message toConversation:conversation];
- [pendingMessage addBaseMessageWithMessage:message];
- if (![message isKindOfClass:[ImageMessage class]] && ![message isKindOfClass:[VideoMessage class]]) {
- [pendingMessage finishedProcessing];
- }
-
- if (finalizeCompletion != nil) {
- finalizeCompletion();
- }
- }
- - (void)startLoadingImageFromMessage:(ImageMessage*)message boxMessage:(AbstractMessage*)boxMessage pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- /* Start loading image */
-
- ImageMessageLoader *loader = [[ImageMessageLoader alloc] init];
- dispatch_async(dispatch_get_main_queue(), ^{
- [loader startWithMessage:message onCompletion:^(BaseMessage *message) {
- [pendingMessage addBaseMessageWithMessage:message];
- DDLogInfo(@"Image message blob load completed");
- if (boxMessage) {
- [pendingMessage finishedProcessing];
- }
- onCompletion();
- } onError:^(NSError *error) {
- DDLogError(@"Image message blob load failed with error: %@", error);
- if (boxMessage) {
- [pendingMessage finishedProcessing];
- }
- onError(error);
- }];
- });
- }
- - (void)conditionallyStartLoadingFileFromMessage:(FileMessage*)message {
- if ([UTIConverter isGifMimeType:message.mimeType] == YES) {
- // only load if not too big
- if (message.fileSize.floatValue > 1*1024*1024) {
- return;
- }
-
- AnimGifMessageLoader *loader = [[AnimGifMessageLoader alloc] init];
- [loader startWithMessage:message onCompletion:^(BaseMessage *message) {
- DDLogInfo(@"File message blob load completed");
- } onError:^(NSError *error) {
- DDLogError(@"File message blob load failed with error: %@", error);
- }];
- } else {
- if ([message renderFileImageMessage] == true || [message renderFileAudioMessage] == true) {
- BlobMessageLoader *loader = [[BlobMessageLoader alloc] init];
- [loader startWithMessage:message onCompletion:^(BaseMessage *message) {
- DDLogInfo(@"File message blob load completed");
- } onError:^(NSError *error) {
- DDLogError(@"File message blob load failed with error: %@", error);
- }];
- }
- }
- }
- - (void)processIncomingVideoMessage:(BoxVideoMessage*)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- __block Conversation *conversation = [self preprocessStorableMessage:msg];
- if (conversation == nil) {
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- }
-
- NSData *fileData = nil;
- NSString *filePath = [self filePath:msg];
- if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
- fileData = [[NSFileManager defaultManager] contentsAtPath:filePath];
- }
-
- /* Conversation deleted in the meantime? */
- if ([conversation wasDeleted]) {
- /* Make a new one */
- conversation = [self preprocessStorableMessage:msg];
- if (conversation == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Cannot get replacement for deleted conversation"]);
- return;
- }
- }
-
- if (fileData) {
- /* Decrypt the box */
- NSData *thumbnailData = [[NaClCrypto sharedCrypto] symmetricDecryptData:fileData withKey:msg.encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_2 length:sizeof(kNonce_2) freeWhenDone:NO]];
-
- if (thumbnailData == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail blob decryption failed"]);
- return;
- }
-
- /* Make thumbnail */
- UIImage *thumbnail = [UIImage imageWithData:thumbnailData];
- if (thumbnail == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail decoding failed"]);
- return;
- }
-
- __block VideoMessage *message;
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = thumbnailData;
- dbThumbnail.width = [NSNumber numberWithInt:thumbnail.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:thumbnail.size.height];
-
- /* Create Message in DB */
- message = [_entityManager.entityCreator videoMessageFromBox:msg];
- message.thumbnail = dbThumbnail;
- message.duration = [NSNumber numberWithInt:msg.duration];
- message.videoSize = [NSNumber numberWithInt:msg.videoSize];
- message.videoBlobId = msg.videoBlobId;
- message.encryptionKey = msg.encryptionKey;
- message.conversation = conversation;
-
- conversation.lastMessage = message;
- conversation.unreadMessageCount = [NSNumber numberWithInt:[[conversation unreadMessageCount] intValue] + 1];
- }];
- [pendingMessage addBaseMessageWithMessage:message];
-
- /* Delete thumbnail blob on server */
- [MessageSender markBlobAsDone:msg.thumbnailBlobId];
-
- /* Delete file from push */
- NSError *error;
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
- [pendingMessage finishedProcessing];
- onCompletion();
- } else {
- __block UIImage *videoPlaceholderImage = [UIImage imageNamed:@"Video"];
- __block NSData *videoPlaceholderImageData = UIImageJPEGRepresentation(videoPlaceholderImage, 1.0);
- __block VideoMessage *message;
- [_entityManager performSyncBlockAndSafe:^{
-
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = videoPlaceholderImageData;
- dbThumbnail.width = [NSNumber numberWithInt:videoPlaceholderImage.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:videoPlaceholderImage.size.height];
- /* Create Message in DB */
- message = [_entityManager.entityCreator videoMessageFromBox:msg];
- message.duration = [NSNumber numberWithInt:msg.duration];
- message.videoSize = [NSNumber numberWithInt:msg.videoSize];
- message.videoBlobId = msg.videoBlobId;
- message.encryptionKey = msg.encryptionKey;
- message.conversation = conversation;
- message.thumbnail = dbThumbnail;
- conversation.lastMessage = message;
- conversation.unreadMessageCount = [NSNumber numberWithInt:[[conversation unreadMessageCount] intValue] + 1];
- }];
- [pendingMessage addBaseMessageWithMessage:message];
- /* Send delivery receipt */
- [MessageSender sendDeliveryReceiptForMessage:message fromIdentity:msg.fromIdentity];
-
- /* Fetch video thumbnail */
- NSURLRequest *request = [BlobUtil urlRequestForBlobId:msg.thumbnailBlobId];
-
- PinnedHTTPSURLLoader *thumbnailLoader = [[PinnedHTTPSURLLoader alloc] init];
- [thumbnailLoader startWithURLRequest:request onCompletion:^(NSData *data) {
- dispatch_async(dispatch_get_main_queue(), ^{
-
- /* Decrypt the box */
- NSData *thumbnailData = [[NaClCrypto sharedCrypto] symmetricDecryptData:data withKey:msg.encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_2 length:sizeof(kNonce_2) freeWhenDone:NO]];
-
- if (thumbnailData == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail blob decryption failed"]);
- return;
- }
-
- /* Make thumbnail */
- UIImage *thumbnail = [UIImage imageWithData:thumbnailData];
- if (thumbnail == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail decoding failed"]);
- return;
- }
-
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = thumbnailData;
- dbThumbnail.width = [NSNumber numberWithInt:thumbnail.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:thumbnail.size.height];
-
- /* Create Message in DB */
- message.thumbnail = dbThumbnail;
- conversation.lastMessage = message;
- }];
- [pendingMessage addBaseMessageWithMessage:message];
- NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
- conversation.objectID, kKeyObjectID,
- nil];
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDBRefreshedDirtyObject object:self userInfo:info];
- /* Delete thumbnail blob on server */
- [MessageSender markBlobAsDone:msg.thumbnailBlobId];
- [pendingMessage finishedProcessing];
- onCompletion();
- });
- } onError:^(NSError *error) {
- [pendingMessage finishedProcessing];
- DDLogError(@"Blob load failed: %@", error);
- onError(error);
- }];
- }
- }
- - (void)startReverserGeocodingForMessage:(LocationMessage*)message {
- /* Reverse geocoding (only necessary if there is no POI name) */
- if (message.poiName == nil) {
- double latitude = message.latitude.doubleValue;
- double longitude = message.longitude.doubleValue;
- double accuracy = message.accuracy.doubleValue;
-
- [Utils reverseGeocodeNearLatitude:latitude longitude:longitude accuracy:accuracy completion:^(NSString *label) {
- if ([message wasDeleted]) {
- return;
- }
-
- [_entityManager performAsyncBlockAndSafe:^{
- message.reverseGeocodingResult = label;
- }];
- } onError:^(NSError *error) {
- DDLogWarn(@"Reverse geocoding failed: %@", error);
- if ([message wasDeleted]) {
- return;
- }
-
- [_entityManager performAsyncBlockAndSafe:^{
- message.reverseGeocodingResult = [NSString stringWithFormat:@"%.5f°, %.5f°", latitude, longitude];
- }];
- }];
- }
- }
- - (void)processIncomingDeliveryReceipt:(DeliveryReceiptMessage*)msg pendingMessage:(PendingMessage *)pendingMessage {
- [_entityManager performAsyncBlockAndSafe:^{
- for (NSData *receiptMessageId in msg.receiptMessageIds) {
- /* Fetch message from DB */
- BaseMessage *dbmsg = [_entityManager.entityFetcher ownMessageWithId: receiptMessageId];
- if (dbmsg == nil) {
- /* This can happen if the user deletes the message before the receipt comes in */
- DDLogInfo(@"Cannot find message ID %@ (delivery receipt from %@)", receiptMessageId, msg.fromIdentity);
- continue;
- }
-
- if (msg.receiptType == DELIVERYRECEIPT_MSGRECEIVED) {
- DDLogVerbose(@"Message ID %@ has been received by recipient", receiptMessageId);
- dbmsg.deliveryDate = msg.date;
- dbmsg.delivered = [NSNumber numberWithBool:YES];
- } else if (msg.receiptType == DELIVERYRECEIPT_MSGREAD) {
- DDLogVerbose(@"Message ID %@ has been read by recipient", receiptMessageId);
- if (!dbmsg.delivered)
- dbmsg.delivered = [NSNumber numberWithBool:YES];
- dbmsg.readDate = msg.date;
- dbmsg.read = [NSNumber numberWithBool:YES];
- } else if (msg.receiptType == DELIVERYRECEIPT_MSGUSERACK) {
- DDLogVerbose(@"Message ID %@ has been user acknowledged by recipient", receiptMessageId);
- dbmsg.userackDate = msg.date;
- dbmsg.userack = [NSNumber numberWithBool:YES];
- } else if (msg.receiptType == DELIVERYRECEIPT_MSGUSERDECLINE) {
- DDLogVerbose(@"Message ID %@ has been user declined by recipient", receiptMessageId);
- dbmsg.userackDate = msg.date;
- dbmsg.userack = [NSNumber numberWithBool:NO];
- } else {
- DDLogWarn(@"Unknown delivery receipt type %d", msg.receiptType);
- }
- }
- [pendingMessage finishedProcessing];
- }];
- }
- - (void)processIncomingTypingIndicator:(TypingIndicatorMessage*)msg {
- [[TypingIndicatorManager sharedInstance] setTypingIndicatorForIdentity:msg.fromIdentity typing:msg.typing];
- }
- - (void)processIncomingGroupSetPhotoMessage:(GroupSetPhotoMessage*)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- Conversation *conversation = [_entityManager.entityFetcher conversationForGroupMessage:msg];
- if (conversation == nil) {
- DDLogInfo(@"Group ID %@ from %@ not found", msg.groupId, msg.groupCreator);
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- } else {
- /* Start loading image */
- ContactGroupPhotoLoader *loader = [[ContactGroupPhotoLoader alloc] init];
- [loader startWithBlobId:msg.blobId encryptionKey:msg.encryptionKey onCompletion:^(NSData *imageData) {
- DDLogInfo(@"Group photo blob load completed");
- if (conversation.managedObjectContext != nil) {
- /* Check if this message is older than the last set date. This ensures that we're using
- the latest image in case multiple images arrive for the same conversation in short succession.
- Must do the check here (main thread) to avoid race condition. */
- if (conversation.groupImageSetDate != nil && [conversation.groupImageSetDate compare:msg.date] == NSOrderedDescending) {
- DDLogInfo(@"Ignoring older group set photo message");
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- }
-
- UIImage *image = [UIImage imageWithData:imageData];
- if (image == nil) {
- onError([ThreemaError threemaError:@"Image decoding failed"]);
- [pendingMessage finishedProcessing];
- return;
- }
-
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbImage = [_entityManager.entityCreator imageData];
- dbImage.data = imageData;
- dbImage.width = [NSNumber numberWithInt:image.size.width];
- dbImage.height = [NSNumber numberWithInt:image.size.height];
-
- conversation.groupImage = dbImage;
- conversation.groupImageSetDate = msg.date;
- }];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- } onError:^(NSError *err) {
- DDLogError(@"Group photo blob load failed with error: %@", err);
- [pendingMessage finishedProcessing];
- onError(err);
- }];
- }
- }
- - (void)processIncomingGroupDeletePhotoMessage:(GroupDeletePhotoMessage*)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion {
-
- Conversation *conversation = [_entityManager.entityFetcher conversationForGroupMessage:msg];
- if (conversation == nil) {
- DDLogInfo(@"Group ID %@ from %@ not found", msg.groupId, msg.groupCreator);
- [pendingMessage finishedProcessing];
- onCompletion();
- return;
- } else {
- if (conversation.managedObjectContext != nil) {
- [_entityManager performSyncBlockAndSafe:^{
- if (conversation.groupImage != nil) {
- [[_entityManager entityDestroyer] deleteObjectWithObject:conversation.groupImage];
- conversation.groupImage = nil;
- conversation.groupImageSetDate = nil;
- }
- }];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- }
- }
- - (void)processIncomingGroupVideoMessage:(GroupVideoMessage*)msg conversation:(Conversation *) conversation pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- NSData *fileData = nil;
- NSString *filePath = [self filePath:msg];
- if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
- fileData = [[NSFileManager defaultManager] contentsAtPath:filePath];
- }
-
- Conversation *currentConversation = conversation;
- /* Conversation deleted in the meantime? */
- if ([conversation wasDeleted]) {
- /* Make a new one */
- currentConversation = [_entityManager.entityFetcher conversationForGroupMessage:msg];
- if (currentConversation == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Cannot get replacement for deleted conversation"]);
- return;
- }
- }
-
- if (fileData) {
- /* Decrypt the box */
- NSData *thumbnailData = [[NaClCrypto sharedCrypto] symmetricDecryptData:fileData withKey:msg.encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_2 length:sizeof(kNonce_2) freeWhenDone:NO]];
-
- if (thumbnailData == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail blob decryption failed"]);
- return;
- }
-
- /* Make thumbnail */
- UIImage *thumbnail = [UIImage imageWithData:thumbnailData];
- if (thumbnail == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail decoding failed"]);
- return;
- }
-
- __block VideoMessage *message;
- /* Create Message in DB */
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = thumbnailData;
- dbThumbnail.width = [NSNumber numberWithInt:thumbnail.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:thumbnail.size.height];
-
- message = [_entityManager.entityCreator videoMessageFromGroupBox:msg];
- message.thumbnail = dbThumbnail;
- message.duration = [NSNumber numberWithInt:msg.duration];
- message.videoSize = [NSNumber numberWithInt:msg.videoSize];
- message.videoBlobId = msg.videoBlobId;
- message.encryptionKey = msg.encryptionKey;
-
- message.conversation = currentConversation;
- message.sender = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
-
- currentConversation.lastMessage = message;
- currentConversation.unreadMessageCount = [NSNumber numberWithInt:[[currentConversation unreadMessageCount] intValue] + 1];
- }];
- [pendingMessage addBaseMessageWithMessage:message];
-
- /* Delete file from push */
- NSError *error;
- [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
- [pendingMessage finishedProcessing];
- onCompletion();
- } else {
- __block UIImage *videoPlaceholderImage = [UIImage imageNamed:@"Video"];
- __block NSData *videoPlaceholderImageData = UIImageJPEGRepresentation(videoPlaceholderImage, 1.0);
- __block VideoMessage *message;
- /* Create Message in DB */
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = videoPlaceholderImageData;
- dbThumbnail.width = [NSNumber numberWithInt:videoPlaceholderImage.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:videoPlaceholderImage.size.height];
-
- message = [_entityManager.entityCreator videoMessageFromGroupBox:msg];
- message.thumbnail = dbThumbnail;
- message.duration = [NSNumber numberWithInt:msg.duration];
- message.videoSize = [NSNumber numberWithInt:msg.videoSize];
- message.videoBlobId = msg.videoBlobId;
- message.encryptionKey = msg.encryptionKey;
-
- message.conversation = currentConversation;
- message.sender = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
-
- currentConversation.lastMessage = message;
- currentConversation.unreadMessageCount = [NSNumber numberWithInt:[[currentConversation unreadMessageCount] intValue] + 1];
- }];
- [pendingMessage addBaseMessageWithMessage:message];
-
- /* Fetch video thumbnail */
- NSURLRequest *request = [BlobUtil urlRequestForBlobId:msg.thumbnailBlobId];
-
- PinnedHTTPSURLLoader *thumbnailLoader = [[PinnedHTTPSURLLoader alloc] init];
- [thumbnailLoader startWithURLRequest:request onCompletion:^(NSData *data) {
- dispatch_async(dispatch_get_main_queue(), ^{
- /* Decrypt the box */
- NSData *thumbnailData = [[NaClCrypto sharedCrypto] symmetricDecryptData:data withKey:msg.encryptionKey nonce:[NSData dataWithBytesNoCopy:kNonce_2 length:sizeof(kNonce_2) freeWhenDone:NO]];
-
- if (thumbnailData == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail blob decryption failed"]);
- return;
- }
-
- /* Make thumbnail */
- UIImage *thumbnail = [UIImage imageWithData:thumbnailData];
- if (thumbnail == nil) {
- [pendingMessage finishedProcessing];
- onError([ThreemaError threemaError:@"Video thumbnail decoding failed"]);
- return;
- }
-
- /* Create Message in DB */
- [_entityManager performSyncBlockAndSafe:^{
- ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
- dbThumbnail.data = thumbnailData;
- dbThumbnail.width = [NSNumber numberWithInt:thumbnail.size.width];
- dbThumbnail.height = [NSNumber numberWithInt:thumbnail.size.height];
-
- message.thumbnail = dbThumbnail;
- currentConversation.lastMessage = message;
- }];
- [pendingMessage addBaseMessageWithMessage:message];
- [pendingMessage finishedProcessing];
- NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
- conversation.objectID, kKeyObjectID,
- nil];
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDBRefreshedDirtyObject object:self userInfo:info];
- onCompletion();
- });
- } onError:^(NSError *error) {
- DDLogError(@"Blob load failed: %@", error);
- [pendingMessage finishedProcessing];
- onError(error);
- }];
- }
- }
- - (void)processIncomingGroupBallotVoteMessage:(GroupBallotVoteMessage*)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- /* Create Message in DB */
- BallotMessageDecoder *decoder = [BallotMessageDecoder messageDecoder];
- if ([decoder decodeVoteFromGroupBox: msg] == NO) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error processing ballot vote"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- //persist decoded data
- [_entityManager performAsyncBlockAndSafe:nil];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processIncomingBallotVoteMessage:(BoxBallotVoteMessage*)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
-
- /* Create Message in DB */
- BallotMessageDecoder *decoder = [BallotMessageDecoder messageDecoder];
- if ([decoder decodeVoteFromBox: msg] == NO) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for ballot vote"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- //persist decoded data
- [_entityManager performAsyncBlockAndSafe:nil];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processPendingGroupMessages {
- DDLogVerbose(@"Processing pending group messages");
- NSArray *messages = [_pendingGroupMessages array];
- for (AbstractGroupMessage *msg in messages) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self processIncomingAbstractMessage:msg onCompletion:^{
- [_pendingGroupMessages removeObject:msg];
- [[ServerConnector sharedServerConnector] completedProcessingAbstractMessage:msg];
- } onError:^(NSError *err) {
- DDLogWarn(@"Processing pending group message failed: %@", err);
- }];
- });
- }
- }
- - (void)processIncomingContactSetPhotoMessage:(ContactSetPhotoMessage *)msg conversation:(Conversation *)conversation pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- /* Start loading image */
- ContactGroupPhotoLoader *loader = [[ContactGroupPhotoLoader alloc] init];
-
- [loader startWithBlobId:msg.blobId encryptionKey:msg.encryptionKey onCompletion:^(NSData *imageData) {
- DDLogInfo(@"contact photo blob load completed");
- ContactStore *contactStore = [ContactStore sharedContactStore];
- Contact *contact;
-
- if (conversation.managedObjectContext != nil) {
- contact = conversation.contact;
- } else {
- contact = [contactStore contactForIdentity:msg.fromIdentity];
- }
-
- NSError *error;
- [contactStore updateProfilePicture:contact imageData:imageData didFailWithError:&error];
-
- if (error != nil) {
- onError(error);
- return;
- }
- [pendingMessage finishedProcessing];
- onCompletion();
- } onError:^(NSError *err) {
- DDLogError(@"Contact photo blob load failed with error: %@", err);
- [pendingMessage finishedProcessing];
- if (err.code == 404)
- onCompletion();
- onError(err);
- }];
- }
- - (void)processIncomingContactDeletePhotoMessage:(ContactDeletePhotoMessage *)msg conversation:(Conversation *)conversation pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- ContactStore *contactStore = [ContactStore sharedContactStore];
- Contact *contact;
- if (conversation.managedObjectContext != nil) {
- contact = conversation.contact;
- } else {
- contact = [contactStore contactForIdentity:msg.fromIdentity];
- }
-
- [contactStore deleteProfilePicture:contact];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processIncomingContactRequestPhotoMessage:(ContactRequestPhotoMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion {
-
- [[ContactStore sharedContactStore] removeProfilePictureFlagForContact:msg.fromIdentity];
-
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processIncomingVoIPCallOfferMessage:(BoxVoIPCallOfferMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- VoIPCallMessageDecoder *decoder = [VoIPCallMessageDecoder messageDecoder];
- VoIPCallOfferMessage *message = [decoder decodeVoIPCallOfferFromBox:msg];
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
- if (message == nil) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for voip call offer"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- [[VoIPCallStateManager shared] incomingCallOfferWithOffer:message contact:contact completion:^{
- [pendingMessage finishedProcessing];
- onCompletion();
- }];
- }
- - (void)processIncomingVoIPCallAnswerMessage:(BoxVoIPCallAnswerMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- VoIPCallMessageDecoder *decoder = [VoIPCallMessageDecoder messageDecoder];
- VoIPCallAnswerMessage *message = [decoder decodeVoIPCallAnswerFromBox:msg];
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
- if (message == nil) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for ballot vote"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
- [[VoIPCallStateManager shared] incomingCallAnswerWithAnswer:message contact:contact completion:^{
- [pendingMessage finishedProcessing];
- onCompletion();
- }];
- }
- - (void)processIncomingVoIPCallIceCandidatesMessage:(BoxVoIPCallIceCandidatesMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- VoIPCallMessageDecoder *decoder = [VoIPCallMessageDecoder messageDecoder];
- VoIPCallIceCandidatesMessage *message = [decoder decodeVoIPCallIceCandidatesFromBox:msg];
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
- if (message == nil) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for ice candidates"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
- [[VoIPCallStateManager shared] incomingIceCandidatesWithCandidates:message contact:contact completion:^{
- [pendingMessage finishedProcessing];
- onCompletion();
- }];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processIncomingVoIPCallHangupMessage:(BoxVoIPCallHangupMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- VoIPCallMessageDecoder *decoder = [VoIPCallMessageDecoder messageDecoder];
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
- VoIPCallHangupMessage *message = [decoder decodeVoIPCallHangupFromBox:msg contact:contact];
-
- if (message == nil) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for hangup"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- [[VoIPCallStateManager shared] incomingCallHangupWithHangup:message];
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (void)processIncomingVoipCallRingingMessage:(BoxVoIPCallRingingMessage *)msg pendingMessage:(PendingMessage *)pendingMessage onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *err))onError {
- VoIPCallMessageDecoder *decoder = [VoIPCallMessageDecoder messageDecoder];
- Contact *contact = [_entityManager.entityFetcher contactForId: msg.fromIdentity];
- VoIPCallRingingMessage *message = [decoder decodeVoIPCallRingingFromBox:msg contact:contact];
- if (message == nil) {
- NSError *error;
- error = [ThreemaError threemaError:@"Error parsing json for ringing"];
- [pendingMessage finishedProcessing];
- onError(error);
- return;
- }
-
- [[VoIPCallStateManager shared] incomingCallRingingWithRinging:message];
-
- [pendingMessage finishedProcessing];
- onCompletion();
- }
- - (NSString *)filePath:(AbstractMessage *)message {
- NSString *groupDocumentsPath = [DocumentManager groupDocumentsDirectory].path;
- NSString *name = [NSString stringWithFormat:@"PushImage_%@", [NSString stringWithHexData:message.messageId]];
- NSString *fileName = [NSString stringWithFormat:@"/%@.jpg", name];
- return [groupDocumentsPath stringByAppendingString:fileName];
- }
- - (void)decryptImageFile:(NSData *)data message:(ImageMessage *)message onCompletion:(void(^)(NSData *decrypted))onCompletion {
- NSData *decryptedData = nil;
-
- ImageMessageLoader *loader = [[ImageMessageLoader alloc] init];
- NSData *encryptionKey = [message blobGetEncryptionKey];
-
- if (encryptionKey == nil) {
- // handle image message backward compatibility
- if ([message isKindOfClass:[ImageMessage class]]) {
- if (((ImageMessage *)message).imageNonce == nil) {
- DDLogWarn(@"Missing image encryption key or nonce!");
- onCompletion(nil);
- }
- } else {
- DDLogWarn(@"Missing encryption key!");
- onCompletion(nil);
- }
- }
- loader.message = message;
- decryptedData = [loader decryptData:data];
- if (decryptedData != nil) {
- [loader updateDBObjectWithData:decryptedData onCompletion:^{
- if (message.conversation.groupId == nil) {
- [MessageSender markBlobAsDone:message.imageBlobId];
- }
-
- DDLogInfo(@"Blob successfully downloaded (%lu bytes)", (unsigned long)data.length);
- onCompletion(decryptedData);
- }];
- } else {
- onCompletion(decryptedData);
- }
- }
- - (void)startProcessPendingGroupMessages {
- [self processPendingGroupMessages];
- }
- @end
|