From fe7171dd93efab4550853a5db69330c2707a8262 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 23 Aug 2016 16:38:05 -0400 Subject: [PATCH] Sync messages with your other devices After provisioning a desktop client, you'll see messages sent from your phone appear on your desktop client, and messages sent from the desktop client appear on your phone. * In the process extracted some of the Attachment processing logic out of the giant MessagesManager. * Some nullability annotations on affected files. // FREEBIE --- src/Contacts/SignalRecipient.h | 1 + src/Contacts/SignalRecipient.m | 9 ++ src/Contacts/TSThread.h | 10 +- src/Contacts/TSThread.m | 16 +- src/Contacts/Threads/TSContactThread.h | 4 +- src/Contacts/Threads/TSContactThread.m | 11 ++ src/Contacts/Threads/TSGroupThread.h | 6 +- src/Contacts/Threads/TSGroupThread.m | 62 ++++++-- .../Attachments/OWSAttachmentsProcessor.h | 30 ++++ .../Attachments/OWSAttachmentsProcessor.m | 92 ++++++++++++ .../OWSOutgoingSentMessageTranscript.h | 19 +++ .../OWSOutgoingSentMessageTranscript.m | 65 ++++++++ .../Interactions/OWSOutgoingSyncMessage.h | 16 ++ .../Interactions/OWSOutgoingSyncMessage.m | 16 ++ src/Messages/Interactions/TSOutgoingMessage.h | 30 +++- src/Messages/Interactions/TSOutgoingMessage.m | 88 +++++++++++ .../OWSIncomingSentMessageTranscript.h | 22 +++ .../OWSIncomingSentMessageTranscript.m | 99 ++++++++++++ src/Messages/OWSLegacyMessageServiceParams.h | 20 +++ src/Messages/OWSLegacyMessageServiceParams.m | 33 ++++ src/Messages/OWSMessageServiceParams.h | 25 ++++ src/Messages/OWSMessageServiceParams.m | 36 +++++ src/Messages/TSMessagesManager+attachments.m | 89 ++++------- src/Messages/TSMessagesManager+sendMessages.m | 141 ++++++++---------- src/Messages/TSMessagesManager.h | 14 +- src/Messages/TSMessagesManager.m | 77 +++++----- src/Messages/TSServerMessage.h | 20 --- src/Messages/TSServerMessage.m | 48 ------ 28 files changed, 827 insertions(+), 272 deletions(-) create mode 100644 src/Messages/Attachments/OWSAttachmentsProcessor.h create mode 100644 src/Messages/Attachments/OWSAttachmentsProcessor.m create mode 100644 src/Messages/Interactions/OWSOutgoingSentMessageTranscript.h create mode 100644 src/Messages/Interactions/OWSOutgoingSentMessageTranscript.m create mode 100644 src/Messages/Interactions/OWSOutgoingSyncMessage.h create mode 100644 src/Messages/Interactions/OWSOutgoingSyncMessage.m create mode 100644 src/Messages/OWSIncomingSentMessageTranscript.h create mode 100644 src/Messages/OWSIncomingSentMessageTranscript.m create mode 100644 src/Messages/OWSLegacyMessageServiceParams.h create mode 100644 src/Messages/OWSLegacyMessageServiceParams.m create mode 100644 src/Messages/OWSMessageServiceParams.h create mode 100644 src/Messages/OWSMessageServiceParams.m delete mode 100644 src/Messages/TSServerMessage.h delete mode 100644 src/Messages/TSServerMessage.m diff --git a/src/Contacts/SignalRecipient.h b/src/Contacts/SignalRecipient.h index 7741808b6..9f8e5b28f 100644 --- a/src/Contacts/SignalRecipient.h +++ b/src/Contacts/SignalRecipient.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN relay:(nullable NSString *)relay supportsVoice:(BOOL)voiceCapable; ++ (instancetype)recipientWithTextSecureIdentifier:(NSString *)textSecureIdentifier; + (instancetype)recipientWithTextSecureIdentifier:(NSString *)textSecureIdentifier withTransaction:(YapDatabaseReadTransaction *)transaction; diff --git a/src/Contacts/SignalRecipient.m b/src/Contacts/SignalRecipient.m index 4cf2b28d1..f78e2ac6a 100644 --- a/src/Contacts/SignalRecipient.m +++ b/src/Contacts/SignalRecipient.m @@ -33,6 +33,15 @@ NS_ASSUME_NONNULL_BEGIN return [self fetchObjectWithUniqueID:textSecureIdentifier transaction:transaction]; } ++ (instancetype)recipientWithTextSecureIdentifier:(NSString *)textSecureIdentifier +{ + __block SignalRecipient *recipient; + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + recipient = [self recipientWithTextSecureIdentifier:textSecureIdentifier withTransaction:transaction]; + }]; + return recipient; +} + - (NSMutableOrderedSet *)devices { return [_devices copy]; } diff --git a/src/Contacts/TSThread.h b/src/Contacts/TSThread.h index 4d5e8fa77..50368ad39 100644 --- a/src/Contacts/TSThread.h +++ b/src/Contacts/TSThread.h @@ -4,6 +4,8 @@ #import "TSYapDatabaseObject.h" #import +NS_ASSUME_NONNULL_BEGIN + @class TSInteraction; @class TSInvalidIdentityKeyReceivingErrorMessage; @@ -27,13 +29,15 @@ */ - (NSString *)name; +- (nullable NSString *)contactIdentifier; + #if TARGET_OS_IOS /** * Returns the image representing the thread. Nil if not available. * * @return UIImage of the thread, or nil. */ -- (UIImage *)image; +- (nullable UIImage *)image; #endif #pragma mark Interactions @@ -89,7 +93,7 @@ * * @return Last archival date. */ -- (NSDate *)archivalDate; +- (nullable NSDate *)archivalDate; /** * Archives a thread with the current date. @@ -134,3 +138,5 @@ - (void)setDraft:(NSString *)draftString transaction:(YapDatabaseReadWriteTransaction *)transaction; @end + +NS_ASSUME_NONNULL_END diff --git a/src/Contacts/TSThread.m b/src/Contacts/TSThread.m index dc246b987..242bcd2b9 100644 --- a/src/Contacts/TSThread.m +++ b/src/Contacts/TSThread.m @@ -9,6 +9,8 @@ #import "TSOutgoingMessage.h" #import "TSStorageManager.h" +NS_ASSUME_NONNULL_BEGIN + @interface TSThread () @property (nonatomic, retain) NSDate *creationDate; @@ -73,12 +75,19 @@ return FALSE; } +// Override in ContactThread +- (nullable NSString *)contactIdentifier +{ + return nil; +} + - (NSString *)name { NSAssert(FALSE, @"Should be implemented in subclasses"); return nil; } -- (UIImage *)image { +- (nullable UIImage *)image +{ return nil; } @@ -215,7 +224,8 @@ #pragma mark Archival -- (NSDate *)archivalDate { +- (nullable NSDate *)archivalDate +{ return _archivalDate; } @@ -253,3 +263,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/src/Contacts/Threads/TSContactThread.h b/src/Contacts/Threads/TSContactThread.h index 51c310136..edca70df0 100644 --- a/src/Contacts/Threads/TSContactThread.h +++ b/src/Contacts/Threads/TSContactThread.h @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN @interface TSContactThread : TSThread ++ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId; + + (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId transaction:(YapDatabaseReadWriteTransaction *)transaction; @@ -16,8 +18,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)contactIdentifier; -- (NSString *)contactIdentifier; - @end NS_ASSUME_NONNULL_END diff --git a/src/Contacts/Threads/TSContactThread.m b/src/Contacts/Threads/TSContactThread.m index 261d13632..87d66ba63 100644 --- a/src/Contacts/Threads/TSContactThread.m +++ b/src/Contacts/Threads/TSContactThread.m @@ -4,6 +4,7 @@ #import "TSContactThread.h" #import "ContactsUpdater.h" #import "TextSecureKitEnv.h" +#import #import NS_ASSUME_NONNULL_BEGIN @@ -55,6 +56,16 @@ NS_ASSUME_NONNULL_BEGIN return thread; } ++ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId +{ + __block TSContactThread *thread; + [[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + thread = [self getOrCreateThreadWithContactId:contactId transaction:transaction]; + }]; + + return thread; +} + - (NSString *)contactIdentifier { return [[self class] contactIdFromThreadId:self.uniqueId]; } diff --git a/src/Contacts/Threads/TSGroupThread.h b/src/Contacts/Threads/TSGroupThread.h index a4810e6f6..2aa6b9367 100644 --- a/src/Contacts/Threads/TSGroupThread.h +++ b/src/Contacts/Threads/TSGroupThread.h @@ -10,11 +10,15 @@ #import "TSThread.h" @interface TSGroupThread : TSThread + @property (nonatomic, strong) TSGroupModel *groupModel; + + (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadWriteTransaction *)transaction; ++ (instancetype)getOrCreateThreadWithGroupIdData:(NSData *)groupId; + ++ (instancetype)fetchWithGroupIdData:(NSData *)groupId; + (instancetype)threadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadTransaction *)transaction; -- (NSData *)groupId; @end diff --git a/src/Contacts/Threads/TSGroupThread.m b/src/Contacts/Threads/TSGroupThread.m index dab19c410..e1d27b65e 100644 --- a/src/Contacts/Threads/TSGroupThread.m +++ b/src/Contacts/Threads/TSGroupThread.m @@ -10,19 +10,51 @@ #define TSGroupThreadPrefix @"g" -- (instancetype)initWithGroupModel:(TSGroupModel *)groupModel { +- (instancetype)initWithGroupModel:(TSGroupModel *)groupModel +{ NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId]; + self = [super initWithUniqueId:uniqueIdentifier]; + if (!self) { + return self; + } - self = [super initWithUniqueId:uniqueIdentifier]; _groupModel = groupModel; + return self; } +- (instancetype)initWithGroupIdData:(NSData *)groupId +{ + TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:nil memberIds:nil image:nil groupId:groupId]; + + self = [self initWithGroupModel:groupModel]; + if (!self) { + return self; + } + + return self; +} -+ (instancetype)threadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadTransaction *)transaction { ++ (instancetype)threadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadTransaction *)transaction +{ return [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction]; } ++ (instancetype)fetchWithGroupIdData:(NSData *)groupId +{ + return [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId]]; +} + ++ (instancetype)getOrCreateThreadWithGroupIdData:(NSData *)groupId +{ + TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId]]; + if (!thread) { + thread = [[self alloc] initWithGroupIdData:groupId]; + [thread save]; + } + return thread; +} + + (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadWriteTransaction *)transaction { TSGroupThread *thread = @@ -35,24 +67,24 @@ return thread; } -- (BOOL)isGroupThread { - return true; -} - -- (NSData *)groupId { - return [[self class] groupIdFromThreadId:self.uniqueId]; ++ (NSString *)threadIdFromGroupId:(NSData *)groupId +{ + return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]]; } -- (NSString *)name { - return self.groupModel.groupName; ++ (NSData *)groupIdFromThreadId:(NSString *)threadId +{ + return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]]; } -+ (NSString *)threadIdFromGroupId:(NSData *)groupId { - return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]]; +- (BOOL)isGroupThread +{ + return true; } -+ (NSData *)groupIdFromThreadId:(NSString *)threadId { - return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]]; +- (NSString *)name +{ + return self.groupModel.groupName; } @end diff --git a/src/Messages/Attachments/OWSAttachmentsProcessor.h b/src/Messages/Attachments/OWSAttachmentsProcessor.h new file mode 100644 index 000000000..a35c854c0 --- /dev/null +++ b/src/Messages/Attachments/OWSAttachmentsProcessor.h @@ -0,0 +1,30 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class TSThread; +@class TSMessagesManager; +@class OWSSignalServiceProtosAttachmentPointer; + +/** + * Given incoming attachment protos, determines which we support. + * It can download those that we support and notifies threads when it receives unsupported attachments. + */ +@interface OWSAttachmentsProcessor : NSObject + +@property (nonatomic, readonly) NSArray *attachmentIds; +@property (nonatomic, readonly) NSArray *supportedAttachmentIds; +@property (nonatomic, readonly) BOOL hasSupportedAttachments; + +- (instancetype)initWithAttachmentPointersProtos:(NSArray *)attachmentProtos + timestamp:(uint64_t)timestamp + relay:(nullable NSString *)relay + avatarGroupId:(nullable NSData *)avatarGroupId + inThread:(TSThread *)thread + messagesManager:(TSMessagesManager *)messagesManager; + +- (void)fetchAttachmentsForMessageId:(nullable NSString *)messageId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Attachments/OWSAttachmentsProcessor.m b/src/Messages/Attachments/OWSAttachmentsProcessor.m new file mode 100644 index 000000000..ea245a642 --- /dev/null +++ b/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -0,0 +1,92 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSAttachmentsProcessor.h" +#import "MIMETypeUtil.h" +#import "OWSSignalServiceProtos.pb.h" +#import "TSAttachmentPointer.h" +#import "TSInfoMessage.h" +#import "TSMessage.h" +#import "TSMessagesManager+attachments.h" +#import "TSMessagesManager.h" +#import "TSThread.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSAttachmentsProcessor () + +@property (nonatomic, readonly) TSMessagesManager *messagesManager; +@property (nonatomic, readonly) NSArray *supportedAttachmentPointers; + +@end + +@implementation OWSAttachmentsProcessor + +- (instancetype)initWithAttachmentPointersProtos:(NSArray *)attachmentProtos + timestamp:(uint64_t)timestamp + relay:(nullable NSString *)relay + avatarGroupId:(nullable NSData *)avatarGroupId + inThread:(TSThread *)thread + messagesManager:(TSMessagesManager *)messagesManager; +{ + self = [super init]; + if (!self) { + return self; + } + + _messagesManager = messagesManager; + + NSMutableArray *attachmentIds = [NSMutableArray new]; + NSMutableArray *supportedAttachmentPointers = [NSMutableArray new]; + NSMutableArray *supportedAttachmentIds = [NSMutableArray new]; + + for (OWSSignalServiceProtosAttachmentPointer *attachmentProto in attachmentProtos) { + TSAttachmentPointer *pointer; + if (avatarGroupId) { + pointer = [[TSAttachmentPointer alloc] initWithIdentifier:attachmentProto.id + key:attachmentProto.key + contentType:attachmentProto.contentType + relay:relay + avatarOfGroupId:avatarGroupId]; + } else { + pointer = [[TSAttachmentPointer alloc] initWithIdentifier:attachmentProto.id + key:attachmentProto.key + contentType:attachmentProto.contentType + relay:relay]; + } + + [attachmentIds addObject:pointer.uniqueId]; + + if ([MIMETypeUtil isSupportedMIMEType:pointer.contentType]) { + [pointer save]; + [supportedAttachmentPointers addObject:pointer]; + [supportedAttachmentIds addObject:pointer.uniqueId]; + } else { + TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:timestamp + inThread:thread + messageType:TSInfoMessageTypeUnsupportedMessage]; + [infoMessage save]; + } + } + + _attachmentIds = [attachmentIds copy]; + _supportedAttachmentPointers = [supportedAttachmentPointers copy]; + _supportedAttachmentIds = [supportedAttachmentIds copy]; + + return self; +} + +- (void)fetchAttachmentsForMessageId:(nullable NSString *)messageId +{ + for (TSAttachmentPointer *attachmentPointer in self.supportedAttachmentPointers) { + [self.messagesManager retrieveAttachment:attachmentPointer messageId:messageId]; + } +} + +- (BOOL)hasSupportedAttachments +{ + return self.supportedAttachmentPointers.count > 0; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.h b/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.h new file mode 100644 index 000000000..01d67c400 --- /dev/null +++ b/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.h @@ -0,0 +1,19 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSOutgoingSyncMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@class TSOutgoingMessage; + +/** + * Notifies your other registered devices (if you have any) that you've sent a message. + * This way the message you just sent can appear on all your devices. + */ +@interface OWSOutgoingSentMessageTranscript : OWSOutgoingSyncMessage + +- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.m b/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.m new file mode 100644 index 000000000..b82642236 --- /dev/null +++ b/src/Messages/Interactions/OWSOutgoingSentMessageTranscript.m @@ -0,0 +1,65 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSOutgoingSentMessageTranscript.h" +#import "OWSSignalServiceProtos.pb.h" +#import "TSOutgoingMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TSOutgoingMessage (OWSOutgoingSentMessageTranscript) + +/** + * Normally this is private, but we need to embed this + * data structure within our own. + */ +- (OWSSignalServiceProtosDataMessage *)buildDataMessage; + +@end + +@interface OWSOutgoingSentMessageTranscript () + +@property (nonatomic, readonly) TSOutgoingMessage *message; + +@end + +@implementation OWSOutgoingSentMessageTranscript + +- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message +{ + self = [super init]; + + if (!self) { + return self; + } + + _message = message; + + return self; +} + +- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage +{ + OWSSignalServiceProtosSyncMessageSentBuilder *sentBuilder = [OWSSignalServiceProtosSyncMessageSentBuilder new]; + [sentBuilder setTimestamp:self.message.timestamp]; + [sentBuilder setDestination:self.message.recipientIdentifier]; + + OWSSignalServiceProtosDataMessage *dataMessage = [self.message buildDataMessage]; + [sentBuilder setMessage:dataMessage]; + + OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new]; + [syncMessageBuilder setSent:[sentBuilder build]]; + + return [syncMessageBuilder build]; +} + +- (NSData *)buildPlainTextData +{ + OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new]; + [contentBuilder setSyncMessage:[self buildSyncMessage]]; + + return [[contentBuilder build] data]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/OWSOutgoingSyncMessage.h b/src/Messages/Interactions/OWSOutgoingSyncMessage.h new file mode 100644 index 000000000..257d131c5 --- /dev/null +++ b/src/Messages/Interactions/OWSOutgoingSyncMessage.h @@ -0,0 +1,16 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "TSOutgoingMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Abstract base class used for the family of sync messages which take care + * of keeping your multiple registered devices consistent. E.g. sharing contacts, sharing groups, + * notifiying your devices of sent messages, and "read" receipts. + */ +@interface OWSOutgoingSyncMessage : TSOutgoingMessage + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/OWSOutgoingSyncMessage.m b/src/Messages/Interactions/OWSOutgoingSyncMessage.m new file mode 100644 index 000000000..e3cee10f1 --- /dev/null +++ b/src/Messages/Interactions/OWSOutgoingSyncMessage.m @@ -0,0 +1,16 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSOutgoingSyncMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSOutgoingSyncMessage + +- (BOOL)shouldSyncTranscript +{ + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSOutgoingMessage.h b/src/Messages/Interactions/TSOutgoingMessage.h index 297ccad73..23045cac3 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.h +++ b/src/Messages/Interactions/TSOutgoingMessage.h @@ -1,13 +1,12 @@ -// -// TSOutgoingMessage.h -// TextSecureKit -// // Created by Frederic Jacobs on 15/11/14. // Copyright (c) 2014 Open Whisper Systems. All rights reserved. -// #import "TSMessage.h" +NS_ASSUME_NONNULL_BEGIN + +@class OWSSignalServiceProtosDataMessage; + @interface TSOutgoingMessage : TSMessage typedef NS_ENUM(NSInteger, TSOutgoingMessageState) { @@ -18,4 +17,25 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState) { }; @property (nonatomic) TSOutgoingMessageState messageState; +@property BOOL hasSyncedTranscript; + +/** + * Signal Identifier (e.g. e164 number) or nil if in a group thread. + */ +- (nullable NSString *)recipientIdentifier; + +/** + * The data representation of this message, to be encrypted, before being sent. + */ +- (NSData *)buildPlainTextData; + +/** + * Should this message be synced to the users other registered devices? This is + * generally always true, except in the case of the sync messages themseleves + * (so we don't end up in an infinite loop). + */ +- (BOOL)shouldSyncTranscript; + @end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSOutgoingMessage.m b/src/Messages/Interactions/TSOutgoingMessage.m index 290bca160..1bc96d384 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.m +++ b/src/Messages/Interactions/TSOutgoingMessage.m @@ -2,8 +2,13 @@ // Copyright (c) 2014 Open Whisper Systems. All rights reserved. #import "TSOutgoingMessage.h" +#import "OWSSignalServiceProtos.pb.h" +#import "TSAttachmentStream.h" +#import "TSContactThread.h" #import "TSGroupThread.h" +NS_ASSUME_NONNULL_BEGIN + @implementation TSOutgoingMessage - (instancetype)initWithTimestamp:(uint64_t)timestamp @@ -18,6 +23,8 @@ } _messageState = TSOutgoingMessageStateAttemptingOut; + _hasSyncedTranscript = NO; + if ([thread isKindOfClass:[TSGroupThread class]]) { self.groupMetaMessage = TSGroupMessageDeliver; } else { @@ -27,4 +34,85 @@ return self; } +- (nullable NSString *)recipientIdentifier +{ + return self.thread.contactIdentifier; +} + +- (OWSSignalServiceProtosDataMessage *)buildDataMessage +{ + TSThread *thread = self.thread; + + OWSSignalServiceProtosDataMessageBuilder *builder = [OWSSignalServiceProtosDataMessageBuilder new]; + [builder setBody:self.body]; + BOOL processAttachments = YES; + if ([thread isKindOfClass:[TSGroupThread class]]) { + TSGroupThread *gThread = (TSGroupThread *)thread; + OWSSignalServiceProtosGroupContextBuilder *groupBuilder = [OWSSignalServiceProtosGroupContextBuilder new]; + + switch (self.groupMetaMessage) { + case TSGroupMessageQuit: + [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeQuit]; + break; + case TSGroupMessageUpdate: + case TSGroupMessageNew: { + if (gThread.groupModel.groupImage != nil && [self.attachmentIds count] == 1) { + id dbObject = [TSAttachmentStream fetchObjectWithUniqueID:self.attachmentIds[0]]; + if ([dbObject isKindOfClass:[TSAttachmentStream class]]) { + TSAttachmentStream *attachment = (TSAttachmentStream *)dbObject; + OWSSignalServiceProtosAttachmentPointerBuilder *attachmentbuilder = + [OWSSignalServiceProtosAttachmentPointerBuilder new]; + [attachmentbuilder setId:[attachment.identifier unsignedLongLongValue]]; + [attachmentbuilder setContentType:attachment.contentType]; + [attachmentbuilder setKey:attachment.encryptionKey]; + [groupBuilder setAvatar:[attachmentbuilder build]]; + processAttachments = NO; + } + } + [groupBuilder setMembersArray:gThread.groupModel.groupMemberIds]; + [groupBuilder setName:gThread.groupModel.groupName]; + [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeUpdate]; + break; + } + default: + [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeDeliver]; + break; + } + [groupBuilder setId:gThread.groupModel.groupId]; + [builder setGroup:groupBuilder.build]; + } + if (processAttachments) { + NSMutableArray *attachments = [NSMutableArray new]; + for (NSString *attachmentId in self.attachmentIds) { + id dbObject = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; + + if ([dbObject isKindOfClass:[TSAttachmentStream class]]) { + TSAttachmentStream *attachment = (TSAttachmentStream *)dbObject; + + OWSSignalServiceProtosAttachmentPointerBuilder *attachmentbuilder = + [OWSSignalServiceProtosAttachmentPointerBuilder new]; + [attachmentbuilder setId:[attachment.identifier unsignedLongLongValue]]; + [attachmentbuilder setContentType:attachment.contentType]; + [attachmentbuilder setKey:attachment.encryptionKey]; + + [attachments addObject:[attachmentbuilder build]]; + } + } + [builder setAttachmentsArray:attachments]; + } + return [builder build]; +} + +- (NSData *)buildPlainTextData +{ + return [[self buildDataMessage] data]; +} + +- (BOOL)shouldSyncTranscript +{ + return !self.hasSyncedTranscript; +} + @end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSIncomingSentMessageTranscript.h b/src/Messages/OWSIncomingSentMessageTranscript.h new file mode 100644 index 000000000..58fc09393 --- /dev/null +++ b/src/Messages/OWSIncomingSentMessageTranscript.h @@ -0,0 +1,22 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class OWSSignalServiceProtosSyncMessageSent; + +/** + * Represents notification of a message sent on our behalf from another device. + * E.g. When we send a message from Signal-Desktop we want to see it in our conversation on iPhone. + */ +@interface OWSIncomingSentMessageTranscript : NSObject + +- (instancetype)initWithProto:(OWSSignalServiceProtosSyncMessageSent *)sentProto relay:(NSString *)relay; + +/** + * Record an outgoing message based on the transcription + */ +- (void)record; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSIncomingSentMessageTranscript.m b/src/Messages/OWSIncomingSentMessageTranscript.m new file mode 100644 index 000000000..29667bf5c --- /dev/null +++ b/src/Messages/OWSIncomingSentMessageTranscript.m @@ -0,0 +1,99 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSIncomingSentMessageTranscript.h" +#import "OWSAttachmentsProcessor.h" +#import "OWSSignalServiceProtos.pb.h" +#import "TSMessagesManager.h" +#import "TSOutgoingMessage.h" +#import "TSThread.h" + +// Thread finding imports +#import "TSContactThread.h" +#import "TSGroupModel.h" +#import "TSGroupThread.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSIncomingSentMessageTranscript () + +@property (nonatomic, readonly) NSString *relay; +@property (nonatomic, readonly) OWSSignalServiceProtosDataMessage *dataMessage; +@property (nonatomic, readonly) NSString *recipientId; +@property (nonatomic, readonly) uint64_t timestamp; + +@end + +@implementation OWSIncomingSentMessageTranscript + +- (instancetype)initWithProto:(OWSSignalServiceProtosSyncMessageSent *)sentProto relay:(NSString *)relay +{ + self = [super init]; + if (!self) { + return self; + } + + _relay = relay; + _dataMessage = sentProto.message; + _recipientId = sentProto.destination; + _timestamp = sentProto.timestamp; + + return self; +} + + +- (void)record +{ + TSThread *thread; + + if (self.dataMessage.hasGroup) { + thread = [TSGroupThread getOrCreateThreadWithGroupIdData:self.dataMessage.group.id]; + } else { + thread = [TSContactThread getOrCreateThreadWithContactId:self.recipientId]; + } + + NSData *avatarGroupId; + NSArray *attachmentPointerProtos; + if (self.dataMessage.hasGroup && (self.dataMessage.group.type == OWSSignalServiceProtosGroupContextTypeUpdate)) { + avatarGroupId = self.dataMessage.group.id; + attachmentPointerProtos = @[ self.dataMessage.group.avatar ]; + } else { + attachmentPointerProtos = self.dataMessage.attachments; + } + + OWSAttachmentsProcessor *attachmentsProcessor = + [[OWSAttachmentsProcessor alloc] initWithAttachmentPointersProtos:attachmentPointerProtos + timestamp:self.timestamp + relay:self.relay + avatarGroupId:avatarGroupId + inThread:thread + messagesManager:[TSMessagesManager sharedManager]]; + + // TODO group updates. Currently desktop doesn't support group updates, so not a problem yet. + TSOutgoingMessage *outgoingMessage = + [[TSOutgoingMessage alloc] initWithTimestamp:self.timestamp + inThread:thread + messageBody:self.dataMessage.body + attachmentIds:attachmentsProcessor.attachmentIds]; + outgoingMessage.messageState = TSOutgoingMessageStateDelivered; + [outgoingMessage save]; + + [attachmentsProcessor fetchAttachmentsForMessageId:outgoingMessage.uniqueId]; + + // If there is an attachment + text, render the text here, as Signal-iOS renders two messages. + if (attachmentsProcessor.hasSupportedAttachments && self.dataMessage.body != nil + && ![self.dataMessage.body isEqualToString:@""]) { + + // render text *after* the attachment + uint64_t textMessageTimestamp = self.timestamp + 1000; + TSOutgoingMessage *textMessage = [[TSOutgoingMessage alloc] initWithTimestamp:textMessageTimestamp + inThread:thread + messageBody:self.dataMessage.body + attachmentIds:nil]; + textMessage.messageState = TSOutgoingMessageStateDelivered; + [textMessage save]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSLegacyMessageServiceParams.h b/src/Messages/OWSLegacyMessageServiceParams.h new file mode 100644 index 000000000..f15fface4 --- /dev/null +++ b/src/Messages/OWSLegacyMessageServiceParams.h @@ -0,0 +1,20 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSMessageServiceParams.h" + +NS_ASSUME_NONNULL_BEGIN +/** + * Contstructs the per-device-message parameters used when submitting a message to + * the Signal Web Service. Using a legacy parameter format. Cannot be used for Sync messages. + */ +@interface OWSLegacyMessageServiceParams : OWSMessageServiceParams + +- (instancetype)initWithType:(TSWhisperMessageType)type + recipientId:(NSString *)destination + device:(int)deviceId + body:(NSData *)body + registrationId:(int)registrationId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSLegacyMessageServiceParams.m b/src/Messages/OWSLegacyMessageServiceParams.m new file mode 100644 index 000000000..79901c508 --- /dev/null +++ b/src/Messages/OWSLegacyMessageServiceParams.m @@ -0,0 +1,33 @@ +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSLegacyMessageServiceParams.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSLegacyMessageServiceParams + ++ (NSDictionary *)JSONKeyPathsByPropertyKey +{ + NSMutableDictionary *keys = [[super JSONKeyPathsByPropertyKey] mutableCopy]; + [keys setObject:@"body" forKey:@"content"]; + return [keys copy]; +} + +- (instancetype)initWithType:(TSWhisperMessageType)type + recipientId:(NSString *)destination + device:(int)deviceId + body:(NSData *)body + registrationId:(int)registrationId +{ + self = [super initWithType:type recipientId:destination device:deviceId content:body registrationId:registrationId]; + if (!self) { + return self; + } + + return self; +} + + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSMessageServiceParams.h b/src/Messages/OWSMessageServiceParams.h new file mode 100644 index 000000000..796dbfe78 --- /dev/null +++ b/src/Messages/OWSMessageServiceParams.h @@ -0,0 +1,25 @@ +// Created by Frederic Jacobs on 18/11/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. + +#import "TSConstants.h" +#import + +/** + * Contstructs the per-device-message parameters used when submitting a message to + * the Signal Web Service. + */ +@interface OWSMessageServiceParams : MTLModel + +@property (nonatomic, readonly) int type; +@property (nonatomic, readonly) NSString *destination; +@property (nonatomic, readonly) int destinationDeviceId; +@property (nonatomic, readonly) int destinationRegistrationId; +@property (nonatomic, readonly) NSString *content; + +- (instancetype)initWithType:(TSWhisperMessageType)type + recipientId:(NSString *)destination + device:(int)deviceId + content:(NSData *)content + registrationId:(int)registrationId; + +@end diff --git a/src/Messages/OWSMessageServiceParams.m b/src/Messages/OWSMessageServiceParams.m new file mode 100644 index 000000000..cf0df0df4 --- /dev/null +++ b/src/Messages/OWSMessageServiceParams.m @@ -0,0 +1,36 @@ +// Created by Frederic Jacobs on 18/11/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. + +#import "OWSMessageServiceParams.h" +#import "NSData+Base64.h" +#import "TSConstants.h" + +@implementation OWSMessageServiceParams + ++ (NSDictionary *)JSONKeyPathsByPropertyKey +{ + return [NSDictionary mtl_identityPropertyMapWithModel:[self class]]; +} + +- (instancetype)initWithType:(TSWhisperMessageType)type + recipientId:(NSString *)destination + device:(int)deviceId + content:(NSData *)content + registrationId:(int)registrationId +{ + self = [super init]; + + if (!self) { + return self; + } + + _type = type; + _destination = destination; + _destinationDeviceId = deviceId; + _destinationRegistrationId = registrationId; + _content = [content base64EncodedString]; + + return self; +} + +@end diff --git a/src/Messages/TSMessagesManager+attachments.m b/src/Messages/TSMessagesManager+attachments.m index 9a60572a5..23a17f516 100644 --- a/src/Messages/TSMessagesManager+attachments.m +++ b/src/Messages/TSMessagesManager+attachments.m @@ -4,6 +4,7 @@ #import "Cryptography.h" #import "MIMETypeUtil.h" #import "NSDate+millisecondTimeStamp.h" +#import "OWSAttachmentsProcessor.h" #import "TSAttachmentPointer.h" #import "TSContactThread.h" #import "TSGroupModel.h" @@ -34,64 +35,36 @@ dispatch_queue_t attachmentsQueue() { - (void)handleReceivedMediaWithEnvelope:(OWSSignalServiceProtosEnvelope *)envelope dataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage { - // TODO extract group avatar handling rather than checking message type multiple times and forking logic. - - NSArray *attachmentsToRetrieve - = (dataMessage.hasGroup && (dataMessage.group.type == OWSSignalServiceProtosGroupContextTypeUpdate)) - ? [NSArray arrayWithObject:dataMessage.group.avatar] - : dataMessage.attachments; + NSData *avatarGroupId; + NSArray *attachmentPointerProtos; + if (dataMessage.hasGroup && (dataMessage.group.type == OWSSignalServiceProtosGroupContextTypeUpdate)) { + avatarGroupId = dataMessage.group.id; + attachmentPointerProtos = @[ dataMessage.group.avatar ]; + } else { + attachmentPointerProtos = dataMessage.attachments; + } - NSMutableArray *retrievedAttachments = [NSMutableArray array]; - __block BOOL shouldProcessMessage = YES; - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (OWSSignalServiceProtosAttachmentPointer *pointer in attachmentsToRetrieve) { - TSAttachmentPointer *attachmentPointer - = (dataMessage.hasGroup && (dataMessage.group.type == OWSSignalServiceProtosGroupContextTypeUpdate)) - ? [[TSAttachmentPointer alloc] initWithIdentifier:pointer.id - key:pointer.key - contentType:pointer.contentType - relay:envelope.relay - avatarOfGroupId:dataMessage.group.id] - : [[TSAttachmentPointer alloc] initWithIdentifier:pointer.id - key:pointer.key - contentType:pointer.contentType - relay:envelope.relay]; - - if ([MIMETypeUtil isSupportedMIMEType:attachmentPointer.contentType]) { - [attachmentPointer saveWithTransaction:transaction]; - [retrievedAttachments addObject:attachmentPointer.uniqueId]; - shouldProcessMessage = YES; - } else { - TSThread *thread = - [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - TSInfoMessage *infoMessage = - [[TSInfoMessage alloc] initWithTimestamp:envelope.timestamp - inThread:thread - messageType:TSInfoMessageTypeUnsupportedMessage]; - [infoMessage saveWithTransaction:transaction]; - shouldProcessMessage = NO; - } - } - }]; + TSThread *thread; + if (dataMessage.hasGroup) { + thread = [TSGroupThread getOrCreateThreadWithGroupIdData:dataMessage.group.id]; + } else { + thread = [TSContactThread getOrCreateThreadWithContactId:envelope.source]; + } - if (shouldProcessMessage) { - [self handleReceivedEnvelope:envelope - withDataMessage:dataMessage - attachmentIds:retrievedAttachments - completionBlock:^(NSString *messageIdentifier) { - for (NSString *pointerId in retrievedAttachments) { - dispatch_async(attachmentsQueue(), ^{ - __block TSAttachmentPointer *pointer; - - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - pointer = [TSAttachmentPointer fetchObjectWithUniqueID:pointerId - transaction:transaction]; - }]; - - [self retrieveAttachment:pointer messageId:messageIdentifier]; - }); - } - }]; + OWSAttachmentsProcessor *attachmentsProcessor = + [[OWSAttachmentsProcessor alloc] initWithAttachmentPointersProtos:attachmentPointerProtos + timestamp:envelope.timestamp + relay:envelope.relay + avatarGroupId:avatarGroupId + inThread:thread + messagesManager:[TSMessagesManager sharedManager]]; + + if (attachmentsProcessor.hasSupportedAttachments) { + TSIncomingMessage *possiblyCreatedMessage = + [self handleReceivedEnvelope:envelope + withDataMessage:dataMessage + attachmentIds:attachmentsProcessor.supportedAttachmentIds]; + [attachmentsProcessor fetchAttachmentsForMessageId:possiblyCreatedMessage.uniqueId]; } } @@ -253,9 +226,11 @@ dispatch_queue_t attachmentsQueue() { [TSGroupThread getOrCreateThreadWithGroupModel:emptyModelToFillOutId transaction:transaction]; gThread.groupModel.groupImage = [stream image]; - // No need to keep the attachment around after assigning the image. + // Avatars are stored directly in the database, so there's no need to keep the attachment around after + // assigning the image. [stream removeWithTransaction:transaction]; + [[TSMessage fetchObjectWithUniqueID:messageId] saveWithTransaction:transaction]; [gThread saveWithTransaction:transaction]; } else { // Causing message to be reloaded in view. diff --git a/src/Messages/TSMessagesManager+sendMessages.m b/src/Messages/TSMessagesManager+sendMessages.m index 1d574d940..ab7b70147 100644 --- a/src/Messages/TSMessagesManager+sendMessages.m +++ b/src/Messages/TSMessagesManager+sendMessages.m @@ -3,6 +3,9 @@ #import "ContactsUpdater.h" #import "NSData+messagePadding.h" +#import "OWSLegacyMessageServiceParams.h" +#import "OWSMessageServiceParams.h" +#import "OWSOutgoingSentMessageTranscript.h" #import "PreKeyBundle+jsonDict.h" #import "TSAccountManager.h" #import "TSAttachmentStream.h" @@ -11,7 +14,6 @@ #import "TSInfoMessage.h" #import "TSMessagesManager+sendMessages.h" #import "TSNetworkManager.h" -#import "TSServerMessage.h" #import "TSStorageHeaders.h" #import #import @@ -228,13 +230,13 @@ dispatch_queue_t sendingQueue() { [self saveMessage:message withState:TSOutgoingMessageStateUnsent]; } - - (void)sendMessage:(TSOutgoingMessage *)message toRecipient:(SignalRecipient *)recipient inThread:(TSThread *)thread withAttemps:(int)remainingAttempts success:(successSendingCompletionBlock)successBlock - failure:(failedSendingCompletionBlock)failureBlock { + failure:(failedSendingCompletionBlock)failureBlock +{ if (remainingAttempts > 0) { remainingAttempts -= 1; @@ -247,11 +249,11 @@ dispatch_queue_t sendingQueue() { [[TSNetworkManager sharedManager] makeRequest:request success:^(NSURLSessionDataTask *task, id responseObject) { - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [recipient saveWithTransaction:transaction]; - }]; - [self handleMessageSent:message]; - BLOCK_SAFE_RUN(successBlock); + dispatch_async(sendingQueue(), ^{ + [recipient save]; + [self handleMessageSent:message]; + BLOCK_SAFE_RUN(successBlock); + }); } failure:^(NSURLSessionDataTask *task, NSError *error) { NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; @@ -349,8 +351,31 @@ dispatch_queue_t sendingQueue() { }]; } -- (void)handleMessageSent:(TSOutgoingMessage *)message { +- (void)handleMessageSent:(TSOutgoingMessage *)message +{ [self saveMessage:message withState:TSOutgoingMessageStateSent]; + if (message.shouldSyncTranscript) { + message.hasSyncedTranscript = YES; + [self sendSyncTranscriptForMessage:message]; + } +} + +- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message +{ + OWSOutgoingSentMessageTranscript *sentMessageTranscript = + [[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message]; + + SignalRecipient *localUser = [SignalRecipient recipientWithTextSecureIdentifier:[TSStorageManager localNumber]]; + [self sendMessage:sentMessageTranscript + toRecipient:localUser + inThread:message.thread + withAttemps:RETRY_ATTEMPTS + success:^{ + DDLogInfo(@"Succesfully sent sync transcript."); + } + failure:^{ + DDLogInfo(@"Failed to send sync transcript."); + }]; } - (NSArray *)deviceMessages:(TSOutgoingMessage *)message @@ -358,15 +383,18 @@ dispatch_queue_t sendingQueue() { inThread:(TSThread *)thread { NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count]; - TSStorageManager *storage = [TSStorageManager sharedManager]; - NSData *plainText = [self plainTextForMessage:message inThread:thread]; + NSData *plainText = [message buildPlainTextData]; for (NSNumber *deviceNumber in recipient.devices) { @try { + // DEPRECATED - Remove after all clients have been upgraded. + BOOL isLegacyMessage = ![message isKindOfClass:[OWSOutgoingSyncMessage class]]; + NSDictionary *messageDict = [self encryptedMessageWithPlaintext:plainText toRecipient:recipient.uniqueId deviceId:deviceNumber - keyingStorage:storage]; + keyingStorage:[TSStorageManager sharedManager] + legacy:isLegacyMessage]; if (messageDict) { [messagesArray addObject:messageDict]; } else { @@ -390,7 +418,9 @@ dispatch_queue_t sendingQueue() { - (NSDictionary *)encryptedMessageWithPlaintext:(NSData *)plainText toRecipient:(NSString *)identifier deviceId:(NSNumber *)deviceNumber - keyingStorage:(TSStorageManager *)storage { + keyingStorage:(TSStorageManager *)storage + legacy:(BOOL)isLegacymessage +{ if (![storage containsSession:identifier deviceId:[deviceNumber intValue]]) { __block dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block PreKeyBundle *bundle; @@ -450,15 +480,24 @@ dispatch_queue_t sendingQueue() { NSData *serializedMessage = encryptedMessage.serialized; TSWhisperMessageType messageType = [self messageTypeForCipherMessage:encryptedMessage]; - - TSServerMessage *serverMessage = [[TSServerMessage alloc] initWithType:messageType - destination:identifier - device:[deviceNumber intValue] - body:serializedMessage - registrationId:cipher.remoteRegistrationId]; + OWSMessageServiceParams *messageParams; + // DEPRECATED - Remove after all clients have been upgraded. + if (isLegacymessage) { + messageParams = [[OWSLegacyMessageServiceParams alloc] initWithType:messageType + recipientId:identifier + device:[deviceNumber intValue] + body:serializedMessage + registrationId:cipher.remoteRegistrationId]; + } else { + messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType + recipientId:identifier + device:[deviceNumber intValue] + content:serializedMessage + registrationId:cipher.remoteRegistrationId]; + } NSError *error; - NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:serverMessage error:&error]; + NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; if (error) { DDLogError(@"Error while making JSON dictionary of message: %@", error.debugDescription); @@ -506,68 +545,6 @@ dispatch_queue_t sendingQueue() { } } - -- (NSData *)plainTextForMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread { - OWSSignalServiceProtosDataMessageBuilder *builder = [OWSSignalServiceProtosDataMessageBuilder new]; - [builder setBody:message.body]; - BOOL processAttachments = YES; - if ([thread isKindOfClass:[TSGroupThread class]]) { - TSGroupThread *gThread = (TSGroupThread *)thread; - OWSSignalServiceProtosGroupContextBuilder *groupBuilder = [OWSSignalServiceProtosGroupContextBuilder new]; - - switch (message.groupMetaMessage) { - case TSGroupMessageQuit: - [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeQuit]; - break; - case TSGroupMessageUpdate: - case TSGroupMessageNew: { - if (gThread.groupModel.groupImage != nil && [message.attachmentIds count] == 1) { - id dbObject = [TSAttachmentStream fetchObjectWithUniqueID:message.attachmentIds[0]]; - if ([dbObject isKindOfClass:[TSAttachmentStream class]]) { - TSAttachmentStream *attachment = (TSAttachmentStream *)dbObject; - OWSSignalServiceProtosAttachmentPointerBuilder *attachmentbuilder = - [OWSSignalServiceProtosAttachmentPointerBuilder new]; - [attachmentbuilder setId:[attachment.identifier unsignedLongLongValue]]; - [attachmentbuilder setContentType:attachment.contentType]; - [attachmentbuilder setKey:attachment.encryptionKey]; - [groupBuilder setAvatar:[attachmentbuilder build]]; - processAttachments = NO; - } - } - [groupBuilder setMembersArray:gThread.groupModel.groupMemberIds]; - [groupBuilder setName:gThread.groupModel.groupName]; - [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeUpdate]; - break; - } - default: - [groupBuilder setType:OWSSignalServiceProtosGroupContextTypeDeliver]; - break; - } - [groupBuilder setId:gThread.groupModel.groupId]; - [builder setGroup:groupBuilder.build]; - } - if (processAttachments) { - NSMutableArray *attachments = [NSMutableArray new]; - for (NSString *attachmentId in message.attachmentIds) { - id dbObject = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; - - if ([dbObject isKindOfClass:[TSAttachmentStream class]]) { - TSAttachmentStream *attachment = (TSAttachmentStream *)dbObject; - - OWSSignalServiceProtosAttachmentPointerBuilder *attachmentbuilder = - [OWSSignalServiceProtosAttachmentPointerBuilder new]; - [attachmentbuilder setId:[attachment.identifier unsignedLongLongValue]]; - [attachmentbuilder setContentType:attachment.contentType]; - [attachmentbuilder setKey:attachment.encryptionKey]; - - [attachments addObject:[attachmentbuilder build]]; - } - } - [builder setAttachmentsArray:attachments]; - } - return [builder.build data]; -} - - (void)handleStaleDevicesWithResponse:(NSData *)responseData recipientId:(NSString *)identifier { dispatch_async(sendingQueue(), ^{ NSDictionary *serialization = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil]; diff --git a/src/Messages/TSMessagesManager.h b/src/Messages/TSMessagesManager.h index 752f81476..5063df57a 100644 --- a/src/Messages/TSMessagesManager.h +++ b/src/Messages/TSMessagesManager.h @@ -22,10 +22,16 @@ outgoingMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread; -- (void)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope - withDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage - attachmentIds:(NSArray *)attachmentIds - completionBlock:(void (^)(NSString *messageIdentifier))completionBlock; +/** + * Processes all kinds of incoming envelopes with a data message, along with any attachments. + * + * @returns + * If an incoming message is created, it will be returned. If it is, for example, a group update, + * no incoming message is created, so nil will be returned. + */ +- (TSIncomingMessage *)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope + withDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage + attachmentIds:(NSArray *)attachmentIds; - (void)handleSendToMyself:(TSOutgoingMessage *)outgoingMessage; diff --git a/src/Messages/TSMessagesManager.m b/src/Messages/TSMessagesManager.m index 63ffbf5aa..15f982c7c 100644 --- a/src/Messages/TSMessagesManager.m +++ b/src/Messages/TSMessagesManager.m @@ -3,6 +3,7 @@ #import "TSMessagesManager.h" #import "NSData+messagePadding.h" +#import "OWSIncomingSentMessageTranscript.h" #import "TSAccountManager.h" #import "TSAttachmentStream.h" #import "TSCall.h" @@ -128,20 +129,20 @@ return; } - OWSSignalServiceProtosDataMessage *dataMessage; - if (messageEnvelope.hasContent) { // New style content payload + if (messageEnvelope.hasContent) { OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData]; - dataMessage = content.dataMessage; + if (content.hasSyncMessage) { + [self handleIncomingEnvelope:messageEnvelope withSyncMessage:content.syncMessage]; + } else if (content.dataMessage) { + [self handleIncomingEnvelope:messageEnvelope withDataMessage:content.dataMessage]; + } } else if (messageEnvelope.hasLegacyMessage) { // DEPRECATED - Remove after all clients have been upgraded. - dataMessage = [OWSSignalServiceProtosDataMessage parseFromData:plaintextData]; + OWSSignalServiceProtosDataMessage *dataMessage = + [OWSSignalServiceProtosDataMessage parseFromData:plaintextData]; + [self handleIncomingEnvelope:messageEnvelope withDataMessage:dataMessage]; + } else { + DDLogWarn(@"Ignoring content that has no dataMessage or syncMessage."); } - - if (!dataMessage) { - DDLogWarn(@"Ignoring content that has no dataMessage."); - return; - } - - [self handleIncomingEnvelope:messageEnvelope withDataMessage:dataMessage]; } } @@ -175,23 +176,20 @@ return; } - OWSSignalServiceProtosDataMessage *dataMessage; if (preKeyEnvelope.hasContent) { OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData]; - if (content.hasDataMessage) { - dataMessage = content.dataMessage; + if (content.hasSyncMessage) { + [self handleIncomingEnvelope:preKeyEnvelope withSyncMessage:content.syncMessage]; + } else if (content.dataMessage) { + [self handleIncomingEnvelope:preKeyEnvelope withDataMessage:content.dataMessage]; } - } else if (preKeyEnvelope.hasLegacyMessage) { - // DEPRECATED - Remove after all clients have been upgraded. - dataMessage = [OWSSignalServiceProtosDataMessage parseFromData:plaintextData]; + } else if (preKeyEnvelope.hasLegacyMessage) { // DEPRECATED - Remove after all clients have been upgraded. + OWSSignalServiceProtosDataMessage *dataMessage = + [OWSSignalServiceProtosDataMessage parseFromData:plaintextData]; + [self handleIncomingEnvelope:preKeyEnvelope withDataMessage:dataMessage]; + } else { + DDLogWarn(@"Ignoring content that has no dataMessage or syncMessage."); } - - if (!dataMessage) { - DDLogError(@"unable to ascertain decrypted dataMessage from preKeyEnvelope"); - return; - } - - [self handleIncomingEnvelope:preKeyEnvelope withDataMessage:dataMessage]; } } @@ -229,6 +227,19 @@ } } +- (void)handleIncomingEnvelope:(OWSSignalServiceProtosEnvelope *)messageEnvelope + withSyncMessage:(OWSSignalServiceProtosSyncMessage *)syncMessage +{ + if (syncMessage.hasSent) { + DDLogInfo(@"Received sent message transcription"); + OWSIncomingSentMessageTranscript *transcript = + [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent relay:messageEnvelope.relay]; + [transcript record]; + } else { + DDLogWarn(@"Ignoring unsupported sync message."); + } +} + - (void)handleEndSessionMessageWithEnvelope:(OWSSignalServiceProtosEnvelope *)endSessionEnvelope dataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage { @@ -250,7 +261,7 @@ - (void)handleReceivedTextMessageWithEnvelope:(OWSSignalServiceProtosEnvelope *)textMessageEnvelope dataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage { - [self handleReceivedEnvelope:textMessageEnvelope withDataMessage:dataMessage attachmentIds:@[] completionBlock:nil]; + [self handleReceivedEnvelope:textMessageEnvelope withDataMessage:dataMessage attachmentIds:@[]]; } - (void)handleSendToMyself:(TSOutgoingMessage *)outgoingMessage @@ -267,17 +278,17 @@ }]; } -- (void)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope - withDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage - attachmentIds:(NSArray *)attachmentIds - completionBlock:(void (^)(NSString *messageIdentifier))completionBlock +- (TSIncomingMessage *)handleReceivedEnvelope:(OWSSignalServiceProtosEnvelope *)envelope + withDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage + attachmentIds:(NSArray *)attachmentIds { uint64_t timeStamp = envelope.timestamp; NSString *body = dataMessage.body; NSData *groupId = dataMessage.hasGroup ? dataMessage.group.id : nil; + __block TSIncomingMessage *incomingMessage; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSIncomingMessage *incomingMessage; TSThread *thread; if (groupId) { NSMutableArray *uniqueMemberIds = [[[NSSet setWithArray:dataMessage.group.members] allObjects] mutableCopy]; @@ -377,10 +388,6 @@ [incomingMessage saveWithTransaction:transaction]; } - if (completionBlock) { - completionBlock(incomingMessage.uniqueId); - } - NSString *name = [thread name]; if (incomingMessage && thread) { @@ -390,6 +397,8 @@ inThread:thread]; } }]; + + return incomingMessage; } - (void)processException:(NSException *)exception envelope:(OWSSignalServiceProtosEnvelope *)envelope diff --git a/src/Messages/TSServerMessage.h b/src/Messages/TSServerMessage.h deleted file mode 100644 index bd5a17986..000000000 --- a/src/Messages/TSServerMessage.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// TSServerMessage.h -// TextSecureKit -// -// Created by Frederic Jacobs on 18/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. -// - -#import -#import "TSConstants.h" - -@interface TSServerMessage : MTLModel - -- (instancetype)initWithType:(TSWhisperMessageType)type - destination:(NSString *)destination - device:(int)deviceId - body:(NSData *)body - registrationId:(int)registrationId; - -@end diff --git a/src/Messages/TSServerMessage.m b/src/Messages/TSServerMessage.m deleted file mode 100644 index e53a39b7a..000000000 --- a/src/Messages/TSServerMessage.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// TSServerMessage.m -// TextSecureKit -// -// Created by Frederic Jacobs on 18/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. -// - -#import "TSConstants.h" -#import "TSServerMessage.h" - -#import "NSData+Base64.h" - -@interface TSServerMessage () - -@property int type; -@property NSString *destination; -@property int destinationDeviceId; -@property int destinationRegistrationId; -@property NSString *body; - -@end - -@implementation TSServerMessage - -+ (NSDictionary *)JSONKeyPathsByPropertyKey { - return [NSDictionary mtl_identityPropertyMapWithModel:[TSServerMessage class]]; -} - -- (instancetype)initWithType:(TSWhisperMessageType)type - destination:(NSString *)destination - device:(int)deviceId - body:(NSData *)body - registrationId:(int)registrationId { - self = [super init]; - - if (self) { - _type = type; - _destination = destination; - _destinationDeviceId = deviceId; - _destinationRegistrationId = registrationId; - _body = [body base64EncodedString]; - } - - return self; -} - -@end