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"
2016-10-14 23:00:29 +02:00
# import "ContactsUpdater.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"
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"
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 ;
@ property ( nonatomic , readonly ) ContactsUpdater * contactsUpdater ;
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
contactsUpdater : ( ContactsUpdater * ) contactsUpdater
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 ;
_contactsUpdater = contactsUpdater ;
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 = @ [ ] ;
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 ] ;
}
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-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-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-04-23 16:30:51 +02:00
- ( NSArray < SignalRecipient * > * ) getRecipientsForRecipientIds : ( NSArray < NSString * > * ) recipientIds error : ( NSError * * ) error
2016-10-14 23:00:29 +02:00
{
2018-04-23 16:30:51 +02:00
OWSAssert ( error ) ;
2018-04-30 16:21:58 +02:00
* error = nil ;
2018-04-23 16:30:51 +02:00
2016-10-14 23:00:29 +02:00
NSMutableArray < SignalRecipient * > * recipients = [ NSMutableArray new ] ;
2018-04-23 16:30:51 +02:00
for ( NSString * recipientId in recipientIds ) {
2016-10-14 23:00:29 +02:00
SignalRecipient * existingRecipient = [ SignalRecipient recipientWithTextSecureIdentifier : recipientId ] ;
if ( existingRecipient ) {
[ recipients addObject : existingRecipient ] ;
} else {
SignalRecipient * newRecipient = [ self . contactsUpdater synchronousLookup : recipientId error : error ] ;
if ( newRecipient ) {
[ recipients addObject : newRecipient ] ;
}
}
}
if ( recipients . count = = 0 && ! * error ) {
// error should be set in contactsUpater , but just in case .
2017-07-27 18:29:05 +02:00
OWSProdError ( [ OWSAnalyticsEvents messageSenderErrorCouldNotFindContacts1 ] ) ;
2016-10-14 23:00:29 +02:00
* error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
}
return [ recipients copy ] ;
}
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 ] , ^ {
2017-09-29 03:12:02 +02:00
TSThread * thread = message . thread ;
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 .
[ self handleSendToMyself : message ] ;
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 ] ;
}
} ] ;
}
2016-10-14 23:00:29 +02:00
NSError * error ;
NSArray < SignalRecipient * > * recipients =
2018-04-23 16:30:51 +02:00
[ self getRecipientsForRecipientIds : sendingRecipientIds . allObjects error : & error ] ;
2016-11-08 19:57:05 +01:00
if ( recipients . count = = 0 ) {
2017-04-14 16:25:52 +02:00
if ( ! error ) {
2017-07-27 18:29:05 +02:00
OWSProdError ( [ OWSAnalyticsEvents messageSenderErrorCouldNotFindContacts2 ] ) ;
2017-04-14 16:25:52 +02:00
error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
2016-11-08 19:57:05 +01:00
}
2017-04-17 22:45:22 +02:00
// If no recipients were found , there ' s no reason to retry . It will just fail again .
2017-04-14 16:25:52 +02:00
[ error setIsRetryable : NO ] ;
failureHandler ( error ) ;
return ;
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 ;
}
2016-10-25 15:54:43 +02:00
SignalRecipient * recipient = [ SignalRecipient recipientWithTextSecureIdentifier : recipientContactId ] ;
2016-10-14 23:00:29 +02:00
if ( ! recipient ) {
NSError * error ;
// possibly returns nil .
2016-12-16 16:26:15 +01:00
recipient = [ self . contactsUpdater synchronousLookup : recipientContactId error : & error ] ;
2016-10-14 23:00:29 +02:00
if ( error ) {
2017-02-02 01:16:07 +01:00
if ( error . code = = OWSErrorCodeNoSuchSignalRecipient ) {
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ recipient contact not found" , self . logTag ) ;
2016-10-14 23:00:29 +02:00
[ self unregisteredRecipient : recipient message : message thread : thread ] ;
}
2017-02-02 01:16:07 +01:00
2017-07-27 18:29:05 +02:00
OWSProdError ( [ OWSAnalyticsEvents messageSenderErrorCouldNotFindContacts3 ] ) ;
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
}
}
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
recipient : recipient
thread : thread
attempts : OWSMessageSenderRetryAttempts
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
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 ] ;
2017-03-05 14:30:04 +01:00
[ [ TSInfoMessage userNotRegisteredMessageInThread : thread ]
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
thread : ( TSThread * ) thread
attempts : ( int ) remainingAttempts
success : ( void ( ^ ) ( void ) ) successHandler
failure : ( RetryableFailureHandler ) failureHandler
2016-10-14 23:00:29 +02:00
{
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
}
2016-10-14 23:00:29 +02:00
if ( remainingAttempts <= 0 ) {
// 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
}
remainingAttempts - = 1 ;
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 ;
2017-11-10 17:04:40 +01:00
} else if ( mayHaveLinkedDevices ) {
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 .
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ sync message has no device messages but account has secondary devices." , self . logTag ) ;
2017-11-02 19:10:45 +01:00
} else if ( hasDeviceMessages ) {
2017-11-07 20:42:32 +01:00
OWSFail ( @ "%@ sync message has device messages for unknown secondary devices." , self . logTag ) ;
2017-11-02 19:10:45 +01:00
} else {
// Account has secondary devices ; proceed as usual .
2017-09-15 16:29:46 +02:00
}
} else {
OWSAssert ( deviceMessages . count > 0 ) ;
}
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 .
//
// 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
relay : recipient . relay
timeStamp : message . timestamp ] ;
2016-10-14 23:00:29 +02:00
[ self . networkManager makeRequest : request
success : ^ ( NSURLSessionDataTask * task , id responseObject ) {
2018-03-02 05:31:41 +01:00
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 ] ;
}
2016-10-14 23:00:29 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2018-04-23 16:30:51 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
[ recipient saveWithTransaction : transaction ] ;
[ message updateWithSentRecipient : recipient . uniqueId transaction : transaction ] ;
} ] ;
2016-10-14 23:00:29 +02:00
[ self handleMessageSentLocally : message ] ;
successHandler ( ) ;
} ) ;
}
failure : ^ ( NSURLSessionDataTask * task , NSError * error ) {
2018-03-22 14:41:27 +01:00
DDLogInfo ( @ "%@ sending to recipient: %@, failed with error." , self . logTag , recipient . uniqueId ) ;
2017-02-09 19:50:32 +01:00
[ DDLog flushLog ] ;
2016-10-14 23:00:29 +02:00
NSHTTPURLResponse * response = ( NSHTTPURLResponse * ) task . response ;
long statuscode = response . statusCode ;
NSData * responseData = error . userInfo [ AFNetworkingOperationFailingURLResponseDataErrorKey ] ;
2017-11-15 12:39:10 +01:00
void ( ^ retrySend ) ( void ) = ^ void ( ) {
2016-10-14 23:00:29 +02:00
if ( remainingAttempts <= 0 ) {
2017-04-05 01:44:14 +02:00
// Since we ' ve already repeatedly failed to send to the messaging API ,
// 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
}
2016-10-24 19:54:00 +02:00
2016-10-14 23:00:29 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2017-11-08 20:04:51 +01:00
DDLogDebug ( @ "%@ Retrying: %@" , self . logTag , message . debugDescription ) ;
2017-11-15 12:39:10 +01:00
[ self sendMessageToService : message
recipient : recipient
thread : thread
attempts : remainingAttempts
success : successHandler
failure : failureHandler ] ;
2016-10-14 23:00:29 +02:00
} ) ;
} ;
switch ( statuscode ) {
2016-12-15 23:09:08 +01:00
case 401 : {
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ Unable to send due to invalid credentials. Did the user's client get de-authed by "
@ "registering elsewhere?" ,
self . logTag ) ;
2016-12-15 23:09:08 +01:00
NSError * error = OWSErrorWithCodeDescription ( OWSErrorCodeSignalServiceFailure , NSLocalizedString ( @ "ERROR_DESCRIPTION_SENDING_UNAUTHORIZED" , @ "Error message when attempting to send message" ) ) ;
2017-04-05 01:44:14 +02:00
// No need to retry if we ' ve been de - authed .
2017-04-14 16:25:52 +02:00
[ error setIsRetryable : NO ] ;
return failureHandler ( error ) ;
2016-12-15 23:09:08 +01:00
}
2016-10-14 23:00:29 +02:00
case 404 : {
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ Unregistered recipient: %@" , self . logTag , recipient . uniqueId ) ;
2017-05-02 17:31:29 +02:00
2016-10-14 23:00:29 +02:00
[ self unregisteredRecipient : recipient message : message thread : thread ] ;
2016-10-25 15:54:43 +02:00
NSError * error = OWSErrorMakeNoSuchSignalRecipientError ( ) ;
2017-04-05 01:44:14 +02:00
// No need to retry if the recipient is not registered .
2017-04-14 16:25:52 +02:00
[ 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 ) ;
2016-10-14 23:00:29 +02:00
}
case 409 : {
// Mismatched devices
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ Mismatch Devices for recipient: %@" , self . logTag , recipient . uniqueId ) ;
2016-10-14 23:00:29 +02:00
NSError * error ;
NSDictionary * serializedResponse =
[ NSJSONSerialization JSONObjectWithData : responseData options : 0 error : & error ] ;
if ( error ) {
2017-07-27 18:29:05 +02:00
OWSProdError ( [ OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson ] ) ;
2017-04-14 16:25:52 +02:00
[ error setIsRetryable : YES ] ;
return failureHandler ( error ) ;
2016-10-14 23:00:29 +02:00
}
2017-04-07 01:11:04 +02:00
[ self handleMismatchedDevices : serializedResponse recipient : recipient completion : retrySend ] ;
2016-10-14 23:00:29 +02:00
break ;
}
case 410 : {
2017-04-14 16:25:52 +02:00
// Stale devices
2017-11-08 20:04:51 +01:00
DDLogWarn ( @ "%@ Stale devices for recipient: %@" , self . logTag , recipient . uniqueId ) ;
2016-10-14 23:00:29 +02:00
if ( ! responseData ) {
DDLogWarn ( @ "Stale devices but server didn't specify devices in response." ) ;
NSError * error = OWSErrorMakeUnableToProcessServerResponseError ( ) ;
2017-04-14 16:25:52 +02:00
[ error setIsRetryable : YES ] ;
return failureHandler ( error ) ;
2016-10-14 23:00:29 +02:00
}
2017-04-07 01:11:04 +02:00
[ self handleStaleDevicesWithResponse : responseData
recipientId : recipient . uniqueId
completion : retrySend ] ;
2016-10-14 23:00:29 +02:00
break ;
}
default :
retrySend ( ) ;
break ;
}
} ] ;
}
2017-04-07 01:11:04 +02:00
- ( void ) handleMismatchedDevices : ( NSDictionary * ) dictionary
recipient : ( SignalRecipient * ) recipient
2017-11-08 20:04:51 +01:00
completion : ( void ( ^ ) ( void ) ) completionHandler
2016-10-14 23:00:29 +02:00
{
NSArray * extraDevices = [ dictionary objectForKey : @ "extraDevices" ] ;
NSArray * missingDevices = [ dictionary objectForKey : @ "missingDevices" ] ;
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 ) handleSendToMyself : ( TSOutgoingMessage * ) outgoingMessage
{
2016-10-28 19:18:46 +02:00
[ self handleMessageSentLocally : outgoingMessage ] ;
if ( ! ( outgoingMessage . body || outgoingMessage . hasAttachments ) ) {
2018-02-15 21:15:23 +01:00
// We only want to "clone" text and attachment messages .
//
// This method shouldn ' t be called for sync messages , so this
// probably represents a bug .
OWSFail ( @ "%@ Refusing to make incoming copy of non-standard message sent to self: %@" ,
2018-02-15 17:02:26 +01:00
self . logTag ,
outgoingMessage ) ;
2016-10-28 19:18:46 +02:00
return ;
}
2017-03-23 21:03:46 +01:00
// Getting the local number uses a transaction , so we need to do that before we
// create a new transaction to avoid deadlock .
NSString * contactId = [ TSAccountManager localNumber ] ;
2016-10-14 23:00:29 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
TSContactThread * cThread =
2017-03-23 21:03:46 +01:00
[ TSContactThread getOrCreateThreadWithContactId : contactId transaction : transaction ] ;
2016-10-14 23:00:29 +02:00
[ cThread saveWithTransaction : transaction ] ;
2017-09-21 17:55:25 +02:00
2017-11-10 17:22:59 +01:00
// We need to clone any attachments for message sent to self ; otherwise deleting
// the incoming or outgoing copy of the message will break the other .
NSMutableArray < NSString * > * attachmentIds = [ NSMutableArray new ] ;
for ( NSString * attachmentId in outgoingMessage . attachmentIds ) {
TSAttachmentStream * _Nullable outgoingAttachment =
2017-11-10 19:12:41 +01:00
[ TSAttachmentStream fetchObjectWithUniqueID : attachmentId transaction : transaction ] ;
2017-11-10 17:22:59 +01:00
OWSAssert ( outgoingAttachment ) ;
if ( ! outgoingAttachment ) {
DDLogError ( @ "%@ Couldn't load outgoing attachment for message sent to self." , self . logTag ) ;
} else {
TSAttachmentStream * incomingAttachment =
[ [ TSAttachmentStream alloc ] initWithContentType : outgoingAttachment . contentType
byteCount : outgoingAttachment . byteCount
sourceFilename : outgoingAttachment . sourceFilename ] ;
NSError * error ;
NSData * _Nullable data = [ outgoingAttachment readDataFromFileWithError : & error ] ;
if ( ! data || error ) {
DDLogError ( @ "%@ Couldn't load attachment data for message sent to self: %@." , self . logTag , error ) ;
} else {
[ incomingAttachment writeData : data error : & error ] ;
if ( error ) {
DDLogError (
@ "%@ Couldn't copy attachment data for message sent to self: %@." , self . logTag , error ) ;
} else {
[ incomingAttachment saveWithTransaction : transaction ] ;
[ attachmentIds addObject : incomingAttachment . uniqueId ] ;
}
}
}
}
2017-09-21 17:55:25 +02:00
// We want the incoming message to appear after the outgoing message .
2018-04-27 21:29:19 +02:00
//
// TODO : We need to be careful to duplicate the attachments for
// quoted message and contact share .
2016-10-14 23:00:29 +02:00
TSIncomingMessage * incomingMessage =
2018-02-07 18:44:09 +01:00
[ [ TSIncomingMessage alloc ] initIncomingMessageWithTimestamp : ( outgoingMessage . timestamp + 1 )
inThread : cThread
authorId : [ cThread contactIdentifier ]
sourceDeviceId : [ OWSDevice currentDeviceId ]
messageBody : outgoingMessage . body
attachmentIds : attachmentIds
expiresInSeconds : outgoingMessage . expiresInSeconds
2018-04-27 21:29:19 +02:00
quotedMessage : outgoingMessage . quotedMessage
2018-05-01 17:26:01 +02:00
contactShare : outgoingMessage . contactShare ] ;
2016-10-14 23:00:29 +02:00
[ incomingMessage saveWithTransaction : transaction ] ;
} ] ;
}
- ( 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
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 .
2017-04-07 01:11:04 +02:00
- ( void ) handleStaleDevicesWithResponse : ( NSData * ) responseData
recipientId : ( NSString * ) identifier
2017-11-08 20:04:51 +01:00
completion : ( void ( ^ ) ( void ) ) completionHandler
2016-10-14 23:00:29 +02:00
{
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
NSDictionary * serialization = [ NSJSONSerialization JSONObjectWithData : responseData options : 0 error : nil ] ;
NSArray * devices = serialization [ @ "staleDevices" ] ;
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