2017-01-23 00:09:38 +01:00
|
|
|
//
|
2018-01-11 16:25:13 +01:00
|
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
2017-01-23 00:09:38 +01:00
|
|
|
//
|
2016-10-08 01:17:38 +02:00
|
|
|
|
|
|
|
#import "OWSMessageSender.h"
|
2017-11-29 21:27:19 +01:00
|
|
|
#import "AppContext.h"
|
2017-06-08 05:21:25 +02:00
|
|
|
#import "NSData+keyVersionByte.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import "NSData+messagePadding.h"
|
2018-04-18 02:53:27 +02:00
|
|
|
#import "NSDate+OWS.h"
|
2018-04-04 22:26:41 +02:00
|
|
|
#import "NSError+MessageSending.h"
|
2017-12-15 19:03:03 +01:00
|
|
|
#import "OWSBackgroundTask.h"
|
2017-04-03 20:42:04 +02:00
|
|
|
#import "OWSBlockingManager.h"
|
2018-05-03 17:31:06 +02:00
|
|
|
#import "OWSContact.h"
|
2017-02-16 00:32:27 +01:00
|
|
|
#import "OWSDevice.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import "OWSDisappearingMessagesJob.h"
|
2016-10-08 01:17:38 +02:00
|
|
|
#import "OWSError.h"
|
2017-06-06 20:12:50 +02:00
|
|
|
#import "OWSIdentityManager.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import "OWSMessageServiceParams.h"
|
2018-04-04 22:26:41 +02:00
|
|
|
#import "OWSOperation.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import "OWSOutgoingSentMessageTranscript.h"
|
|
|
|
#import "OWSOutgoingSyncMessage.h"
|
2018-03-05 15:30:58 +01:00
|
|
|
#import "OWSPrimaryStorage+PreKeyStore.h"
|
|
|
|
#import "OWSPrimaryStorage+SignedPreKeyStore.h"
|
|
|
|
#import "OWSPrimaryStorage+sessionStore.h"
|
|
|
|
#import "OWSPrimaryStorage.h"
|
2018-03-02 04:29:59 +01:00
|
|
|
#import "OWSRequestFactory.h"
|
2018-04-04 22:26:41 +02:00
|
|
|
#import "OWSUploadOperation.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import "PreKeyBundle+jsonDict.h"
|
|
|
|
#import "SignalRecipient.h"
|
|
|
|
#import "TSAccountManager.h"
|
|
|
|
#import "TSAttachmentStream.h"
|
|
|
|
#import "TSContactThread.h"
|
|
|
|
#import "TSGroupThread.h"
|
|
|
|
#import "TSIncomingMessage.h"
|
|
|
|
#import "TSInfoMessage.h"
|
|
|
|
#import "TSInvalidIdentityKeySendingErrorMessage.h"
|
2016-10-08 01:17:38 +02:00
|
|
|
#import "TSNetworkManager.h"
|
|
|
|
#import "TSOutgoingMessage.h"
|
2017-02-10 19:20:11 +01:00
|
|
|
#import "TSPreKeyManager.h"
|
2018-04-04 22:26:41 +02:00
|
|
|
#import "TSQuotedMessage.h"
|
2018-05-18 19:55:22 +02:00
|
|
|
#import "TSSocketManager.h"
|
2016-10-08 01:17:38 +02:00
|
|
|
#import "TSThread.h"
|
2017-09-21 17:55:25 +02:00
|
|
|
#import "Threading.h"
|
2016-10-14 23:00:29 +02:00
|
|
|
#import <AxolotlKit/AxolotlExceptions.h>
|
|
|
|
#import <AxolotlKit/CipherMessage.h>
|
|
|
|
#import <AxolotlKit/PreKeyBundle.h>
|
|
|
|
#import <AxolotlKit/SessionBuilder.h>
|
|
|
|
#import <AxolotlKit/SessionCipher.h>
|
|
|
|
#import <TwistedOakCollapsingFutures/CollapsingFutures.h>
|
2016-10-08 01:17:38 +02:00
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
2018-04-11 21:21:17 +02:00
|
|
|
const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
|
2018-01-11 23:41:13 +01:00
|
|
|
|
2017-04-07 01:11:04 +02:00
|
|
|
void AssertIsOnSendingQueue()
|
|
|
|
{
|
2017-04-07 18:46:42 +02:00
|
|
|
#ifdef DEBUG
|
2017-12-01 23:38:41 +01:00
|
|
|
if (@available(iOS 10.0, *)) {
|
2017-04-07 01:11:04 +02:00
|
|
|
dispatch_assert_queue([OWSDispatch sendingQueue]);
|
|
|
|
} // else, skip assert as it's a development convenience.
|
2017-04-07 18:46:42 +02:00
|
|
|
#endif
|
2017-04-07 01:11:04 +02:00
|
|
|
}
|
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
2017-03-17 17:26:25 +01:00
|
|
|
/**
|
|
|
|
* OWSSendMessageOperation encapsulates all the work associated with sending a message, e.g. uploading attachments,
|
2017-03-20 19:57:05 +01:00
|
|
|
* getting proper keys, and retrying upon failure.
|
2017-03-17 17:26:25 +01:00
|
|
|
*
|
|
|
|
* Used by `OWSMessageSender` to serialize message sending, ensuring that messages are emitted in the order they
|
|
|
|
* were sent.
|
|
|
|
*/
|
2018-04-04 22:26:41 +02:00
|
|
|
@interface OWSSendMessageOperation : OWSOperation
|
2017-03-17 17:26:25 +01:00
|
|
|
|
|
|
|
- (instancetype)init NS_UNAVAILABLE;
|
|
|
|
- (instancetype)initWithMessage:(TSOutgoingMessage *)message
|
|
|
|
messageSender:(OWSMessageSender *)messageSender
|
2018-04-04 22:26:41 +02:00
|
|
|
dbConnection:(YapDatabaseConnection *)dbConnection
|
|
|
|
success:(void (^)(void))aSuccessHandler
|
|
|
|
failure:(void (^)(NSError *_Nonnull error))aFailureHandler NS_DESIGNATED_INITIALIZER;
|
2017-03-17 17:26:25 +01:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2017-04-10 16:35:43 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
2017-03-17 17:26:25 +01:00
|
|
|
@interface OWSMessageSender (OWSSendMessageOperation)
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
2017-11-08 20:04:51 +01:00
|
|
|
success:(void (^)(void))successHandler
|
2017-04-05 01:44:14 +02:00
|
|
|
failure:(RetryableFailureHandler)failureHandler;
|
2017-03-17 17:26:25 +01:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
2017-04-10 16:35:43 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
2017-03-17 17:26:25 +01:00
|
|
|
@interface OWSSendMessageOperation ()
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) TSOutgoingMessage *message;
|
|
|
|
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
2018-04-04 22:26:41 +02:00
|
|
|
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
2017-11-15 12:39:10 +01:00
|
|
|
@property (nonatomic, readonly) void (^successHandler)(void);
|
2017-03-17 17:26:25 +01:00
|
|
|
@property (nonatomic, readonly) void (^failureHandler)(NSError *_Nonnull error);
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2017-04-10 16:35:43 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
2017-03-17 17:26:25 +01:00
|
|
|
@implementation OWSSendMessageOperation
|
|
|
|
|
|
|
|
- (instancetype)initWithMessage:(TSOutgoingMessage *)message
|
|
|
|
messageSender:(OWSMessageSender *)messageSender
|
2018-04-04 22:26:41 +02:00
|
|
|
dbConnection:(YapDatabaseConnection *)dbConnection
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(void (^)(NSError *_Nonnull error))failureHandler
|
2017-03-17 17:26:25 +01:00
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
self.remainingRetries = 6;
|
2017-03-17 17:26:25 +01:00
|
|
|
_message = message;
|
|
|
|
_messageSender = messageSender;
|
2018-04-04 22:26:41 +02:00
|
|
|
_dbConnection = dbConnection;
|
|
|
|
_successHandler = successHandler;
|
|
|
|
_failureHandler = failureHandler;
|
2017-03-17 17:26:25 +01:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
#pragma mark - OWSOperation overrides
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
- (nullable NSError *)checkForPreconditionError
|
2017-03-17 17:26:25 +01:00
|
|
|
{
|
2018-04-04 22:26:41 +02:00
|
|
|
for (NSOperation *dependency in self.dependencies) {
|
|
|
|
if (![dependency isKindOfClass:[OWSOperation class]]) {
|
|
|
|
NSString *errorDescription =
|
|
|
|
[NSString stringWithFormat:@"%@ unknown dependency: %@", self.logTag, dependency.class];
|
|
|
|
NSError *assertionError = OWSErrorMakeAssertionError(errorDescription);
|
|
|
|
return assertionError;
|
|
|
|
}
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
OWSOperation *upload = (OWSOperation *)dependency;
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
// Cannot proceed if dependency failed - surface the dependency's error.
|
|
|
|
NSError *_Nullable dependencyError = upload.failingError;
|
|
|
|
if (dependencyError) {
|
|
|
|
return dependencyError;
|
|
|
|
}
|
|
|
|
}
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
// Sanity check preconditions
|
|
|
|
if (self.message.hasAttachments) {
|
|
|
|
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
|
|
|
TSAttachmentStream *attachmentStream
|
|
|
|
= (TSAttachmentStream *)[self.message attachmentWithTransaction:transaction];
|
|
|
|
OWSAssert(attachmentStream);
|
|
|
|
OWSAssert([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
|
|
|
OWSAssert(attachmentStream.serverId);
|
|
|
|
OWSAssert(attachmentStream.isUploaded);
|
|
|
|
}];
|
|
|
|
}
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
return nil;
|
|
|
|
}
|
2017-03-17 17:26:25 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
- (void)run
|
2017-03-17 17:26:25 +01:00
|
|
|
{
|
2017-11-14 19:41:01 +01:00
|
|
|
// If the message has been deleted, abort send.
|
2017-11-15 23:53:16 +01:00
|
|
|
if (self.message.shouldBeSaved && ![TSOutgoingMessage fetchObjectWithUniqueID:self.message.uniqueId]) {
|
2017-11-14 19:41:01 +01:00
|
|
|
DDLogInfo(@"%@ aborting message send; message deleted.", self.logTag);
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(
|
|
|
|
OWSErrorCodeMessageDeletedBeforeSent, @"Message was deleted before it could be sent.");
|
2018-04-04 22:26:41 +02:00
|
|
|
error.isFatal = YES;
|
|
|
|
[self reportError:error];
|
2017-11-14 19:41:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
[self.messageSender sendMessageToService:self.message
|
|
|
|
success:^{
|
|
|
|
[self reportSuccess];
|
2017-04-05 01:44:14 +02:00
|
|
|
}
|
2018-04-04 22:26:41 +02:00
|
|
|
failure:^(NSError *error) {
|
|
|
|
[self reportError:error];
|
|
|
|
}];
|
2017-03-17 17:26:25 +01:00
|
|
|
}
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
- (void)didSucceed
|
2017-03-17 17:26:25 +01:00
|
|
|
{
|
2018-04-30 16:21:58 +02:00
|
|
|
if (self.message.messageState != TSOutgoingMessageStateSent) {
|
|
|
|
OWSProdLogAndFail(@"%@ unexpected message status: %@", self.logTag, self.message.statusDescription);
|
|
|
|
}
|
2018-04-23 16:30:51 +02:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
self.successHandler();
|
|
|
|
}
|
2017-06-17 19:41:29 +02:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
- (void)didFailWithError:(NSError *)error
|
|
|
|
{
|
|
|
|
[self.message updateWithSendingError:error];
|
|
|
|
|
|
|
|
DDLogDebug(@"%@ failed with error: %@", self.logTag, error);
|
|
|
|
self.failureHandler(error);
|
2017-03-17 17:26:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
int const OWSMessageSenderRetryAttempts = 3;
|
|
|
|
NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException";
|
2016-11-10 15:59:07 +01:00
|
|
|
NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2016-10-08 01:17:38 +02:00
|
|
|
@interface OWSMessageSender ()
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) TSNetworkManager *networkManager;
|
2018-03-05 15:30:58 +01:00
|
|
|
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
|
2017-04-03 20:42:04 +02:00
|
|
|
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
|
2016-10-14 23:00:29 +02:00
|
|
|
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
|
|
|
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
|
2017-03-23 19:35:30 +01:00
|
|
|
@property (atomic, readonly) NSMutableDictionary<NSString *, NSOperationQueue *> *sendingQueueMap;
|
2016-10-08 01:17:38 +02:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation OWSMessageSender
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager
|
2018-03-05 15:30:58 +01:00
|
|
|
primaryStorage:(OWSPrimaryStorage *)primaryStorage
|
2016-10-14 23:00:29 +02:00
|
|
|
contactsManager:(id<ContactsManagerProtocol>)contactsManager
|
2016-10-08 01:17:38 +02:00
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
_networkManager = networkManager;
|
2018-03-05 15:30:58 +01:00
|
|
|
_primaryStorage = primaryStorage;
|
2016-10-14 23:00:29 +02:00
|
|
|
_contactsManager = contactsManager;
|
2017-03-23 17:41:15 +01:00
|
|
|
_sendingQueueMap = [NSMutableDictionary new];
|
2018-03-05 15:30:58 +01:00
|
|
|
_dbConnection = primaryStorage.newDatabaseConnection;
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2017-04-01 00:45:46 +02:00
|
|
|
OWSSingletonAssert();
|
|
|
|
|
2016-10-08 01:17:38 +02:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2017-04-03 20:42:04 +02:00
|
|
|
- (void)setBlockingManager:(OWSBlockingManager *)blockingManager
|
2017-04-01 22:47:16 +02:00
|
|
|
{
|
|
|
|
OWSAssert(blockingManager);
|
|
|
|
OWSAssert(!_blockingManager);
|
|
|
|
|
|
|
|
_blockingManager = blockingManager;
|
|
|
|
}
|
|
|
|
|
2017-03-23 17:41:15 +01:00
|
|
|
- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message
|
|
|
|
{
|
|
|
|
OWSAssert(message);
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
|
2017-03-23 17:41:15 +01:00
|
|
|
NSString *kDefaultQueueKey = @"kDefaultQueueKey";
|
|
|
|
NSString *queueKey = message.uniqueThreadId ?: kDefaultQueueKey;
|
|
|
|
OWSAssert(queueKey.length > 0);
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
if ([kDefaultQueueKey isEqualToString:queueKey]) {
|
|
|
|
// when do we get here?
|
|
|
|
DDLogDebug(@"%@ using default message queue", self.logTag);
|
|
|
|
}
|
|
|
|
|
2017-03-23 17:41:15 +01:00
|
|
|
@synchronized(self)
|
|
|
|
{
|
|
|
|
NSOperationQueue *sendingQueue = self.sendingQueueMap[queueKey];
|
|
|
|
|
|
|
|
if (!sendingQueue) {
|
|
|
|
sendingQueue = [NSOperationQueue new];
|
|
|
|
sendingQueue.qualityOfService = NSOperationQualityOfServiceUserInitiated;
|
|
|
|
sendingQueue.maxConcurrentOperationCount = 1;
|
|
|
|
|
|
|
|
self.sendingQueueMap[queueKey] = sendingQueue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sendingQueue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 19:21:31 +01:00
|
|
|
- (void)enqueueMessage:(TSOutgoingMessage *)message
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(void (^)(NSError *error))failureHandler
|
2017-03-17 17:26:25 +01:00
|
|
|
{
|
2017-04-19 21:17:56 +02:00
|
|
|
OWSAssert(message);
|
2018-01-11 23:41:13 +01:00
|
|
|
if (message.body.length > 0) {
|
|
|
|
OWSAssert([message.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold);
|
|
|
|
}
|
2017-03-20 20:23:41 +01:00
|
|
|
|
2017-09-21 17:55:25 +02:00
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
2018-04-09 16:42:47 +02:00
|
|
|
|
|
|
|
__block NSArray<TSAttachmentStream *> *quotedThumbnailAttachments = @[];
|
2018-05-03 17:31:06 +02:00
|
|
|
__block TSAttachmentStream *_Nullable contactShareAvatarAttachment;
|
2018-04-09 16:42:47 +02:00
|
|
|
|
2017-09-21 17:55:25 +02:00
|
|
|
// This method will use a read/write transaction. This transaction
|
|
|
|
// will block until any open read/write transactions are complete.
|
|
|
|
//
|
|
|
|
// That's key - we don't want to send any messages in response
|
|
|
|
// to an incoming message until processing of that batch of messages
|
2017-09-21 23:25:13 +02:00
|
|
|
// is complete. For example, we wouldn't want to auto-reply to a
|
|
|
|
// group info request before that group info request's batch was
|
|
|
|
// finished processing. Otherwise, we might receive a delivery
|
|
|
|
// notice for a group update we hadn't yet saved to the db.
|
|
|
|
//
|
|
|
|
// So we're using YDB behavior to ensure this invariant, which is a bit
|
|
|
|
// unorthodox.
|
2017-11-15 12:39:10 +01:00
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2018-04-09 16:42:47 +02:00
|
|
|
if (message.quotedMessage) {
|
|
|
|
quotedThumbnailAttachments =
|
|
|
|
[message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction];
|
|
|
|
}
|
|
|
|
|
2018-05-03 17:31:06 +02:00
|
|
|
if (message.contactShare.avatarAttachmentId != nil) {
|
|
|
|
TSAttachment *avatarAttachment = [message.contactShare avatarAttachmentWithTransaction:transaction];
|
|
|
|
if ([avatarAttachment isKindOfClass:[TSAttachmentStream class]]) {
|
|
|
|
contactShareAvatarAttachment = (TSAttachmentStream *)avatarAttachment;
|
|
|
|
} else {
|
|
|
|
OWSFail(@"%@ in %s unexpected avatarAttachment: %@",
|
|
|
|
self.logTag,
|
|
|
|
__PRETTY_FUNCTION__,
|
|
|
|
avatarAttachment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
// All outgoing messages should be saved at the time they are enqueued.
|
|
|
|
[message saveWithTransaction:transaction];
|
2018-04-23 16:30:51 +02:00
|
|
|
// When we start a message send, all "failed" recipients should be marked as "sending".
|
|
|
|
[message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction];
|
2017-11-15 12:39:10 +01:00
|
|
|
}];
|
2017-03-17 20:38:22 +01:00
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
|
2017-09-21 17:55:25 +02:00
|
|
|
OWSSendMessageOperation *sendMessageOperation =
|
|
|
|
[[OWSSendMessageOperation alloc] initWithMessage:message
|
|
|
|
messageSender:self
|
2018-04-04 22:26:41 +02:00
|
|
|
dbConnection:self.dbConnection
|
2017-09-21 17:55:25 +02:00
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
2018-05-03 17:31:06 +02:00
|
|
|
|
|
|
|
// TODO de-dupe attachment enque logic.
|
2018-04-04 22:26:41 +02:00
|
|
|
if (message.hasAttachments) {
|
|
|
|
OWSUploadOperation *uploadAttachmentOperation =
|
|
|
|
[[OWSUploadOperation alloc] initWithAttachmentId:message.attachmentIds.firstObject
|
|
|
|
dbConnection:self.dbConnection];
|
|
|
|
[sendMessageOperation addDependency:uploadAttachmentOperation];
|
|
|
|
[sendingQueue addOperation:uploadAttachmentOperation];
|
2017-04-10 16:35:43 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-04-09 16:42:47 +02:00
|
|
|
// Though we currently only ever expect at most one thumbnail, the proto data model
|
|
|
|
// suggests this could change. The logic is intended to work with multiple, but
|
|
|
|
// if we ever actually want to send multiple, we should do more testing.
|
|
|
|
OWSAssert(quotedThumbnailAttachments.count <= 1);
|
|
|
|
for (TSAttachmentStream *thumbnailAttachment in quotedThumbnailAttachments) {
|
|
|
|
OWSAssert(message.quotedMessage);
|
2018-04-06 22:53:16 +02:00
|
|
|
|
2018-04-09 16:42:47 +02:00
|
|
|
OWSUploadOperation *uploadQuoteThumbnailOperation =
|
|
|
|
[[OWSUploadOperation alloc] initWithAttachmentId:thumbnailAttachment.uniqueId
|
|
|
|
dbConnection:self.dbConnection];
|
|
|
|
|
|
|
|
// TODO put attachment uploads on a (lowly) concurrent queue
|
|
|
|
[sendMessageOperation addDependency:uploadQuoteThumbnailOperation];
|
|
|
|
[sendingQueue addOperation:uploadQuoteThumbnailOperation];
|
2018-04-06 22:53:16 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-05-03 17:31:06 +02:00
|
|
|
if (contactShareAvatarAttachment != nil) {
|
|
|
|
OWSAssert(message.contactShare);
|
|
|
|
OWSUploadOperation *uploadAvatarOperation =
|
|
|
|
[[OWSUploadOperation alloc] initWithAttachmentId:contactShareAvatarAttachment.uniqueId
|
|
|
|
dbConnection:self.dbConnection];
|
|
|
|
|
|
|
|
// TODO put attachment uploads on a (lowly) concurrent queue
|
|
|
|
[sendMessageOperation addDependency:uploadAvatarOperation];
|
|
|
|
[sendingQueue addOperation:uploadAvatarOperation];
|
|
|
|
}
|
|
|
|
|
2018-04-04 22:26:41 +02:00
|
|
|
[sendingQueue addOperation:sendMessageOperation];
|
|
|
|
});
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 19:21:31 +01:00
|
|
|
- (void)enqueueTemporaryAttachment:(DataSource *)dataSource
|
|
|
|
contentType:(NSString *)contentType
|
|
|
|
inMessage:(TSOutgoingMessage *)message
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(void (^)(NSError *error))failureHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2017-09-08 18:51:25 +02:00
|
|
|
OWSAssert(dataSource);
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
void (^successWithDeleteHandler)(void) = ^() {
|
2016-10-14 23:00:29 +02:00
|
|
|
successHandler();
|
|
|
|
|
2017-11-22 21:39:52 +01:00
|
|
|
DDLogDebug(@"%@ Removing successful temporary attachment message with attachment ids: %@",
|
|
|
|
self.logTag,
|
|
|
|
message.attachmentIds);
|
2016-10-14 23:00:29 +02:00
|
|
|
[message remove];
|
|
|
|
};
|
|
|
|
|
|
|
|
void (^failureWithDeleteHandler)(NSError *error) = ^(NSError *error) {
|
|
|
|
failureHandler(error);
|
|
|
|
|
2017-11-22 21:39:52 +01:00
|
|
|
DDLogDebug(@"%@ Removing failed temporary attachment message with attachment ids: %@",
|
|
|
|
self.logTag,
|
|
|
|
message.attachmentIds);
|
2016-10-14 23:00:29 +02:00
|
|
|
[message remove];
|
|
|
|
};
|
|
|
|
|
2017-11-15 19:21:31 +01:00
|
|
|
[self enqueueAttachment:dataSource
|
|
|
|
contentType:contentType
|
|
|
|
sourceFilename:nil
|
|
|
|
inMessage:message
|
|
|
|
success:successWithDeleteHandler
|
|
|
|
failure:failureWithDeleteHandler];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 19:21:31 +01:00
|
|
|
- (void)enqueueAttachment:(DataSource *)dataSource
|
|
|
|
contentType:(NSString *)contentType
|
|
|
|
sourceFilename:(nullable NSString *)sourceFilename
|
|
|
|
inMessage:(TSOutgoingMessage *)message
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(void (^)(NSError *error))failureHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2017-09-08 18:51:25 +02:00
|
|
|
OWSAssert(dataSource);
|
2017-03-24 19:37:24 +01:00
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
dispatch_async([OWSDispatch attachmentsQueue], ^{
|
2017-11-08 18:56:55 +01:00
|
|
|
TSAttachmentStream *attachmentStream =
|
|
|
|
[[TSAttachmentStream alloc] initWithContentType:contentType
|
|
|
|
byteCount:(UInt32)dataSource.dataLength
|
|
|
|
sourceFilename:sourceFilename];
|
2017-05-12 15:11:43 +02:00
|
|
|
if (message.isVoiceMessage) {
|
|
|
|
attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage;
|
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2017-09-08 18:51:25 +02:00
|
|
|
if (![attachmentStream writeDataSource:dataSource]) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotWriteAttachment]);
|
2017-09-08 18:51:25 +02:00
|
|
|
NSError *error = OWSErrorMakeWriteAttachmentDataError();
|
2016-10-14 23:00:29 +02:00
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
[attachmentStream save];
|
|
|
|
[message.attachmentIds addObject:attachmentStream.uniqueId];
|
2017-05-15 15:53:16 +02:00
|
|
|
if (sourceFilename) {
|
|
|
|
message.attachmentFilenameMap[attachmentStream.uniqueId] = sourceFilename;
|
2017-04-13 18:54:03 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2017-11-15 19:21:31 +01:00
|
|
|
[self enqueueMessage:message success:successHandler failure:failureHandler];
|
2016-10-14 23:00:29 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-14 21:27:34 +02:00
|
|
|
- (NSArray<SignalRecipient *> *)signalRecipientsForRecipientIds:(NSArray<NSString *> *)recipientIds
|
|
|
|
message:(TSOutgoingMessage *)message
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2018-05-14 21:27:34 +02:00
|
|
|
OWSAssert(recipientIds);
|
|
|
|
OWSAssert(message);
|
2018-04-23 16:30:51 +02:00
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
NSMutableArray<SignalRecipient *> *recipients = [NSMutableArray new];
|
2018-07-13 20:03:28 +02:00
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
for (NSString *recipientId in recipientIds) {
|
|
|
|
SignalRecipient *recipient =
|
|
|
|
[SignalRecipient ensureRecipientExistsWithRecipientId:recipientId transaction:transaction];
|
|
|
|
[recipients addObject:recipient];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
2018-07-13 20:03:28 +02:00
|
|
|
}];
|
|
|
|
return recipients;
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
2018-06-14 18:48:40 +02:00
|
|
|
TSThread *_Nullable thread = message.thread;
|
2017-09-29 03:12:02 +02:00
|
|
|
|
2018-04-26 20:12:34 +02:00
|
|
|
// TODO: It would be nice to combine the "contact" and "group" send logic here.
|
|
|
|
if ([thread isKindOfClass:[TSContactThread class]] &&
|
|
|
|
[((TSContactThread *)thread).contactIdentifier isEqualToString:[TSAccountManager localNumber]]) {
|
|
|
|
// Send to self.
|
2018-05-14 20:41:45 +02:00
|
|
|
OWSAssert(message.recipientIds.count == 1);
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
for (NSString *recipientId in message.sendingRecipientIds) {
|
|
|
|
[message updateWithSentRecipient:recipientId transaction:transaction];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
2018-05-14 16:56:47 +02:00
|
|
|
[self handleMessageSentLocally:message];
|
2018-05-14 20:41:45 +02:00
|
|
|
|
2018-04-26 20:12:34 +02:00
|
|
|
successHandler();
|
|
|
|
return;
|
|
|
|
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
|
2018-04-23 16:30:51 +02:00
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
TSGroupThread *gThread = (TSGroupThread *)thread;
|
|
|
|
|
2018-04-23 16:30:51 +02:00
|
|
|
// Send to the intersection of:
|
|
|
|
//
|
|
|
|
// * "sending" recipients of the message.
|
|
|
|
// * members of the group.
|
|
|
|
//
|
|
|
|
// I.e. try to send a message IFF:
|
|
|
|
//
|
|
|
|
// * The recipient was in the group when the message was first tried to be sent.
|
|
|
|
// * The recipient is still in the group.
|
|
|
|
// * The recipient is in the "sending" state.
|
2018-04-26 20:17:41 +02:00
|
|
|
|
|
|
|
NSMutableSet<NSString *> *sendingRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
|
|
|
[sendingRecipientIds intersectSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]];
|
|
|
|
[sendingRecipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]];
|
|
|
|
|
|
|
|
// Mark skipped recipients as such. We skip because:
|
|
|
|
//
|
|
|
|
// * Recipient is no longer in the group.
|
|
|
|
// * Recipient is blocked.
|
|
|
|
//
|
|
|
|
// Elsewhere, we skip recipient if their Signal account has been deactivated.
|
2018-04-23 16:30:51 +02:00
|
|
|
NSMutableSet<NSString *> *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
2018-04-26 20:17:41 +02:00
|
|
|
[obsoleteRecipientIds minusSet:sendingRecipientIds];
|
2018-04-23 16:30:51 +02:00
|
|
|
if (obsoleteRecipientIds.count > 0) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
for (NSString *recipientId in obsoleteRecipientIds) {
|
|
|
|
// Mark this recipient as "skipped".
|
|
|
|
[message updateWithSkippedRecipient:recipientId transaction:transaction];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2018-05-04 16:52:22 +02:00
|
|
|
if (sendingRecipientIds.count < 1) {
|
|
|
|
// All recipients are already sent or can be skipped.
|
|
|
|
successHandler();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
NSArray<SignalRecipient *> *recipients =
|
2018-07-13 20:03:28 +02:00
|
|
|
[self signalRecipientsForRecipientIds:sendingRecipientIds.allObjects message:message];
|
|
|
|
OWSAssert(recipients.count == sendingRecipientIds.count);
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
[self groupSend:recipients message:message thread:gThread success:successHandler failure:failureHandler];
|
|
|
|
|
|
|
|
} else if ([thread isKindOfClass:[TSContactThread class]]
|
|
|
|
|| [message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
|
|
|
|
|
|
|
|
TSContactThread *contactThread = (TSContactThread *)thread;
|
|
|
|
|
2018-04-26 20:12:34 +02:00
|
|
|
NSString *recipientContactId
|
|
|
|
= ([message isKindOfClass:[OWSOutgoingSyncMessage class]] ? [TSAccountManager localNumber]
|
|
|
|
: contactThread.contactIdentifier);
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
// If we block a user, don't send 1:1 messages to them. The UI
|
|
|
|
// should prevent this from occurring, but in some edge cases
|
|
|
|
// you might, for example, have a pending outgoing message when
|
|
|
|
// you block them.
|
2017-04-01 22:47:16 +02:00
|
|
|
OWSAssert(recipientContactId.length > 0);
|
2018-04-23 16:30:51 +02:00
|
|
|
if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) {
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogInfo(@"%@ skipping 1:1 send to blocked contact: %@", self.logTag, recipientContactId);
|
2017-04-05 23:34:47 +02:00
|
|
|
NSError *error = OWSErrorMakeMessageSendFailedToBlockListError();
|
2017-04-05 01:44:14 +02:00
|
|
|
// No need to retry - the user will continue to be blocked.
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
|
|
|
failureHandler(error);
|
2017-04-01 22:47:16 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:03:28 +02:00
|
|
|
__block SignalRecipient *recipient;
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
recipient =
|
|
|
|
[SignalRecipient ensureRecipientExistsWithRecipientId:recipientContactId transaction:transaction];
|
|
|
|
}];
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
if (!recipient) {
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
DDLogWarn(@"recipient contact still not found after attempting lookup.");
|
2017-04-05 01:44:14 +02:00
|
|
|
// No need to repeat trying to find a failure. Apart from repeatedly failing, it would also cause us to
|
|
|
|
// print redundant error messages.
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
|
|
|
failureHandler(error);
|
2017-04-01 22:47:16 +02:00
|
|
|
return;
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
[self sendMessageToService:message
|
2018-07-03 23:31:25 +02:00
|
|
|
recipient:recipient
|
|
|
|
thread:thread
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
|
|
|
useWebsocketIfAvailable:YES
|
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
2016-11-04 17:19:13 +01:00
|
|
|
} else {
|
2017-04-05 01:44:14 +02:00
|
|
|
// Neither a group nor contact thread? This should never happen.
|
2017-11-08 20:04:51 +01:00
|
|
|
OWSFail(@"%@ Unknown message type: %@", self.logTag, NSStringFromClass([message class]));
|
2017-04-05 01:44:14 +02:00
|
|
|
|
2016-11-04 17:19:13 +01:00
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
|
|
|
failureHandler(error);
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
// For group sends, we're using chained futures to make the code more readable.
|
2016-10-14 23:00:29 +02:00
|
|
|
- (TOCFuture *)sendMessageFuture:(TSOutgoingMessage *)message
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
thread:(TSThread *)thread
|
|
|
|
{
|
|
|
|
TOCFutureSource *futureSource = [[TOCFutureSource alloc] init];
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
[self sendMessageToService:message
|
2016-10-14 23:00:29 +02:00
|
|
|
recipient:recipient
|
|
|
|
thread:thread
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
2018-07-03 23:31:25 +02:00
|
|
|
useWebsocketIfAvailable:YES
|
2016-10-14 23:00:29 +02:00
|
|
|
success:^{
|
|
|
|
[futureSource trySetResult:@1];
|
|
|
|
}
|
2017-04-14 16:25:52 +02:00
|
|
|
failure:^(NSError *error) {
|
2016-10-14 23:00:29 +02:00
|
|
|
[futureSource trySetFailure:error];
|
|
|
|
}];
|
|
|
|
|
|
|
|
return futureSource.future;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)groupSend:(NSArray<SignalRecipient *> *)recipients
|
|
|
|
message:(TSOutgoingMessage *)message
|
|
|
|
thread:(TSThread *)thread
|
2017-11-08 20:04:51 +01:00
|
|
|
success:(void (^)(void))successHandler
|
2017-04-05 01:44:14 +02:00
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
|
|
|
[self saveGroupMessage:message inThread:thread];
|
2018-04-24 22:11:10 +02:00
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
NSMutableArray<TOCFuture *> *futures = [NSMutableArray array];
|
|
|
|
|
2017-04-11 22:57:28 +02:00
|
|
|
for (SignalRecipient *recipient in recipients) {
|
2017-05-12 17:38:24 +02:00
|
|
|
NSString *recipientId = recipient.recipientId;
|
|
|
|
|
2017-04-01 22:47:16 +02:00
|
|
|
// We don't need to send the message to ourselves...
|
2017-08-02 21:15:31 +02:00
|
|
|
if ([recipientId isEqualToString:[TSAccountManager localNumber]]) {
|
2017-05-12 17:38:24 +02:00
|
|
|
continue;
|
|
|
|
}
|
2017-04-01 22:47:16 +02:00
|
|
|
|
|
|
|
// ...otherwise we send.
|
2017-04-11 22:57:28 +02:00
|
|
|
[futures addObject:[self sendMessageFuture:message recipient:recipient thread:thread]];
|
2017-04-01 22:47:16 +02:00
|
|
|
}
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
TOCFuture *completionFuture = futures.toc_thenAll;
|
|
|
|
|
|
|
|
[completionFuture thenDo:^(id value) {
|
|
|
|
successHandler();
|
|
|
|
}];
|
|
|
|
|
|
|
|
[completionFuture catchDo:^(id failure) {
|
2017-04-14 16:25:52 +02:00
|
|
|
// failure from toc_thenAll yields an array of failed Futures, rather than the future's failure.
|
2017-04-14 17:33:13 +02:00
|
|
|
NSError *firstRetryableError = nil;
|
|
|
|
NSError *firstNonRetryableError = nil;
|
|
|
|
|
2016-10-24 19:54:00 +02:00
|
|
|
if ([failure isKindOfClass:[NSArray class]]) {
|
2017-04-12 23:40:36 +02:00
|
|
|
NSArray *groupSendFutures = (NSArray *)failure;
|
|
|
|
for (TOCFuture *groupSendFuture in groupSendFutures) {
|
|
|
|
if (groupSendFuture.hasFailed) {
|
|
|
|
id failureResult = groupSendFuture.forceGetFailure;
|
2016-10-24 19:54:00 +02:00
|
|
|
if ([failureResult isKindOfClass:[NSError class]]) {
|
2017-04-14 16:25:52 +02:00
|
|
|
NSError *error = failureResult;
|
2017-04-19 21:39:34 +02:00
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
// Some errors should be ignored when sending messages
|
|
|
|
// to groups. See discussion on
|
|
|
|
// NSError (OWSMessageSender) category.
|
|
|
|
if ([error shouldBeIgnoredForGroups]) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-04-14 17:33:13 +02:00
|
|
|
|
2017-04-19 21:39:34 +02:00
|
|
|
// Some errors should never be retried, in order to avoid
|
|
|
|
// hitting rate limits, for example. Unfortunately, since
|
|
|
|
// group send retry is all-or-nothing, we need to fail
|
|
|
|
// immediately even if some of the other recipients had
|
|
|
|
// retryable errors.
|
|
|
|
if ([error isFatal]) {
|
|
|
|
failureHandler(error);
|
2017-06-13 21:08:27 +02:00
|
|
|
return;
|
2017-04-19 21:39:34 +02:00
|
|
|
}
|
|
|
|
|
2017-04-14 17:33:13 +02:00
|
|
|
if ([error isRetryable] && !firstRetryableError) {
|
|
|
|
firstRetryableError = error;
|
|
|
|
} else if (![error isRetryable] && !firstNonRetryableError) {
|
|
|
|
firstNonRetryableError = error;
|
|
|
|
}
|
2016-10-24 19:54:00 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-14 17:33:13 +02:00
|
|
|
// If any of the group send errors are retryable, we want to retry.
|
|
|
|
// Therefore, prefer to propagate a retryable error.
|
|
|
|
if (firstRetryableError) {
|
|
|
|
return failureHandler(firstRetryableError);
|
|
|
|
} else if (firstNonRetryableError) {
|
|
|
|
return failureHandler(firstNonRetryableError);
|
|
|
|
} else {
|
|
|
|
// If we only received errors that we should ignore,
|
2017-04-17 22:45:22 +02:00
|
|
|
// consider this send a success, unless the message could
|
|
|
|
// not be sent to any recipient.
|
|
|
|
if (message.sentRecipientsCount == 0) {
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
|
|
|
|
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
|
|
|
|
@"Error indicating that an outgoing message had no valid recipients."));
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
failureHandler(error);
|
|
|
|
} else {
|
|
|
|
successHandler();
|
|
|
|
}
|
2017-04-14 17:33:13 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unregisteredRecipient:(SignalRecipient *)recipient
|
|
|
|
message:(TSOutgoingMessage *)message
|
|
|
|
thread:(TSThread *)thread
|
|
|
|
{
|
|
|
|
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2018-04-24 21:49:08 +02:00
|
|
|
if (thread.isGroupThread) {
|
|
|
|
// Mark as "skipped" group members who no longer have signal accounts.
|
|
|
|
[message updateWithSkippedRecipient:recipient.recipientId transaction:transaction];
|
|
|
|
}
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
[recipient removeWithTransaction:transaction];
|
2018-05-14 21:27:34 +02:00
|
|
|
[[TSInfoMessage userNotRegisteredMessageInThread:thread recipientId:recipient.recipientId]
|
2016-10-14 23:00:29 +02:00
|
|
|
saveWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
|
|
|
recipient:(SignalRecipient *)recipient
|
2018-06-13 22:37:44 +02:00
|
|
|
thread:(nullable TSThread *)thread
|
2018-07-03 23:31:25 +02:00
|
|
|
attempts:(int)remainingAttemptsParam
|
|
|
|
useWebsocketIfAvailable:(BOOL)useWebsocketIfAvailable
|
2017-11-15 12:39:10 +01:00
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2018-06-13 22:37:44 +02:00
|
|
|
OWSAssert(thread || [message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
|
2017-10-12 17:06:18 +02:00
|
|
|
DDLogInfo(@"%@ attempting to send message: %@, timestamp: %llu, recipient: %@",
|
2017-11-08 20:04:51 +01:00
|
|
|
self.logTag,
|
2017-10-12 17:06:18 +02:00
|
|
|
message.class,
|
|
|
|
message.timestamp,
|
|
|
|
recipient.uniqueId);
|
2017-04-07 01:11:04 +02:00
|
|
|
AssertIsOnSendingQueue();
|
2017-02-09 19:50:32 +01:00
|
|
|
|
2017-02-10 19:20:11 +01:00
|
|
|
if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdError([OWSAnalyticsEvents messageSendErrorFailedDueToPrekeyUpdateFailures]);
|
2017-02-10 19:20:11 +01:00
|
|
|
|
|
|
|
// Retry prekey update every time user tries to send a message while app
|
|
|
|
// is disabled due to prekey update failures.
|
|
|
|
//
|
|
|
|
// Only try to update the signed prekey; updating it is sufficient to
|
|
|
|
// re-enable message sending.
|
|
|
|
[TSPreKeyManager registerPreKeysWithMode:RefreshPreKeysMode_SignedOnly
|
|
|
|
success:^{
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogInfo(@"%@ New prekeys registered with server.", self.logTag);
|
2017-02-10 19:20:11 +01:00
|
|
|
}
|
|
|
|
failure:^(NSError *error) {
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogWarn(@"%@ Failed to update prekeys with the server: %@", self.logTag, error);
|
2017-02-10 19:20:11 +01:00
|
|
|
}];
|
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError();
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
return failureHandler(error);
|
2017-02-10 19:20:11 +01:00
|
|
|
}
|
|
|
|
|
2018-07-03 23:31:25 +02:00
|
|
|
if (remainingAttemptsParam <= 0) {
|
2016-10-14 23:00:29 +02:00
|
|
|
// We should always fail with a specific error.
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorGenericSendFailure]);
|
2017-04-05 01:44:14 +02:00
|
|
|
|
2017-04-14 16:25:52 +02:00
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
return failureHandler(error);
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
2018-07-03 23:31:25 +02:00
|
|
|
int remainingAttempts = remainingAttemptsParam - 1;
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
NSArray<NSDictionary *> *deviceMessages;
|
|
|
|
@try {
|
2017-09-27 23:13:29 +02:00
|
|
|
deviceMessages = [self deviceMessages:message forRecipient:recipient];
|
2016-10-14 23:00:29 +02:00
|
|
|
} @catch (NSException *exception) {
|
|
|
|
deviceMessages = @[];
|
2016-11-10 15:59:07 +01:00
|
|
|
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
|
2017-06-08 05:21:25 +02:00
|
|
|
// This *can* happen under normal usage, but it should happen relatively rarely.
|
|
|
|
// We expect it to happen whenever Bob reinstalls, and Alice messages Bob before
|
|
|
|
// she can pull down his latest identity.
|
|
|
|
// If it's happening a lot, we should rethink our profile fetching strategy.
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdInfo([OWSAnalyticsEvents messageSendErrorFailedDueToUntrustedKey]);
|
2017-06-08 05:21:25 +02:00
|
|
|
|
2017-06-08 16:31:10 +02:00
|
|
|
NSString *localizedErrorDescriptionFormat
|
|
|
|
= NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
|
|
|
|
@"action sheet header when re-sending message which failed because of untrusted identity keys");
|
|
|
|
|
|
|
|
NSString *localizedErrorDescription =
|
|
|
|
[NSString stringWithFormat:localizedErrorDescriptionFormat,
|
|
|
|
[self.contactsManager displayNameForPhoneIdentifier:recipient.recipientId]];
|
2017-12-22 00:01:40 +01:00
|
|
|
NSError *error = OWSErrorMakeUntrustedIdentityError(localizedErrorDescription, recipient.recipientId);
|
2017-06-08 05:21:25 +02:00
|
|
|
|
2017-04-05 01:44:14 +02:00
|
|
|
// Key will continue to be unaccepted, so no need to retry. It'll only cause us to hit the Pre-Key request
|
|
|
|
// rate limit
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
2017-04-19 21:39:34 +02:00
|
|
|
// Avoid the "Too many failures with this contact" error rate limiting.
|
|
|
|
[error setIsFatal:YES];
|
2017-06-08 05:21:25 +02:00
|
|
|
|
2017-12-22 00:01:40 +01:00
|
|
|
PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey];
|
|
|
|
if (newKeyBundle == nil) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorMissingNewPreKeyBundle]);
|
|
|
|
failureHandler(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-08 05:21:25 +02:00
|
|
|
if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle]);
|
2017-06-08 05:21:25 +02:00
|
|
|
failureHandler(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey;
|
2017-06-19 17:05:06 +02:00
|
|
|
|
2017-06-08 05:21:25 +02:00
|
|
|
if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyType]);
|
2017-06-08 05:21:25 +02:00
|
|
|
failureHandler(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:05:06 +02:00
|
|
|
// TODO migrate to storing the full 33 byte representation of the identity key.
|
|
|
|
if (newIdentityKeyWithVersion.length != kIdentityKeyLength) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyLength]);
|
2017-06-08 05:21:25 +02:00
|
|
|
failureHandler(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:05:06 +02:00
|
|
|
NSData *newIdentityKey = [newIdentityKeyWithVersion removeKeyType];
|
2018-02-02 18:56:55 +01:00
|
|
|
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
|
2017-06-08 05:21:25 +02:00
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
return;
|
2016-11-10 15:59:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) {
|
2017-02-01 16:21:50 +01:00
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited,
|
2016-11-10 15:59:07 +01:00
|
|
|
NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT",
|
|
|
|
@"action sheet header when re-sending message which failed because of too many attempts"));
|
2017-04-05 01:44:14 +02:00
|
|
|
|
|
|
|
// We're already rate-limited. No need to exacerbate the problem.
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
2017-04-19 21:39:34 +02:00
|
|
|
// Avoid exacerbating the rate limiting.
|
|
|
|
[error setIsFatal:YES];
|
2017-04-14 16:25:52 +02:00
|
|
|
return failureHandler(error);
|
2016-11-10 15:59:07 +01:00
|
|
|
}
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
if (remainingAttempts == 0) {
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogWarn(@"%@ Terminal failure to build any device messages. Giving up with exception:%@",
|
|
|
|
self.logTag,
|
|
|
|
exception);
|
2016-10-14 23:00:29 +02:00
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
2017-04-05 01:44:14 +02:00
|
|
|
// Since we've already repeatedly failed to build messages, it's unlikely that repeating the whole process
|
|
|
|
// will succeed.
|
2017-04-14 16:25:52 +02:00
|
|
|
[error setIsRetryable:NO];
|
|
|
|
return failureHandler(error);
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-15 16:29:46 +02:00
|
|
|
NSString *localNumber = [TSAccountManager localNumber];
|
2018-03-02 05:31:41 +01:00
|
|
|
BOOL isLocalNumber = [localNumber isEqualToString:recipient.uniqueId];
|
|
|
|
if (isLocalNumber) {
|
2017-11-07 20:52:31 +01:00
|
|
|
OWSAssert([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
2018-03-02 05:31:41 +01:00
|
|
|
// Messages sent to the "local number" should be sync messages.
|
2017-11-07 20:52:31 +01:00
|
|
|
//
|
|
|
|
// We can skip sending sync messages if we know that we have no linked
|
|
|
|
// devices. However, we need to be sure to handle the case where the
|
|
|
|
// linked device list has just changed.
|
|
|
|
//
|
|
|
|
// The linked device list is reflected in two separate pieces of state:
|
|
|
|
//
|
|
|
|
// * OWSDevice's state is updated when you link or unlink a device.
|
|
|
|
// * SignalRecipient's state is updated by 409 "Mismatched devices"
|
|
|
|
// responses from the service.
|
|
|
|
//
|
|
|
|
// If _both_ of these pieces of state agree that there are no linked
|
|
|
|
// devices, then can safely skip sending sync message.
|
|
|
|
|
|
|
|
// 1. Check OWSDevice's state.
|
2017-11-10 18:04:24 +01:00
|
|
|
BOOL mayHaveLinkedDevices = [OWSDeviceManager.sharedManager mayHaveLinkedDevices:self.dbConnection];
|
2017-11-02 19:10:45 +01:00
|
|
|
|
2017-11-07 20:52:31 +01:00
|
|
|
// 2. Check SignalRecipient's state.
|
2017-11-02 19:10:45 +01:00
|
|
|
BOOL hasDeviceMessages = deviceMessages.count > 0;
|
|
|
|
|
2018-03-02 05:31:41 +01:00
|
|
|
DDLogInfo(@"%@ mayHaveLinkedDevices: %d, hasDeviceMessages: %d",
|
|
|
|
self.logTag,
|
|
|
|
mayHaveLinkedDevices,
|
|
|
|
hasDeviceMessages);
|
|
|
|
|
2017-11-10 17:04:40 +01:00
|
|
|
if (!mayHaveLinkedDevices && !hasDeviceMessages) {
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogInfo(@"%@ Ignoring sync message without secondary devices: %@", self.logTag, [message class]);
|
2017-09-15 16:29:46 +02:00
|
|
|
OWSAssert([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
2017-11-02 19:10:45 +01:00
|
|
|
// This emulates the completion logic of an actual successful save (see below).
|
2018-04-27 22:37:09 +02:00
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[message updateWithSkippedRecipient:localNumber transaction:transaction];
|
|
|
|
[recipient saveWithTransaction:transaction];
|
|
|
|
}];
|
2017-09-15 16:29:46 +02:00
|
|
|
successHandler();
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
2018-07-02 18:22:29 +02:00
|
|
|
} else if (mayHaveLinkedDevices && !hasDeviceMessages) {
|
2017-11-02 19:10:45 +01:00
|
|
|
// We may have just linked a new secondary device which is not yet reflected in
|
|
|
|
// the SignalRecipient that corresponds to ourself. Proceed. Client should learn
|
2017-11-07 20:52:31 +01:00
|
|
|
// of new secondary devices via 409 "Mismatched devices" response.
|
2018-07-02 18:22:29 +02:00
|
|
|
DDLogWarn(@"%@ account has secondary devices, but sync message has no device messages", self.logTag);
|
|
|
|
} else if (!mayHaveLinkedDevices && hasDeviceMessages) {
|
2017-11-07 20:42:32 +01:00
|
|
|
OWSFail(@"%@ sync message has device messages for unknown secondary devices.", self.logTag);
|
2017-09-15 16:29:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-02 05:31:41 +01:00
|
|
|
if (deviceMessages.count == 0) {
|
2018-03-02 16:53:22 +01:00
|
|
|
// This might happen:
|
|
|
|
//
|
|
|
|
// * The first (after upgrading?) time we send a sync message to our linked devices.
|
|
|
|
// * After unlinking all linked devices.
|
|
|
|
// * After trying and failing to link a device.
|
2018-07-13 20:42:11 +02:00
|
|
|
// * The first time we send a message to a user, if they don't have their
|
|
|
|
// default device (device id = 0). For example, if they have unregistered
|
|
|
|
// their primary but still have a linked device. Or later, when they re-register.
|
2018-03-02 16:53:22 +01:00
|
|
|
//
|
|
|
|
// When we're not sure if we have linked devices, we need to try
|
|
|
|
// to send self-sync messages even if they have no device messages
|
|
|
|
// so that we can learn from the service whether or not there are
|
|
|
|
// linked devices that we don't know about.
|
2018-03-02 05:31:41 +01:00
|
|
|
DDLogWarn(@"%@ Sending a message with no device messages.", self.logTag);
|
|
|
|
}
|
|
|
|
|
2018-03-02 05:06:52 +01:00
|
|
|
TSRequest *request = [OWSRequestFactory submitMessageRequestWithRecipient:recipient.uniqueId
|
|
|
|
messages:deviceMessages
|
|
|
|
timeStamp:message.timestamp];
|
2018-07-03 23:31:25 +02:00
|
|
|
if (useWebsocketIfAvailable && TSSocketManager.canMakeRequests) {
|
2018-05-18 19:55:22 +02:00
|
|
|
[TSSocketManager.sharedManager makeRequest:request
|
2018-05-18 20:35:00 +02:00
|
|
|
success:^(id _Nullable responseObject) {
|
|
|
|
[self messageSendDidSucceed:message
|
|
|
|
recipient:recipient
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
success:successHandler];
|
2018-03-02 05:31:41 +01:00
|
|
|
}
|
2018-05-30 21:41:01 +02:00
|
|
|
failure:^(NSInteger statusCode, NSData *_Nullable responseData, NSError *error) {
|
2018-07-05 15:56:48 +02:00
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
DDLogDebug(
|
|
|
|
@"%@ in %s falling back to REST since first attempt failed.", self.logTag, __PRETTY_FUNCTION__);
|
|
|
|
|
|
|
|
// Websockets can fail in different ways, so we don't decrement remainingAttempts for websocket
|
|
|
|
// failure. Instead we fall back to REST, which will decrement retries. e.g. after linking a new
|
|
|
|
// device, sync messages will fail until the websocket re-opens.
|
|
|
|
[self sendMessageToService:message
|
|
|
|
recipient:recipient
|
|
|
|
thread:thread
|
|
|
|
attempts:remainingAttemptsParam
|
|
|
|
useWebsocketIfAvailable:NO
|
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
|
|
|
});
|
2018-05-18 20:35:00 +02:00
|
|
|
}];
|
|
|
|
} else {
|
|
|
|
[self.networkManager makeRequest:request
|
|
|
|
success:^(NSURLSessionDataTask *task, id responseObject) {
|
|
|
|
[self messageSendDidSucceed:message
|
|
|
|
recipient:recipient
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
success:successHandler];
|
|
|
|
}
|
|
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
|
|
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
|
|
|
NSInteger statusCode = response.statusCode;
|
2018-05-30 16:03:14 +02:00
|
|
|
NSData *_Nullable responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
2018-05-18 20:35:00 +02:00
|
|
|
|
|
|
|
[self messageSendDidFail:message
|
|
|
|
recipient:recipient
|
|
|
|
thread:thread
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
remainingAttempts:remainingAttempts
|
|
|
|
statusCode:statusCode
|
|
|
|
error:error
|
2018-05-30 16:03:14 +02:00
|
|
|
responseData:responseData
|
2018-05-18 20:35:00 +02:00
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
2018-03-02 05:31:41 +01:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
- (void)messageSendDidSucceed:(TSOutgoingMessage *)message
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
isLocalNumber:(BOOL)isLocalNumber
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
{
|
|
|
|
OWSAssert(message);
|
|
|
|
OWSAssert(recipient);
|
|
|
|
OWSAssert(deviceMessages);
|
|
|
|
OWSAssert(successHandler);
|
|
|
|
|
|
|
|
DDLogInfo(@"%@ Message send succeeded.", self.logTag);
|
|
|
|
|
|
|
|
if (isLocalNumber && deviceMessages.count == 0) {
|
|
|
|
DDLogInfo(@"%@ Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.", self.logTag);
|
|
|
|
// In order to avoid skipping necessary sync messages, the default value
|
|
|
|
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
|
|
|
// sync message with no device messages (e.g. the service has confirmed
|
|
|
|
// that we have no linked devices), we can set mayHaveLinkedDevices to NO
|
|
|
|
// to avoid unnecessary message sends for sync messages until we learn
|
|
|
|
// of a linked device (e.g. through the device linking UI or by receiving
|
|
|
|
// a sync message, etc.).
|
|
|
|
[OWSDeviceManager.sharedManager clearMayHaveLinkedDevicesIfNotSet];
|
|
|
|
}
|
2018-04-23 16:30:51 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[recipient saveWithTransaction:transaction];
|
|
|
|
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
|
|
|
}];
|
2018-05-18 19:55:22 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
[self handleMessageSentLocally:message];
|
|
|
|
successHandler();
|
|
|
|
});
|
|
|
|
}
|
2018-05-18 19:55:22 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
- (void)messageSendDidFail:(TSOutgoingMessage *)message
|
|
|
|
recipient:(SignalRecipient *)recipient
|
2018-06-13 22:37:44 +02:00
|
|
|
thread:(nullable TSThread *)thread
|
2018-05-18 20:35:00 +02:00
|
|
|
isLocalNumber:(BOOL)isLocalNumber
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
remainingAttempts:(int)remainingAttempts
|
|
|
|
statusCode:(NSInteger)statusCode
|
2018-05-30 16:03:14 +02:00
|
|
|
error:(NSError *)responseError
|
|
|
|
responseData:(nullable NSData *)responseData
|
2018-05-18 20:35:00 +02:00
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
{
|
|
|
|
OWSAssert(message);
|
|
|
|
OWSAssert(recipient);
|
2018-06-13 22:37:44 +02:00
|
|
|
OWSAssert(thread || [message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
2018-05-18 20:35:00 +02:00
|
|
|
OWSAssert(deviceMessages);
|
2018-05-30 16:03:14 +02:00
|
|
|
OWSAssert(responseError);
|
2018-05-18 20:35:00 +02:00
|
|
|
OWSAssert(successHandler);
|
|
|
|
OWSAssert(failureHandler);
|
2016-10-24 19:54:00 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
DDLogInfo(@"%@ sending to recipient: %@, failed with error.", self.logTag, recipient.uniqueId);
|
2018-05-18 19:55:22 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
void (^retrySend)(void) = ^void() {
|
|
|
|
if (remainingAttempts <= 0) {
|
|
|
|
// Since we've already repeatedly failed to send to the messaging API,
|
|
|
|
// it's unlikely that repeating the whole process will succeed.
|
2018-05-30 16:03:14 +02:00
|
|
|
[responseError setIsRetryable:NO];
|
|
|
|
return failureHandler(responseError);
|
2018-05-18 20:35:00 +02:00
|
|
|
}
|
2017-05-02 17:31:29 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
DDLogDebug(@"%@ Retrying: %@", self.logTag, message.debugDescription);
|
|
|
|
[self sendMessageToService:message
|
2018-07-03 23:31:25 +02:00
|
|
|
recipient:recipient
|
|
|
|
thread:thread
|
|
|
|
attempts:remainingAttempts
|
|
|
|
useWebsocketIfAvailable:NO
|
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
2018-05-18 20:35:00 +02:00
|
|
|
});
|
|
|
|
};
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-05-18 20:35:00 +02:00
|
|
|
switch (statusCode) {
|
|
|
|
case 401: {
|
|
|
|
DDLogWarn(@"%@ Unable to send due to invalid credentials. Did the user's client get de-authed by "
|
|
|
|
@"registering elsewhere?",
|
|
|
|
self.logTag);
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure,
|
|
|
|
NSLocalizedString(
|
|
|
|
@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED", @"Error message when attempting to send message"));
|
|
|
|
// No need to retry if we've been de-authed.
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
case 404: {
|
|
|
|
DDLogWarn(@"%@ Unregistered recipient: %@", self.logTag, recipient.uniqueId);
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-06-14 18:48:40 +02:00
|
|
|
OWSAssert(thread);
|
2018-05-18 20:35:00 +02:00
|
|
|
[self unregisteredRecipient:recipient message:message thread:thread];
|
|
|
|
NSError *error = OWSErrorMakeNoSuchSignalRecipientError();
|
|
|
|
// No need to retry if the recipient is not registered.
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
// If one member of a group deletes their account,
|
|
|
|
// the group should ignore errors when trying to send
|
|
|
|
// messages to this ex-member.
|
|
|
|
[error setShouldBeIgnoredForGroups:YES];
|
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
case 409: {
|
|
|
|
// Mismatched devices
|
2018-05-30 16:03:14 +02:00
|
|
|
DDLogWarn(@"%@ Mismatched devices for recipient: %@", self.logTag, recipient.uniqueId);
|
2018-05-18 19:55:22 +02:00
|
|
|
|
2018-05-30 16:03:14 +02:00
|
|
|
NSError *_Nullable error = nil;
|
|
|
|
NSDictionary *_Nullable responseJson = nil;
|
|
|
|
if (responseData) {
|
|
|
|
responseJson = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
|
|
|
|
}
|
|
|
|
if (error || !responseJson) {
|
2018-05-18 20:35:00 +02:00
|
|
|
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson]);
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
|
2018-05-30 16:03:14 +02:00
|
|
|
[self handleMismatchedDevicesWithResponseJson:responseJson recipient:recipient completion:retrySend];
|
2018-05-18 20:35:00 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 410: {
|
|
|
|
// Stale devices
|
|
|
|
DDLogWarn(@"%@ Stale devices for recipient: %@", self.logTag, recipient.uniqueId);
|
|
|
|
|
2018-05-30 16:03:14 +02:00
|
|
|
NSError *_Nullable error = nil;
|
|
|
|
NSDictionary *_Nullable responseJson = nil;
|
|
|
|
if (responseData) {
|
|
|
|
responseJson = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
|
|
|
|
}
|
|
|
|
if (error || !responseJson) {
|
2018-05-18 20:35:00 +02:00
|
|
|
DDLogWarn(@"Stale devices but server didn't specify devices in response.");
|
|
|
|
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
|
2018-05-30 16:03:14 +02:00
|
|
|
[self handleStaleDevicesWithResponseJson:responseJson recipientId:recipient.uniqueId completion:retrySend];
|
2018-05-18 20:35:00 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
retrySend();
|
|
|
|
break;
|
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2018-05-30 16:03:14 +02:00
|
|
|
- (void)handleMismatchedDevicesWithResponseJson:(NSDictionary *)responseJson
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
completion:(void (^)(void))completionHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2018-05-30 16:03:14 +02:00
|
|
|
OWSAssert(responseJson);
|
|
|
|
OWSAssert(recipient);
|
|
|
|
OWSAssert(completionHandler);
|
|
|
|
|
|
|
|
NSArray *extraDevices = responseJson[@"extraDevices"];
|
|
|
|
NSArray *missingDevices = responseJson[@"missingDevices"];
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2017-11-10 17:04:40 +01:00
|
|
|
if (missingDevices.count > 0) {
|
|
|
|
NSString *localNumber = [TSAccountManager localNumber];
|
|
|
|
if ([localNumber isEqualToString:recipient.uniqueId]) {
|
2018-01-11 16:25:13 +01:00
|
|
|
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
|
2017-11-10 17:04:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-02 18:32:23 +01:00
|
|
|
[self.dbConnection
|
2018-01-31 17:44:51 +01:00
|
|
|
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
2018-01-30 21:05:04 +01:00
|
|
|
if (extraDevices.count < 1 && missingDevices.count < 1) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorNoMissingOrExtraDevices]);
|
|
|
|
}
|
2017-05-02 17:31:29 +02:00
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
if (extraDevices && extraDevices.count > 0) {
|
|
|
|
DDLogInfo(@"%@ removing extra devices: %@", self.logTag, extraDevices);
|
|
|
|
for (NSNumber *extraDeviceId in extraDevices) {
|
2018-03-05 15:30:58 +01:00
|
|
|
[self.primaryStorage deleteSessionForContact:recipient.uniqueId
|
2018-01-30 21:05:04 +01:00
|
|
|
deviceId:extraDeviceId.intValue
|
|
|
|
protocolContext:transaction];
|
|
|
|
}
|
|
|
|
|
|
|
|
[recipient removeDevices:[NSSet setWithArray:extraDevices]];
|
2017-04-07 01:11:04 +02:00
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
if (missingDevices && missingDevices.count > 0) {
|
|
|
|
DDLogInfo(@"%@ Adding missing devices: %@", self.logTag, missingDevices);
|
|
|
|
[recipient addDevices:[NSSet setWithArray:missingDevices]];
|
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
[recipient saveWithTransaction:transaction];
|
2016-10-14 23:00:29 +02:00
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
completionHandler();
|
|
|
|
});
|
|
|
|
}];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)handleMessageSentLocally:(TSOutgoingMessage *)message
|
|
|
|
{
|
|
|
|
if (message.shouldSyncTranscript) {
|
2017-06-22 19:56:38 +02:00
|
|
|
// TODO: I suspect we shouldn't optimistically set hasSyncedTranscript.
|
|
|
|
// We could set this in a success handler for [sendSyncTranscriptForMessage:].
|
2017-11-14 19:41:01 +01:00
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2017-11-15 12:39:10 +01:00
|
|
|
[message updateWithHasSyncedTranscript:YES transaction:transaction];
|
2017-11-14 19:41:01 +01:00
|
|
|
}];
|
2017-06-22 19:56:38 +02:00
|
|
|
[self sendSyncTranscriptForMessage:message];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 00:38:29 +02:00
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2018-04-18 02:53:27 +02:00
|
|
|
[[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:message
|
|
|
|
expirationStartedAt:[NSDate ows_millisecondTimeStamp]
|
|
|
|
transaction:transaction];
|
2018-04-17 00:38:29 +02:00
|
|
|
}];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message
|
2016-10-08 01:17:38 +02:00
|
|
|
{
|
2016-10-14 23:00:29 +02:00
|
|
|
OWSOutgoingSentMessageTranscript *sentMessageTranscript =
|
|
|
|
[[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message];
|
|
|
|
|
2017-11-15 12:39:10 +01:00
|
|
|
[self sendMessageToService:sentMessageTranscript
|
2016-10-14 23:00:29 +02:00
|
|
|
recipient:[SignalRecipient selfRecipient]
|
|
|
|
thread:message.thread
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
2018-07-03 23:31:25 +02:00
|
|
|
useWebsocketIfAvailable:YES
|
2016-10-08 01:17:38 +02:00
|
|
|
success:^{
|
2018-02-01 02:54:58 +01:00
|
|
|
DDLogInfo(@"Successfully sent sync transcript.");
|
2016-10-08 01:17:38 +02:00
|
|
|
}
|
2017-04-14 16:25:52 +02:00
|
|
|
failure:^(NSError *error) {
|
2017-04-05 01:44:14 +02:00
|
|
|
// FIXME: We don't yet honor the isRetryable flag here, since sendSyncTranscriptForMessage
|
|
|
|
// isn't yet wrapped in our retryable SendMessageOperation. Addressing this would require
|
|
|
|
// a refactor to the MessageSender. Note that we *do* however continue to respect the
|
|
|
|
// OWSMessageSenderRetryAttempts, which is an "inner" retry loop, encompassing only the
|
|
|
|
// messaging API.
|
2017-04-14 16:25:52 +02:00
|
|
|
DDLogInfo(@"Failed to send sync transcript: %@ (isRetryable: %d)", error, [error isRetryable]);
|
2016-10-08 01:17:38 +02:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
- (NSArray<NSDictionary *> *)deviceMessages:(TSOutgoingMessage *)message
|
|
|
|
forRecipient:(SignalRecipient *)recipient
|
|
|
|
{
|
2017-09-27 23:13:29 +02:00
|
|
|
OWSAssert(message);
|
|
|
|
OWSAssert(recipient);
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
|
2017-08-01 23:01:07 +02:00
|
|
|
|
|
|
|
NSData *plainText = [message buildPlainTextData:recipient];
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogDebug(@"%@ built message: %@ plainTextData.length: %lu",
|
|
|
|
self.logTag,
|
|
|
|
[message class],
|
|
|
|
(unsigned long)plainText.length);
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
for (NSNumber *deviceNumber in recipient.devices) {
|
|
|
|
@try {
|
2017-01-23 00:09:38 +01:00
|
|
|
__block NSDictionary *messageDict;
|
2017-01-31 15:46:25 +01:00
|
|
|
__block NSException *encryptionException;
|
2018-02-02 18:32:23 +01:00
|
|
|
[self.dbConnection
|
2018-01-30 21:05:04 +01:00
|
|
|
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
@try {
|
|
|
|
messageDict = [self encryptedMessageWithPlaintext:plainText
|
|
|
|
toRecipient:recipient.uniqueId
|
|
|
|
deviceId:deviceNumber
|
2018-03-05 15:30:58 +01:00
|
|
|
keyingStorage:self.primaryStorage
|
2018-01-30 21:05:04 +01:00
|
|
|
isSilent:message.isSilent
|
|
|
|
transaction:transaction];
|
|
|
|
} @catch (NSException *exception) {
|
|
|
|
encryptionException = exception;
|
|
|
|
}
|
|
|
|
}];
|
2017-09-13 22:48:38 +02:00
|
|
|
|
2017-01-31 15:46:25 +01:00
|
|
|
if (encryptionException) {
|
2017-11-08 20:04:51 +01:00
|
|
|
DDLogInfo(@"%@ Exception during encryption: %@", self.logTag, encryptionException);
|
2017-01-31 15:46:25 +01:00
|
|
|
@throw encryptionException;
|
|
|
|
}
|
2017-01-23 00:09:38 +01:00
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
if (messageDict) {
|
|
|
|
[messagesArray addObject:messageDict];
|
|
|
|
} else {
|
2018-01-25 16:44:13 +01:00
|
|
|
OWSRaiseException(InvalidMessageException, @"Failed to encrypt message");
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
} @catch (NSException *exception) {
|
|
|
|
if ([exception.name isEqualToString:OWSMessageSenderInvalidDeviceException]) {
|
|
|
|
[recipient removeDevices:[NSSet setWithObject:deviceNumber]];
|
|
|
|
} else {
|
|
|
|
@throw exception;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [messagesArray copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)encryptedMessageWithPlaintext:(NSData *)plainText
|
|
|
|
toRecipient:(NSString *)identifier
|
|
|
|
deviceId:(NSNumber *)deviceNumber
|
2018-03-05 15:30:58 +01:00
|
|
|
keyingStorage:(OWSPrimaryStorage *)storage
|
2017-09-27 23:13:29 +02:00
|
|
|
isSilent:(BOOL)isSilent
|
2018-01-30 21:05:04 +01:00
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
2017-09-27 23:13:29 +02:00
|
|
|
OWSAssert(plainText);
|
|
|
|
OWSAssert(identifier.length > 0);
|
|
|
|
OWSAssert(deviceNumber);
|
|
|
|
OWSAssert(storage);
|
2018-01-30 21:05:04 +01:00
|
|
|
OWSAssert(transaction);
|
2017-09-27 23:13:29 +02:00
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
if (![storage containsSession:identifier deviceId:[deviceNumber intValue] protocolContext:transaction]) {
|
2016-10-14 23:00:29 +02:00
|
|
|
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
2017-07-24 16:35:18 +02:00
|
|
|
__block PreKeyBundle *_Nullable bundle;
|
|
|
|
__block NSException *_Nullable exception;
|
2018-02-13 22:11:42 +01:00
|
|
|
// It's not ideal that we're using a semaphore inside a read/write transaction.
|
|
|
|
// To avoid deadlock, we need to ensure that our success/failure completions
|
|
|
|
// are called _off_ the main thread. Otherwise we'll deadlock if the main
|
|
|
|
// thread is blocked on opening a transaction.
|
|
|
|
TSRequest *request =
|
2018-03-02 04:29:59 +01:00
|
|
|
[OWSRequestFactory recipientPrekeyRequestWithRecipient:identifier deviceId:[deviceNumber stringValue]];
|
2018-02-13 22:11:42 +01:00
|
|
|
[self.networkManager makeRequest:request
|
2018-02-14 16:36:12 +01:00
|
|
|
completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
2016-10-14 23:00:29 +02:00
|
|
|
success:^(NSURLSessionDataTask *task, id responseObject) {
|
|
|
|
bundle = [PreKeyBundle preKeyBundleFromDictionary:responseObject forDeviceNumber:deviceNumber];
|
|
|
|
dispatch_semaphore_signal(sema);
|
|
|
|
}
|
|
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
2017-07-24 17:06:19 +02:00
|
|
|
if (!IsNSErrorNetworkFailure(error)) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdError([OWSAnalyticsEvents messageSenderErrorRecipientPrekeyRequestFailed]);
|
2017-07-24 17:06:19 +02:00
|
|
|
}
|
2017-07-21 22:22:07 +02:00
|
|
|
DDLogError(@"Server replied to PreKeyBundle request with error: %@", error);
|
2016-10-14 23:00:29 +02:00
|
|
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
|
|
|
if (response.statusCode == 404) {
|
2016-11-03 21:10:06 +01:00
|
|
|
// Can't throw exception from within callback as it's probabably a different thread.
|
|
|
|
exception = [NSException exceptionWithName:OWSMessageSenderInvalidDeviceException
|
|
|
|
reason:@"Device not registered"
|
|
|
|
userInfo:nil];
|
2016-11-10 15:59:07 +01:00
|
|
|
} else if (response.statusCode == 413) {
|
|
|
|
// Can't throw exception from within callback as it's probabably a different thread.
|
|
|
|
exception = [NSException exceptionWithName:OWSMessageSenderRateLimitedException
|
|
|
|
reason:@"Too many prekey requests"
|
|
|
|
userInfo:nil];
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
dispatch_semaphore_signal(sema);
|
|
|
|
}];
|
2018-02-13 17:31:02 +01:00
|
|
|
// FIXME: Currently this happens within a readwrite transaction - meaning our read-write transaction blocks
|
|
|
|
// on a network request.
|
2016-10-14 23:00:29 +02:00
|
|
|
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
2016-11-03 21:10:06 +01:00
|
|
|
if (exception) {
|
|
|
|
@throw exception;
|
|
|
|
}
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
if (!bundle) {
|
2018-01-25 16:44:13 +01:00
|
|
|
OWSRaiseException(
|
|
|
|
InvalidVersionException, @"Can't get a prekey bundle from the server with required information");
|
2016-10-14 23:00:29 +02:00
|
|
|
} else {
|
|
|
|
SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage
|
|
|
|
preKeyStore:storage
|
|
|
|
signedPreKeyStore:storage
|
2017-06-06 20:12:50 +02:00
|
|
|
identityKeyStore:[OWSIdentityManager sharedManager]
|
2016-10-14 23:00:29 +02:00
|
|
|
recipientId:identifier
|
|
|
|
deviceId:[deviceNumber intValue]];
|
|
|
|
@try {
|
2018-01-30 21:05:04 +01:00
|
|
|
[builder processPrekeyBundle:bundle protocolContext:transaction];
|
2016-10-14 23:00:29 +02:00
|
|
|
} @catch (NSException *exception) {
|
|
|
|
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
|
2018-01-25 16:44:13 +01:00
|
|
|
OWSRaiseExceptionWithUserInfo(UntrustedIdentityKeyException,
|
|
|
|
(@{ TSInvalidPreKeyBundleKey : bundle, TSInvalidRecipientKey : identifier }),
|
|
|
|
@"");
|
2016-10-14 23:00:29 +02:00
|
|
|
}
|
|
|
|
@throw exception;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage
|
|
|
|
preKeyStore:storage
|
|
|
|
signedPreKeyStore:storage
|
2017-06-06 20:12:50 +02:00
|
|
|
identityKeyStore:[OWSIdentityManager sharedManager]
|
2016-10-14 23:00:29 +02:00
|
|
|
recipientId:identifier
|
|
|
|
deviceId:[deviceNumber intValue]];
|
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
id<CipherMessage> encryptedMessage =
|
|
|
|
[cipher encryptMessage:[plainText paddedMessageBody] protocolContext:transaction];
|
2017-01-23 00:09:38 +01:00
|
|
|
|
|
|
|
|
2016-10-14 23:00:29 +02:00
|
|
|
NSData *serializedMessage = encryptedMessage.serialized;
|
|
|
|
TSWhisperMessageType messageType = [self messageTypeForCipherMessage:encryptedMessage];
|
|
|
|
|
2018-01-30 21:05:04 +01:00
|
|
|
OWSMessageServiceParams *messageParams =
|
|
|
|
[[OWSMessageServiceParams alloc] initWithType:messageType
|
|
|
|
recipientId:identifier
|
|
|
|
device:[deviceNumber intValue]
|
|
|
|
content:serializedMessage
|
|
|
|
isSilent:isSilent
|
|
|
|
registrationId:[cipher remoteRegistrationId:transaction]];
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error];
|
|
|
|
|
|
|
|
if (error) {
|
2017-07-27 18:29:05 +02:00
|
|
|
OWSProdError([OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson]);
|
2016-10-14 23:00:29 +02:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return jsonDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (TSWhisperMessageType)messageTypeForCipherMessage:(id<CipherMessage>)cipherMessage
|
|
|
|
{
|
|
|
|
if ([cipherMessage isKindOfClass:[PreKeyWhisperMessage class]]) {
|
|
|
|
return TSPreKeyWhisperMessageType;
|
|
|
|
} else if ([cipherMessage isKindOfClass:[WhisperMessage class]]) {
|
|
|
|
return TSEncryptedWhisperMessageType;
|
|
|
|
}
|
|
|
|
return TSUnknownMessageType;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)saveGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread
|
|
|
|
{
|
|
|
|
if (message.groupMetaMessage == TSGroupMessageDeliver) {
|
2017-04-11 22:57:28 +02:00
|
|
|
// TODO: Why is this necessary?
|
|
|
|
[message save];
|
2016-10-14 23:00:29 +02:00
|
|
|
} else if (message.groupMetaMessage == TSGroupMessageQuit) {
|
2017-09-21 23:25:13 +02:00
|
|
|
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
2016-10-14 23:00:29 +02:00
|
|
|
inThread:thread
|
|
|
|
messageType:TSInfoMessageTypeGroupQuit
|
|
|
|
customMessage:message.customMessage] save];
|
|
|
|
} else {
|
2017-09-21 23:25:13 +02:00
|
|
|
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
2016-10-14 23:00:29 +02:00
|
|
|
inThread:thread
|
|
|
|
messageType:TSInfoMessageTypeGroupUpdate
|
|
|
|
customMessage:message.customMessage] save];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-08 05:21:25 +02:00
|
|
|
// Called when the server indicates that the devices no longer exist - e.g. when the remote recipient has reinstalled.
|
2018-05-30 16:03:14 +02:00
|
|
|
- (void)handleStaleDevicesWithResponseJson:(NSDictionary *)responseJson
|
|
|
|
recipientId:(NSString *)identifier
|
|
|
|
completion:(void (^)(void))completionHandler
|
2016-10-14 23:00:29 +02:00
|
|
|
{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
2018-05-30 16:03:14 +02:00
|
|
|
NSArray *devices = responseJson[@"staleDevices"];
|
2016-10-14 23:00:29 +02:00
|
|
|
|
|
|
|
if (!([devices count] > 0)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-02 18:32:23 +01:00
|
|
|
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
2017-04-07 01:11:04 +02:00
|
|
|
for (NSUInteger i = 0; i < [devices count]; i++) {
|
|
|
|
int deviceNumber = [devices[i] intValue];
|
2018-03-05 15:30:58 +01:00
|
|
|
[[OWSPrimaryStorage sharedManager] deleteSessionForContact:identifier
|
|
|
|
deviceId:deviceNumber
|
|
|
|
protocolContext:transaction];
|
2017-04-07 01:11:04 +02:00
|
|
|
}
|
2018-01-30 21:05:04 +01:00
|
|
|
}];
|
|
|
|
completionHandler();
|
2016-10-14 23:00:29 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-10-08 01:17:38 +02:00
|
|
|
@end
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|