2017-01-23 00:09:38 +01:00
//
2019-01-04 21:06:52 +01:00
// Copyright ( c ) 2019 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-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"
2018-08-28 21:22:47 +02:00
# import "OWSDispatch.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"
2019-05-10 08:32:20 +02:00
# import "OWSPrimaryStorage+Loki.h"
2018-03-05 15:30:58 +01:00
# 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"
2018-09-20 18:52:43 +02:00
# import "SSKEnvironment.h"
2016-10-14 23:00:29 +02:00
# 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-10-03 14:55:14 +02:00
# import "TSRequest.h"
2018-05-18 19:55:22 +02:00
# import "TSSocketManager.h"
2016-10-08 01:17:38 +02:00
# import "TSThread.h"
2019-10-04 06:52:59 +02:00
# import "TSContactThread.h"
2019-05-21 03:40:29 +02:00
# import "LKFriendRequestMessage.h"
2019-09-25 04:22:34 +02:00
# import "LKDeviceLinkMessage.h"
2019-05-24 08:07:00 +02:00
# import "LKAddressMessage.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 >
2018-07-20 21:22:51 +02:00
# import < PromiseKit / AnyPromise . h >
2018-10-03 14:55:14 +02:00
# import < SignalCoreKit / NSData + OWS . h >
2018-09-21 21:41:10 +02:00
# import < SignalCoreKit / NSDate + OWS . h >
2018-10-28 19:18:04 +01:00
# import < SignalCoreKit / SCKExceptionWrapper . h >
2018-09-21 21:41:10 +02:00
# import < SignalCoreKit / Threading . h >
2018-10-03 14:55:14 +02:00
# import < SignalMetadataKit / SignalMetadataKit - Swift . h >
2018-07-20 21:22:51 +02:00
# import < SignalServiceKit / SignalServiceKit - Swift . h >
2019-08-22 02:12:48 +02:00
# import < SignalServiceKit / ProfileManagerProtocol . h >
2016-10-08 01:17:38 +02:00
NS_ASSUME _NONNULL _BEGIN
2019-01-04 21:06:52 +01:00
NSString * NoSessionForTransientMessageException = @ "NoSessionForTransientMessageException" ;
2018-04-11 21:21:17 +02:00
const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024 ;
2018-01-11 23:41:13 +01:00
2018-10-04 20:39:40 +02:00
NSError * SSKEnsureError ( NSError * _Nullable error , OWSErrorCode fallbackCode , NSString * fallbackErrorDescription )
{
if ( error ) {
return error ;
}
2018-10-05 18:53:54 +02:00
OWSCFailDebug ( @ "Using fallback error." ) ;
return OWSErrorWithCodeDescription ( fallbackCode , fallbackErrorDescription ) ;
2018-10-04 20:39:40 +02:00
}
# pragma mark -
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 -
2018-11-02 16:33:05 +01:00
@ implementation OWSOutgoingAttachmentInfo
- ( instancetype ) initWithDataSource : ( DataSource * ) dataSource
contentType : ( NSString * ) contentType
sourceFilename : ( nullable NSString * ) sourceFilename
2018-11-06 23:30:09 +01:00
caption : ( nullable NSString * ) caption
2018-11-07 18:00:34 +01:00
albumMessageId : ( nullable NSString * ) albumMessageId
2018-11-02 16:33:05 +01:00
{
self = [ super init ] ;
if ( ! self ) {
return self ;
}
_dataSource = dataSource ;
_contentType = contentType ;
_sourceFilename = sourceFilename ;
2018-11-06 23:30:09 +01:00
_caption = caption ;
2018-11-07 18:00:34 +01:00
_albumMessageId = albumMessageId ;
2018-11-02 16:33:05 +01:00
return self ;
}
@ end
# 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
2018-07-16 19:11:00 +02:00
failure : ( void ( ^ ) ( NSError * 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 ) ;
2018-07-16 19:11:00 +02:00
@ property ( nonatomic , readonly ) void ( ^ failureHandler ) ( NSError * error ) ;
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
@ implementation OWSSendMessageOperation
- ( instancetype ) initWithMessage : ( TSOutgoingMessage * ) message
messageSender : ( OWSMessageSender * ) messageSender
2018-04-04 22:26:41 +02:00
dbConnection : ( YapDatabaseConnection * ) dbConnection
success : ( void ( ^ ) ( void ) ) successHandler
2018-07-16 19:11:00 +02:00
failure : ( void ( ^ ) ( NSError * error ) ) failureHandler
2017-03-17 17:26:25 +01:00
{
self = [ super init ] ;
if ( ! self ) {
return self ;
}
_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-11-27 15:54:18 +01:00
__block NSError * _Nullable error = [ super checkForPreconditionError ] ;
2018-07-18 22:46:46 +02:00
if ( error ) {
return error ;
2018-04-04 22:26:41 +02:00
}
2017-03-17 17:26:25 +01:00
2018-04-04 22:26:41 +02:00
// Sanity check preconditions
if ( self . message . hasAttachments ) {
2018-11-02 16:33:05 +01:00
[ self . dbConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
for ( TSAttachment * attachment in [ self . message attachmentsWithTransaction : transaction ] ) {
2018-11-26 23:11:20 +01:00
if ( ! [ attachment isKindOfClass : [ TSAttachmentStream class ] ] ) {
2018-11-29 20:48:56 +01:00
error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
2018-11-27 15:54:18 +01:00
break ;
2018-11-26 23:11:20 +01:00
}
2018-11-02 16:33:05 +01:00
TSAttachmentStream * attachmentStream = ( TSAttachmentStream * ) attachment ;
OWSAssertDebug ( attachmentStream ) ;
OWSAssertDebug ( attachmentStream . serverId ) ;
OWSAssertDebug ( attachmentStream . isUploaded ) ;
}
2018-04-04 22:26:41 +02:00
} ] ;
}
2017-03-17 17:26:25 +01:00
2018-11-27 15:54:18 +01:00
return error ;
2018-04-04 22:26:41 +02:00
}
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 ] ) {
2018-08-27 18:51:32 +02:00
OWSLogInfo ( @ "aborting message send; message deleted." ) ;
2017-11-14 19:41:01 +01:00
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 ) {
2018-08-27 18:51:32 +02:00
OWSFailDebug ( @ "unexpected message status: %@" , self . message . statusDescription ) ;
2018-04-30 16:21:58 +02:00
}
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
{
2019-02-26 17:47:43 +01:00
OWSLogError ( @ "failed with error: %@" , error ) ;
2018-04-04 22:26:41 +02:00
self . failureHandler ( error ) ;
2017-03-17 17:26:25 +01:00
}
@ end
2018-10-03 14:55:14 +02:00
# pragma mark -
2016-10-14 23:00:29 +02:00
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 ( )
2018-03-05 15:30:58 +01:00
@ property ( nonatomic , readonly ) OWSPrimaryStorage * primaryStorage ;
2016-10-14 23:00:29 +02:00
@ property ( nonatomic , readonly ) YapDatabaseConnection * dbConnection ;
2017-03-23 19:35:30 +01:00
@ property ( atomic , readonly ) NSMutableDictionary < NSString * , NSOperationQueue * > * sendingQueueMap ;
2016-10-08 01:17:38 +02:00
@ end
2018-10-03 14:55:14 +02:00
# pragma mark -
2016-10-08 01:17:38 +02:00
@ implementation OWSMessageSender
2018-09-20 18:52:43 +02:00
- ( instancetype ) initWithPrimaryStorage : ( OWSPrimaryStorage * ) primaryStorage
2016-10-08 01:17:38 +02:00
{
self = [ super init ] ;
if ( ! self ) {
return self ;
}
2018-03-05 15:30:58 +01:00
_primaryStorage = primaryStorage ;
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 ;
}
2018-10-05 16:32:32 +02:00
# pragma mark - Dependencies
2018-10-03 14:55:14 +02:00
2018-09-20 18:52:43 +02:00
- ( id < ContactsManagerProtocol > ) contactsManager
2017-04-01 22:47:16 +02:00
{
2018-09-20 18:52:43 +02:00
OWSAssertDebug ( SSKEnvironment . shared . contactsManager ) ;
2017-04-01 22:47:16 +02:00
2018-09-20 18:52:43 +02:00
return SSKEnvironment . shared . contactsManager ;
}
- ( OWSBlockingManager * ) blockingManager
{
OWSAssertDebug ( SSKEnvironment . shared . blockingManager ) ;
return SSKEnvironment . shared . blockingManager ;
}
- ( TSNetworkManager * ) networkManager
{
OWSAssertDebug ( SSKEnvironment . shared . networkManager ) ;
return SSKEnvironment . shared . networkManager ;
2017-04-01 22:47:16 +02:00
}
2018-10-03 14:55:14 +02:00
- ( id < OWSUDManager > ) udManager
{
OWSAssertDebug ( SSKEnvironment . shared . udManager ) ;
return SSKEnvironment . shared . udManager ;
}
- ( TSAccountManager * ) tsAccountManager
{
return TSAccountManager . sharedInstance ;
}
2018-10-03 23:04:41 +02:00
- ( OWSIdentityManager * ) identityManager
{
return SSKEnvironment . shared . identityManager ;
}
2018-10-03 14:55:14 +02:00
# pragma mark -
2017-03-23 17:41:15 +01:00
- ( NSOperationQueue * ) sendingQueueForMessage : ( TSOutgoingMessage * ) message
{
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( message ) ;
2017-03-23 17:41:15 +01:00
2018-04-04 22:26:41 +02:00
2017-03-23 17:41:15 +01:00
NSString * kDefaultQueueKey = @ "kDefaultQueueKey" ;
NSString * queueKey = message . uniqueThreadId ? : kDefaultQueueKey ;
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( queueKey . length > 0 ) ;
2017-03-23 17:41:15 +01:00
2018-04-04 22:26:41 +02:00
if ( [ kDefaultQueueKey isEqualToString : queueKey ] ) {
// when do we get here ?
2018-08-27 18:51:32 +02:00
OWSLogDebug ( @ "using default message queue" ) ;
2018-04-04 22:26:41 +02:00
}
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 ;
2018-09-14 20:02:17 +02:00
sendingQueue . name = [ NSString stringWithFormat : @ "%@:%@" , self . logTag , queueKey ] ;
2017-03-23 17:41:15 +01:00
self . sendingQueueMap [ queueKey ] = sendingQueue ;
}
return sendingQueue ;
}
}
2018-10-20 19:51:48 +02:00
- ( void ) sendMessage : ( TSOutgoingMessage * ) message
success : ( void ( ^ ) ( void ) ) successHandler
failure : ( void ( ^ ) ( NSError * error ) ) failureHandler
2017-03-17 17:26:25 +01:00
{
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( message ) ;
2018-01-11 23:41:13 +01:00
if ( message . body . length > 0 ) {
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( [ message . body lengthOfBytesUsingEncoding : NSUTF8StringEncoding ] <= kOversizeTextMessageSizeThreshold ) ;
2018-01-11 23:41:13 +01:00
}
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 ) , ^ {
2019-01-17 17:56:21 +01:00
NSMutableArray < NSString * > * allAttachmentIds = [ NSMutableArray new ] ;
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 ) {
2019-01-17 17:56:21 +01:00
[ allAttachmentIds
addObjectsFromArray : [ OutgoingMessagePreparer prepareMessageForSending : message transaction : 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
2019-01-17 17:56:21 +01:00
for ( NSString * attachmentId in allAttachmentIds ) {
2019-10-18 00:46:44 +02:00
OWSUploadOperation * uploadAttachmentOperation = [ [ OWSUploadOperation alloc ] initWithAttachmentId : attachmentId threadID : message . thread . uniqueId dbConnection : self . dbConnection ] ;
2019-01-17 17:56:21 +01:00
// TODO : put attachment uploads on a ( low priority ) concurrent queue
2018-04-04 22:26:41 +02:00
[ sendMessageOperation addDependency : uploadAttachmentOperation ] ;
[ sendingQueue addOperation : uploadAttachmentOperation ] ;
2017-04-10 16:35:43 +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
}
2018-10-20 19:51:48 +02:00
- ( void ) sendTemporaryAttachment : ( 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
{
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( dataSource ) ;
2017-09-08 18:51:25 +02:00
2017-11-15 12:39:10 +01:00
void ( ^ successWithDeleteHandler ) ( void ) = ^ ( ) {
2016-10-14 23:00:29 +02:00
successHandler ( ) ;
2018-08-27 18:51:32 +02:00
OWSLogDebug ( @ "Removing successful temporary attachment message with attachment ids: %@" , message . attachmentIds ) ;
2016-10-14 23:00:29 +02:00
[ message remove ] ;
} ;
void ( ^ failureWithDeleteHandler ) ( NSError * error ) = ^ ( NSError * error ) {
failureHandler ( error ) ;
2018-08-27 18:51:32 +02:00
OWSLogDebug ( @ "Removing failed temporary attachment message with attachment ids: %@" , message . attachmentIds ) ;
2016-10-14 23:00:29 +02:00
[ message remove ] ;
} ;
2018-10-20 19:51:48 +02:00
[ self sendAttachment : dataSource
contentType : contentType
sourceFilename : nil
2018-11-12 20:28:44 +01:00
albumMessageId : nil
2018-10-20 19:51:48 +02:00
inMessage : message
success : successWithDeleteHandler
failure : failureWithDeleteHandler ] ;
2016-10-14 23:00:29 +02:00
}
2018-10-20 19:51:48 +02:00
- ( void ) sendAttachment : ( DataSource * ) dataSource
contentType : ( NSString * ) contentType
sourceFilename : ( nullable NSString * ) sourceFilename
2018-11-12 20:28:44 +01:00
albumMessageId : ( nullable NSString * ) albumMessageId
2018-10-20 19:51:48 +02:00
inMessage : ( TSOutgoingMessage * ) message
2018-11-12 20:28:44 +01:00
success : ( void ( ^ ) ( void ) ) success
failure : ( void ( ^ ) ( NSError * error ) ) failure
2016-10-14 23:00:29 +02:00
{
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( dataSource ) ;
2018-11-07 18:00:34 +01:00
2018-11-02 16:33:05 +01:00
OWSOutgoingAttachmentInfo * attachmentInfo = [ [ OWSOutgoingAttachmentInfo alloc ] initWithDataSource : dataSource
contentType : contentType
2018-11-06 23:30:09 +01:00
sourceFilename : sourceFilename
2018-11-07 18:00:34 +01:00
caption : nil
albumMessageId : albumMessageId ] ;
2018-11-12 20:28:44 +01:00
[ self sendAttachments : @ [
attachmentInfo ,
]
inMessage : message
success : success
failure : failure ] ;
}
- ( void ) sendAttachments : ( NSArray < OWSOutgoingAttachmentInfo * > * ) attachmentInfos
inMessage : ( TSOutgoingMessage * ) message
success : ( void ( ^ ) ( void ) ) success
failure : ( void ( ^ ) ( NSError * error ) ) failure
{
OWSAssertDebug ( attachmentInfos . count > 0 ) ;
[ OutgoingMessagePreparer prepareAttachments : attachmentInfos
2018-11-02 16:33:05 +01:00
inMessage : message
completionHandler : ^ ( NSError * _Nullable error ) {
if ( error ) {
2018-11-12 20:28:44 +01:00
failure ( error ) ;
2018-11-02 16:33:05 +01:00
return ;
}
2018-11-12 20:28:44 +01:00
[ self sendMessage : message success : success failure : failure ] ;
2018-11-02 16:33:05 +01:00
} ] ;
2016-10-14 23:00:29 +02:00
}
2017-11-15 12:39:10 +01:00
- ( void ) sendMessageToService : ( TSOutgoingMessage * ) message
2018-10-03 23:04:41 +02:00
success : ( void ( ^ ) ( void ) ) success
failure : ( RetryableFailureHandler ) failure
{
[ self . udManager
2018-10-30 17:05:06 +01:00
ensureSenderCertificateWithSuccess : ^ ( SMKSenderCertificate * senderCertificate ) {
2018-10-04 20:39:40 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
[ self sendMessageToService : message senderCertificate : senderCertificate success : success failure : failure ] ;
} ) ;
2018-10-03 23:04:41 +02:00
}
failure : ^ ( NSError * error ) {
OWSLogError ( @ "Could not obtain UD sender certificate: %@" , error ) ;
// Proceed using non - UD message sends .
2018-10-04 20:39:40 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
[ self sendMessageToService : message senderCertificate : nil success : success failure : failure ] ;
} ) ;
2018-10-03 23:04:41 +02:00
} ] ;
}
2018-10-04 20:39:40 +02:00
- ( nullable NSArray < NSString * > * ) unsentRecipientsForMessage : ( TSOutgoingMessage * ) message
thread : ( nullable TSThread * ) thread
error : ( NSError * * ) errorHandle
2016-10-14 23:00:29 +02:00
{
2018-10-04 20:39:40 +02:00
OWSAssertDebug ( message ) ;
OWSAssertDebug ( errorHandle ) ;
2018-04-23 16:30:51 +02:00
2018-10-04 20:39:40 +02:00
NSMutableSet < NSString * > * recipientIds = [ NSMutableSet new ] ;
if ( [ message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ) {
2018-12-21 20:15:19 +01:00
[ recipientIds addObject : self . tsAccountManager . localNumber ] ;
2018-10-04 20:39:40 +02:00
} else if ( thread . isGroupThread ) {
2019-10-15 00:43:58 +02:00
[ self . primaryStorage . dbReadConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
2019-10-15 01:29:41 +02:00
LKPublicChat * publicChat = [ LKDatabaseUtilities getPublicChatForThreadID : thread . uniqueId transaction : transaction ] ;
if ( publicChat ! = nil ) {
[ recipientIds addObject : publicChat . server ] ;
} else {
// TODO : Handle
}
2019-10-09 03:40:39 +02:00
} ] ;
2018-10-04 20:39:40 +02:00
} else if ( [ thread isKindOfClass : [ TSContactThread class ] ] ) {
NSString * recipientContactId = ( ( TSContactThread * ) thread ) . contactIdentifier ;
// Treat 1 : 1 sends to blocked contacts as failures .
// 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 .
OWSAssertDebug ( recipientContactId . length > 0 ) ;
if ( [ self . blockingManager isRecipientIdBlocked : recipientContactId ] ) {
OWSLogInfo ( @ "skipping 1:1 send to blocked contact: %@" , recipientContactId ) ;
2018-10-05 18:00:31 +02:00
NSError * error = OWSErrorMakeMessageSendFailedDueToBlockListError ( ) ;
2017-04-14 16:25:52 +02:00
[ error setIsRetryable : NO ] ;
2018-10-04 20:39:40 +02:00
* errorHandle = error ;
return nil ;
2016-10-14 23:00:29 +02:00
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
[ recipientIds addObject : recipientContactId ] ;
2018-10-03 14:55:14 +02:00
2018-12-21 20:15:19 +01:00
if ( [ recipientIds containsObject : self . tsAccountManager . localNumber ] ) {
2018-10-04 20:39:40 +02:00
OWSFailDebug ( @ "Message send recipients should not include self." ) ;
2018-10-03 14:55:14 +02:00
}
2018-10-04 20:39:40 +02:00
} else {
// Neither a group nor contact thread ? This should never happen .
OWSFailDebug ( @ "Unknown message type: %@" , [ message class ] ) ;
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : NO ] ;
* errorHandle = error ;
return nil ;
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
[ recipientIds minusSet : [ NSSet setWithArray : self . blockingManager . blockedPhoneNumbers ] ] ;
return recipientIds . allObjects ;
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
- ( NSArray < SignalRecipient * > * ) recipientsForRecipientIds : ( NSArray < NSString * > * ) recipientIds
{
OWSAssertDebug ( recipientIds . count > 0 ) ;
NSMutableArray < SignalRecipient * > * recipients = [ NSMutableArray new ] ;
[ self . dbConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
for ( NSString * recipientId in recipientIds ) {
SignalRecipient * recipient =
[ SignalRecipient getOrBuildUnsavedRecipientForRecipientId : recipientId transaction : transaction ] ;
[ recipients addObject : recipient ] ;
}
} ] ;
return [ recipients copy ] ;
2016-10-14 23:00:29 +02:00
}
2018-10-04 20:39:40 +02:00
- ( AnyPromise * ) sendPromiseForRecipients : ( NSArray < SignalRecipient * > * ) recipients
message : ( TSOutgoingMessage * ) message
thread : ( nullable TSThread * ) thread
senderCertificate : ( nullable SMKSenderCertificate * ) senderCertificate
sendErrors : ( NSMutableArray < NSError * > * ) sendErrors
2016-10-14 23:00:29 +02:00
{
2018-10-04 20:39:40 +02:00
OWSAssertDebug ( recipients . count > 0 ) ;
OWSAssertDebug ( message ) ;
OWSAssertDebug ( sendErrors ) ;
2018-04-24 22:11:10 +02:00
2018-07-20 21:22:51 +02:00
NSMutableArray < AnyPromise * > * sendPromises = [ NSMutableArray array ] ;
2016-10-14 23:00:29 +02:00
2018-10-04 20:39:40 +02:00
for ( SignalRecipient * recipient in recipients ) {
// Use chained promises to make the code more readable .
2018-07-20 21:22:51 +02:00
AnyPromise * sendPromise = [ AnyPromise promiseWithResolverBlock : ^ ( PMKResolver resolve ) {
2018-11-07 20:16:51 +01:00
NSString * localNumber = self . tsAccountManager . localNumber ;
2018-10-30 17:06:20 +01:00
OWSUDAccess * _Nullable theirUDAccess ;
2018-11-07 20:16:51 +01:00
if ( senderCertificate ! = nil && ! [ recipient . recipientId isEqualToString : localNumber ] ) {
2018-10-30 18:25:01 +01:00
theirUDAccess = [ self . udManager udAccessForRecipientId : recipient . recipientId requireSyncAccess : YES ] ;
2018-10-26 19:23:50 +02:00
}
2018-10-04 20:39:40 +02:00
OWSMessageSend * messageSend = [ [ OWSMessageSend alloc ] initWithMessage : message
thread : thread
recipient : recipient
senderCertificate : senderCertificate
2018-10-30 17:06:20 +01:00
udAccess : theirUDAccess
2018-10-04 20:39:40 +02:00
localNumber : self . tsAccountManager . localNumber
2018-07-20 21:22:51 +02:00
success : ^ {
2018-07-23 19:25:21 +02:00
// The value doesn ' t matter , we just need any non - NSError value .
2018-07-20 21:22:51 +02:00
resolve ( @ ( 1 ) ) ;
}
failure : ^ ( NSError * error ) {
@ synchronized ( sendErrors ) {
[ sendErrors addObject : error ] ;
}
resolve ( error ) ;
} ] ;
2019-11-07 04:28:55 +01:00
[ self sendMessageToDestinationAndLinkedDevices : messageSend ] ;
2018-07-20 21:22:51 +02:00
} ] ;
[ sendPromises addObject : sendPromise ] ;
}
2016-10-14 23:00:29 +02:00
2018-07-20 21:22:51 +02:00
// We use PMKJoin ( ) , not PMKWhen ( ) , because we don ' t want the
// completion promise to execute until _all _ send promises
// have either succeeded or failed . PMKWhen ( ) executes as
// soon as any of its input promises fail .
2018-10-04 20:39:40 +02:00
return PMKJoin ( sendPromises ) ;
}
- ( void ) sendMessageToService : ( TSOutgoingMessage * ) message
senderCertificate : ( nullable SMKSenderCertificate * ) senderCertificate
2018-10-19 22:31:34 +02:00
success : ( void ( ^ ) ( void ) ) successHandlerParam
failure : ( RetryableFailureHandler ) failureHandlerParam
2018-10-04 20:39:40 +02:00
{
AssertIsOnSendingQueue ( ) ;
2018-10-19 22:31:34 +02:00
void ( ^ successHandler ) ( void ) = ^ ( ) {
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2018-10-24 16:53:23 +02:00
[ self handleMessageSentLocally : message
success : ^ {
successHandlerParam ( ) ;
}
failure : ^ ( NSError * error ) {
OWSLogError ( @ "Error sending sync message for message: %@ timestamp: %llu" ,
message . class ,
message . timestamp ) ;
failureHandlerParam ( error ) ;
} ] ;
2018-10-19 22:31:34 +02:00
} ) ;
} ;
void ( ^ failureHandler ) ( NSError * ) = ^ ( NSError * error ) {
if ( message . wasSentToAnyRecipient ) {
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2018-10-24 16:53:23 +02:00
[ self handleMessageSentLocally : message
success : ^ {
failureHandlerParam ( error ) ;
}
failure : ^ ( NSError * syncError ) {
OWSLogError ( @ "Error sending sync message for message: %@ timestamp: %llu, %@" ,
message . class ,
message . timestamp ,
syncError ) ;
// Discard the "sync message" error in favor of the
// original error .
failureHandlerParam ( error ) ;
} ] ;
2018-10-19 22:31:34 +02:00
} ) ;
2018-10-24 16:53:23 +02:00
return ;
2018-10-19 22:31:34 +02:00
}
failureHandlerParam ( error ) ;
} ;
2018-11-26 16:00:25 +01:00
TSThread * _Nullable thread = message . thread ;
2018-11-21 20:54:32 +01:00
BOOL isSyncMessage = [ message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ;
if ( ! thread && ! isSyncMessage ) {
OWSFailDebug ( @ "Missing thread for non-sync message." ) ;
// This thread has been deleted since the message was enqueued .
NSError * error = OWSErrorWithCodeDescription ( OWSErrorCodeMessageSendNoValidRecipients ,
NSLocalizedString ( @ "ERROR_DESCRIPTION_NO_VALID_RECIPIENTS" ,
@ "Error indicating that an outgoing message had no valid recipients." ) ) ;
[ error setIsRetryable : NO ] ;
return failureHandler ( error ) ;
}
2018-10-04 20:39:40 +02:00
// In the "self-send" special case , we ony need to send a sync message with a delivery receipt .
if ( [ thread isKindOfClass : [ TSContactThread class ] ] &&
2018-12-21 20:15:19 +01:00
[ ( ( TSContactThread * ) thread ) . contactIdentifier isEqualToString : self . tsAccountManager . localNumber ] ) {
2018-10-04 20:39:40 +02:00
// Send to self .
OWSAssertDebug ( message . recipientIds . count = = 1 ) ;
2018-12-21 20:15:19 +01:00
// Don ' t mark self - sent messages as read ( or sent ) until the sync transcript is sent .
2016-10-14 23:00:29 +02:00
successHandler ( ) ;
2018-10-04 20:39:40 +02:00
return ;
}
2018-10-04 21:22:42 +02:00
if ( thread . isGroupThread ) {
[ self saveInfoMessageForGroupMessage : message inThread : thread ] ;
}
2018-10-04 20:39:40 +02:00
NSError * error ;
NSArray < NSString * > * _Nullable recipientIds = [ self unsentRecipientsForMessage : message thread : thread error : & error ] ;
if ( error || ! recipientIds ) {
error = SSKEnsureError (
error , OWSErrorCodeMessageSendNoValidRecipients , @ "Could not build recipients list for message." ) ;
[ error setIsRetryable : NO ] ;
return failureHandler ( error ) ;
}
// 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 .
NSMutableSet < NSString * > * obsoleteRecipientIds = [ NSMutableSet setWithArray : message . sendingRecipientIds ] ;
[ obsoleteRecipientIds minusSet : [ NSSet setWithArray : recipientIds ] ] ;
if ( obsoleteRecipientIds . count > 0 ) {
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
for ( NSString * recipientId in obsoleteRecipientIds ) {
// Mark this recipient as "skipped" .
[ message updateWithSkippedRecipient : recipientId transaction : transaction ] ;
}
} ] ;
}
if ( recipientIds . count < 1 ) {
// All recipients are already sent or can be skipped .
successHandler ( ) ;
return ;
}
NSArray < SignalRecipient * > * recipients = [ self recipientsForRecipientIds : recipientIds ] ;
BOOL isGroupSend = ( thread && thread . isGroupThread ) ;
NSMutableArray < NSError * > * sendErrors = [ NSMutableArray array ] ;
AnyPromise * sendPromise = [ self sendPromiseForRecipients : recipients
message : message
thread : thread
senderCertificate : senderCertificate
sendErrors : sendErrors ]
. then ( ^ ( id value ) {
successHandler ( ) ;
} ) ;
sendPromise . catch ( ^ ( id failure ) {
2017-04-14 17:33:13 +02:00
NSError * firstRetryableError = nil ;
NSError * firstNonRetryableError = nil ;
2018-07-20 21:22:51 +02:00
NSArray < NSError * > * sendErrorsCopy ;
@ synchronized ( sendErrors ) {
sendErrorsCopy = [ sendErrors copy ] ;
}
for ( NSError * error in sendErrorsCopy ) {
// Some errors should be ignored when sending messages
// to groups . See discussion on
// NSError ( OWSMessageSender ) category .
2018-10-03 14:55:14 +02:00
if ( isGroupSend && [ error shouldBeIgnoredForGroups ] ) {
2018-07-20 21:22:51 +02:00
continue ;
}
// 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 ) ;
return ;
}
if ( [ error isRetryable ] && ! firstRetryableError ) {
firstRetryableError = error ;
} else if ( ! [ error isRetryable ] && ! firstNonRetryableError ) {
firstNonRetryableError = error ;
2016-10-14 23:00:29 +02:00
}
}
2018-10-03 14:55:14 +02:00
// If any of the send errors are retryable , we want to retry .
2017-04-14 17:33:13 +02:00
// 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 .
2018-10-04 20:39:40 +02:00
if ( message . sentRecipientsCount = = 0 ) {
2017-04-17 22:45:22 +02:00
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
}
2018-07-20 21:22:51 +02:00
} ) ;
2018-10-04 20:39:40 +02:00
[ sendPromise retainUntilComplete ] ;
2016-10-14 23:00:29 +02:00
}
- ( void ) unregisteredRecipient : ( SignalRecipient * ) recipient
message : ( TSOutgoingMessage * ) message
thread : ( TSThread * ) thread
{
2018-07-17 16:09:01 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( 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 ] ;
}
2018-07-17 16:09:01 +02:00
if ( ! [ SignalRecipient isRegisteredRecipient : recipient . recipientId transaction : transaction ] ) {
2018-07-13 21:23:08 +02:00
return ;
}
2018-07-16 17:25:10 +02:00
2018-11-01 21:06:03 +01:00
[ SignalRecipient markRecipientAsUnregistered : recipient . recipientId transaction : transaction ] ;
2018-07-13 21:23:08 +02:00
2018-05-14 21:27:34 +02:00
[ [ TSInfoMessage userNotRegisteredMessageInThread : thread recipientId : recipient . recipientId ]
2016-10-14 23:00:29 +02:00
saveWithTransaction : transaction ] ;
2018-07-17 16:09:01 +02:00
// TODO : Should we deleteAllSessionsForContact here ?
// If so , we ' ll need to avoid doing a prekey fetch every
// time we try to send a message to an unregistered user .
2016-10-14 23:00:29 +02:00
} ] ;
}
2018-11-11 04:12:53 +01:00
- ( nullable NSArray < NSDictionary * > * ) deviceMessagesForMessageSend : ( OWSMessageSend * ) messageSend
error : ( NSError * * ) errorHandle
2016-10-14 23:00:29 +02:00
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug ( messageSend ) ;
OWSAssertDebug ( errorHandle ) ;
2017-04-07 01:11:04 +02:00
AssertIsOnSendingQueue ( ) ;
2017-02-09 19:50:32 +01:00
2018-10-03 14:55:14 +02:00
SignalRecipient * recipient = messageSend . recipient ;
2016-10-14 23:00:29 +02:00
NSArray < NSDictionary * > * deviceMessages ;
@ try {
2018-11-11 04:12:53 +01:00
deviceMessages = [ self throws_deviceMessagesForMessageSend : messageSend ] ;
2016-10-14 23:00:29 +02:00
} @ catch ( NSException * exception ) {
2019-01-04 21:06:52 +01:00
if ( [ exception . name isEqualToString : NoSessionForTransientMessageException ] ) {
// When users re - register , we don ' t want transient messages ( like typing
// indicators ) to cause users to hit the prekey fetch rate limit . So
// we silently discard these message if there is no pre - existing session
// for the recipient .
NSError * error = OWSErrorWithCodeDescription (
OWSErrorCodeNoSessionForTransientMessage , @ "No session for transient message." ) ;
[ error setIsRetryable : NO ] ;
[ error setIsFatal : YES ] ;
* errorHandle = error ;
return nil ;
} else 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 ] ;
2018-10-03 14:55:14 +02:00
* errorHandle = error ;
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 ] ) ;
2018-10-03 14:55:14 +02:00
return nil ;
2017-12-22 00:01:40 +01:00
}
2017-06-08 05:21:25 +02:00
if ( ! [ newKeyBundle isKindOfClass : [ PreKeyBundle class ] ] ) {
2017-07-27 18:29:05 +02:00
OWSProdFail ( [ OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle ] ) ;
2018-10-03 14:55:14 +02:00
return nil ;
2017-06-08 05:21:25 +02:00
}
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 ] ) ;
2018-10-03 14:55:14 +02:00
return nil ;
2017-06-08 05:21:25 +02:00
}
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 ] ) ;
2018-10-03 14:55:14 +02:00
return nil ;
2017-06-08 05:21:25 +02:00
}
2018-10-30 16:21:26 +01:00
NSData * newIdentityKey = [ newIdentityKeyWithVersion throws_removeKeyType ] ;
2018-10-03 23:04:41 +02:00
[ self . identityManager saveRemoteIdentity : newIdentityKey recipientId : recipient . recipientId ] ;
2017-06-08 05:21:25 +02:00
2018-10-03 14:55:14 +02:00
return nil ;
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 ] ;
2018-10-03 14:55:14 +02:00
* errorHandle = error ;
return nil ;
2016-11-10 15:59:07 +01:00
}
2018-10-03 14:55:14 +02:00
OWSLogWarn ( @ "Could not build device messages: %@" , exception ) ;
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : YES ] ;
* errorHandle = error ;
return nil ;
2016-10-14 23:00:29 +02:00
}
2018-10-03 14:55:14 +02:00
return deviceMessages ;
}
2019-10-04 03:21:20 +02:00
- ( OWSMessageSend * ) getMultiDeviceFriendRequestMessageForHexEncodedPublicKey : ( NSString * ) hexEncodedPublicKey
2019-10-03 08:46:08 +02:00
{
2019-10-04 06:52:59 +02:00
__block TSContactThread * thread ;
[ OWSPrimaryStorage . sharedManager . dbReadWriteConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
thread = [ TSContactThread fetchObjectWithUniqueID : [ TSContactThread threadIdFromContactId : hexEncodedPublicKey ] transaction : transaction ] ;
if ( thread = = nil ) {
thread = [ [ TSContactThread alloc ] initWithContactId : hexEncodedPublicKey ] ;
}
thread . isForceHidden = YES ;
[ thread saveWithTransaction : transaction ] ;
} ] ;
LKFriendRequestMessage * message = [ [ LKFriendRequestMessage alloc ] initOutgoingMessageWithTimestamp : NSDate . ows_millisecondTimeStamp inThread : thread messageBody : @ "Accept this friend request to enable messages to be synced across devices" attachmentIds : [ NSMutableArray new ]
2019-10-04 03:21:20 +02:00
expiresInSeconds : 0 expireStartedAt : 0 isVoiceMessage : NO groupMetaMessage : TSGroupMetaMessageUnspecified quotedMessage : nil contactShare : nil linkPreview : nil ] ;
SignalRecipient * recipient = [ [ SignalRecipient alloc ] initWithUniqueId : hexEncodedPublicKey ] ;
NSString * userHexEncodedPublicKey = OWSIdentityManager . sharedManager . identityKeyPair . hexEncodedPublicKey ;
return [ [ OWSMessageSend alloc ] initWithMessage : message thread : thread recipient : recipient senderCertificate : nil udAccess : nil localNumber : userHexEncodedPublicKey success : ^ { } failure : ^ ( NSError * error ) { } ] ;
2019-10-03 08:46:08 +02:00
}
2019-11-07 04:28:55 +01:00
- ( void ) sendMessageToDestinationAndLinkedDevices : ( OWSMessageSend * ) messageSend
2019-09-30 04:08:55 +02:00
{
2019-10-04 03:21:20 +02:00
TSOutgoingMessage * message = messageSend . message ;
NSString * contactID = messageSend . recipient . recipientId ;
BOOL isGroupMessage = messageSend . thread . isGroupThread ;
BOOL isDeviceLinkMessage = [ message isKindOfClass : LKDeviceLinkMessage . class ] ;
2019-11-07 04:28:55 +01:00
if ( isGroupMessage || isDeviceLinkMessage ) {
2019-09-30 04:08:55 +02:00
[ self sendMessage : messageSend ] ;
} else {
2019-11-07 06:16:56 +01:00
BOOL isSilentMessage = message . isSilent || [ message isKindOfClass : LKEphemeralMessage . class ] ;
2019-10-04 03:21:20 +02:00
BOOL isFriendRequestMessage = [ message isKindOfClass : LKFriendRequestMessage . class ] ;
[ [ LKAPI getDestinationsFor : contactID ]
2019-09-30 04:08:55 +02:00
. thenOn ( OWSDispatch . sendingQueue , ^ ( NSArray < LKDestination * > * destinations ) {
2019-10-04 06:02:41 +02:00
// Get master destination
2019-11-07 01:59:11 +01:00
LKDestination * masterDestination = [ destinations filtered : ^ BOOL ( LKDestination * destination ) {
2019-09-30 04:08:55 +02:00
return [ destination . kind isEqual : @ "master" ] ;
} ] . firstObject ;
2019-10-04 06:02:41 +02:00
// Send to master destination
2019-09-30 04:08:55 +02:00
if ( masterDestination ! = nil ) {
2019-10-03 08:46:08 +02:00
TSContactThread * thread = [ TSContactThread getOrCreateThreadWithContactId : masterDestination . hexEncodedPublicKey ] ;
2019-10-04 03:21:20 +02:00
if ( thread . isContactFriend || isSilentMessage || ( isFriendRequestMessage && contactID = = masterDestination . hexEncodedPublicKey ) ) {
2019-10-03 08:46:08 +02:00
OWSMessageSend * messageSendCopy = [ messageSend copyWithDestination : masterDestination ] ;
[ self sendMessage : messageSendCopy ] ;
} else {
2019-10-04 03:21:20 +02:00
OWSMessageSend * friendRequestMessage = [ self getMultiDeviceFriendRequestMessageForHexEncodedPublicKey : masterDestination . hexEncodedPublicKey ] ;
[ self sendMessage : friendRequestMessage ] ;
2019-10-03 08:46:08 +02:00
}
2019-09-30 04:08:55 +02:00
}
2019-10-04 06:02:41 +02:00
// Get slave destinations
2019-11-07 01:59:11 +01:00
NSArray * slaveDestinations = [ destinations filtered : ^ BOOL ( LKDestination * destination ) {
2019-10-04 06:02:41 +02:00
return [ destination . kind isEqual : @ "slave" ] ;
} ] ;
// Send to slave destinations ( using a best attempt approach ( i . e . ignoring the message send result ) for now )
for ( LKDestination * slaveDestination in slaveDestinations ) {
TSContactThread * thread = [ TSContactThread getOrCreateThreadWithContactId : slaveDestination . hexEncodedPublicKey ] ;
if ( thread . isContactFriend || isSilentMessage || ( isFriendRequestMessage && contactID = = slaveDestination . hexEncodedPublicKey ) ) {
OWSMessageSend * messageSendCopy = [ messageSend copyWithDestination : slaveDestination ] ;
[ self sendMessage : messageSendCopy ] ;
} else {
OWSMessageSend * friendRequestMessage = [ self getMultiDeviceFriendRequestMessageForHexEncodedPublicKey : slaveDestination . hexEncodedPublicKey ] ;
[ self sendMessage : friendRequestMessage ] ;
}
}
2019-09-30 04:08:55 +02:00
} )
. catchOn ( OWSDispatch . sendingQueue , ^ ( NSError * error ) {
[ self messageSendDidFail : messageSend deviceMessages : @ { } statusCode : 0 error : error responseData : nil ] ;
} ) retainUntilComplete ] ;
}
}
- ( void ) sendMessage : ( OWSMessageSend * ) messageSend
2018-10-03 14:55:14 +02:00
{
OWSAssertDebug ( messageSend ) ;
OWSAssertDebug ( messageSend . thread || [ messageSend . message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ) ;
TSOutgoingMessage * message = messageSend . message ;
SignalRecipient * recipient = messageSend . recipient ;
OWSLogInfo ( @ "attempting to send message: %@, timestamp: %llu, recipient: %@" ,
message . class ,
message . timestamp ,
recipient . uniqueId ) ;
AssertIsOnSendingQueue ( ) ;
if ( [ TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures ] ) {
OWSProdError ( [ OWSAnalyticsEvents messageSendErrorFailedDueToPrekeyUpdateFailures ] ) ;
// 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
rotateSignedPreKeyWithSuccess : ^ {
OWSLogInfo ( @ "New prekeys registered with server." ) ;
NSError * error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError ( ) ;
[ error setIsRetryable : YES ] ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-10-03 14:55:14 +02:00
}
failure : ^ ( NSError * error ) {
OWSLogWarn ( @ "Failed to update prekeys with the server: %@" , error ) ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-10-03 14:55:14 +02:00
} ] ;
}
if ( messageSend . remainingAttempts <= 0 ) {
// We should always fail with a specific error .
OWSProdFail ( [ OWSAnalyticsEvents messageSenderErrorGenericSendFailure ] ) ;
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : YES ] ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-10-03 14:55:14 +02:00
}
2018-10-03 20:01:55 +02:00
2018-10-03 14:55:14 +02:00
// Consume an attempt .
messageSend . remainingAttempts = messageSend . remainingAttempts - 1 ;
2018-10-22 20:04:30 +02:00
// We need to disable UD for sync messages before we build the device messages ,
// since we don ' t want to build a device message for the local device in the
// non - UD auth case .
2018-10-22 18:31:28 +02:00
if ( [ message isKindOfClass : [ OWSOutgoingSyncMessage class ] ]
&& ! [ message isKindOfClass : [ OWSOutgoingSentMessageTranscript class ] ] ) {
[ messageSend disableUD ] ;
}
2018-10-03 14:55:14 +02:00
NSError * deviceMessagesError ;
2019-08-21 03:50:23 +02:00
NSArray < NSDictionary * > * _Nullable deviceMessages ;
if ( ! message . thread . isGroupThread ) {
deviceMessages = [ self deviceMessagesForMessageSend : messageSend error : & deviceMessagesError ] ;
} else {
deviceMessages = @ { } ;
}
2018-10-03 14:55:14 +02:00
if ( deviceMessagesError || ! deviceMessages ) {
OWSAssertDebug ( deviceMessagesError ) ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( deviceMessagesError ) ;
2018-10-03 14:55:14 +02:00
}
2019-09-30 04:08:55 +02:00
/ *
2018-10-03 14:55:14 +02:00
if ( messageSend . isLocalNumber ) {
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( [ 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 .
2018-10-11 16:23:19 +02:00
//
// NOTE : Sync messages sent via UD include the local device .
2017-11-07 20:52:31 +01:00
2017-11-10 18:04:24 +01:00
BOOL mayHaveLinkedDevices = [ OWSDeviceManager . sharedManager mayHaveLinkedDevices : self . dbConnection ] ;
2017-11-02 19:10:45 +01:00
2018-10-11 16:23:19 +02:00
BOOL hasDeviceMessages = NO ;
for ( NSDictionary < NSString * , id > * deviceMessage in deviceMessages ) {
NSString * _Nullable destination = deviceMessage [ @ "destination" ] ;
if ( ! destination ) {
OWSFailDebug ( @ "Sync device message missing destination: %@" , deviceMessage ) ;
continue ;
}
if ( ! [ destination isEqualToString : messageSend . localNumber ] ) {
OWSFailDebug ( @ "Sync device message has invalid destination: %@" , deviceMessage ) ;
continue ;
}
NSNumber * _Nullable destinationDeviceId = deviceMessage [ @ "destinationDeviceId" ] ;
if ( ! destinationDeviceId ) {
OWSFailDebug ( @ "Sync device message missing destination device id: %@" , deviceMessage ) ;
continue ;
}
if ( destinationDeviceId . intValue ! = OWSDevicePrimaryDeviceId ) {
hasDeviceMessages = YES ;
break ;
}
}
2017-11-02 19:10:45 +01:00
2018-08-27 18:51:32 +02:00
OWSLogInfo ( @ "mayHaveLinkedDevices: %d, hasDeviceMessages: %d" , mayHaveLinkedDevices , hasDeviceMessages ) ;
2018-03-02 05:31:41 +01:00
2017-11-10 17:04:40 +01:00
if ( ! mayHaveLinkedDevices && ! hasDeviceMessages ) {
2018-08-27 18:51:32 +02:00
OWSLogInfo ( @ "Ignoring sync message without secondary devices: %@" , [ message class ] ) ;
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( [ message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ) ;
2017-09-15 16:29:46 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2018-10-03 14:55:14 +02:00
// This emulates the completion logic of an actual successful send ( see below ) .
2018-04-27 22:37:09 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2018-10-03 14:55:14 +02:00
[ message updateWithSkippedRecipient : messageSend . localNumber transaction : transaction ] ;
2018-04-27 22:37:09 +02:00
} ] ;
2018-10-04 20:39:40 +02:00
messageSend . success ( ) ;
2017-09-15 16:29:46 +02:00
} ) ;
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-08-27 18:51:32 +02:00
OWSLogWarn ( @ "account has secondary devices, but sync message has no device messages" ) ;
2018-07-02 18:22:29 +02:00
} else if ( ! mayHaveLinkedDevices && hasDeviceMessages ) {
2018-08-27 18:51:32 +02:00
OWSFailDebug ( @ "sync message has device messages for unknown secondary devices." ) ;
2017-09-15 16:29:46 +02:00
}
2018-07-17 16:09:01 +02:00
} else {
2018-10-31 19:24:13 +01:00
// This can happen for users who have unregistered .
// We still want to try sending to them in case they have re - registered .
2018-10-31 19:36:46 +01:00
if ( deviceMessages . count < 1 ) {
OWSLogWarn ( @ "Message send attempt with no device messages." ) ;
}
2017-09-15 16:29:46 +02:00
}
2019-09-30 04:08:55 +02:00
* /
2017-09-15 16:29:46 +02:00
2018-11-29 20:30:16 +01:00
for ( NSDictionary * deviceMessage in deviceMessages ) {
NSNumber * _Nullable messageType = deviceMessage [ @ "type" ] ;
OWSAssertDebug ( messageType ) ;
2018-11-29 21:00:37 +01:00
BOOL hasValidMessageType ;
2018-11-29 20:30:16 +01:00
if ( messageSend . isUDSend ) {
2018-11-29 21:00:37 +01:00
hasValidMessageType = [ messageType isEqualToNumber : @ ( TSUnidentifiedSenderMessageType ) ] ;
2018-11-29 20:30:16 +01:00
} else {
2019-05-10 05:38:00 +02:00
// Loki : TSFriendRequestMessageType represents a Loki friend request
NSArray * validMessageTypes = @ [ @ ( TSEncryptedWhisperMessageType ) , @ ( TSPreKeyWhisperMessageType ) , @ ( TSFriendRequestMessageType ) ] ;
2019-05-07 05:59:34 +02:00
hasValidMessageType = [ validMessageTypes containsObject : messageType ] ;
2019-05-16 07:32:43 +02:00
/ * Loki : Original code
2019-05-24 03:24:27 +02:00
* === === = =
hasValidMessageType = ( [ messageType isEqualToNumber : @ ( TSEncryptedWhisperMessageType ) ] || [ messageType isEqualToNumber : @ ( TSPreKeyWhisperMessageType ) ] ) ;
* === === = =
2019-05-07 05:59:34 +02:00
* /
2018-11-29 20:30:16 +01:00
}
2018-11-29 21:00:37 +01:00
if ( ! hasValidMessageType ) {
OWSFailDebug ( @ "Invalid message type: %@" , messageType ) ;
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : NO ] ;
return messageSend . failure ( error ) ;
}
2018-11-29 20:30:16 +01:00
}
2019-08-21 03:50:23 +02:00
if ( deviceMessages . count = = 0 && ! message . thread . isGroupThread ) {
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
2018-07-17 16:37:26 +02:00
// default device . For example , if they have unregistered
2018-07-13 20:42:11 +02:00
// 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-08-27 18:51:32 +02:00
OWSLogWarn ( @ "Sending a message with no device messages." ) ;
2019-11-07 06:16:56 +01:00
2019-05-06 06:22:34 +02:00
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : NO ] ;
return messageSend . failure ( error ) ;
2018-03-02 05:31:41 +01:00
}
2019-08-19 05:41:23 +02:00
void ( ^ failedMessageSend ) ( NSError * error ) = ^ ( NSError * error ) {
2019-05-24 03:24:27 +02:00
// Handle the error
NSUInteger statusCode = 0 ;
NSData * _Nullable responseData = nil ;
if ( [ error . domain isEqualToString : TSNetworkManagerErrorDomain ] ) {
statusCode = error . code ;
NSError * _Nullable underlyingError = error . userInfo [ NSUnderlyingErrorKey ] ;
if ( underlyingError ) {
responseData = underlyingError . userInfo [ AFNetworkingOperationFailingURLResponseDataErrorKey ] ;
} else {
OWSFailDebug ( @ "Missing underlying error: %@." , error ) ;
}
} else {
2019-08-22 02:12:48 +02:00
// TODO : Re - enable ?
// OWSFailDebug ( @ "Unexpected error: %@." , error ) ;
2019-05-24 03:24:27 +02:00
}
[ self messageSendDidFail : messageSend deviceMessages : deviceMessages statusCode : statusCode error : error responseData : responseData ] ;
} ;
2019-08-19 05:41:23 +02:00
2019-10-15 01:29:41 +02:00
__block LKPublicChat * publicChat ;
2019-10-09 02:18:11 +02:00
[ OWSPrimaryStorage . sharedManager . dbReadConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
2019-10-15 01:29:41 +02:00
publicChat = [ LKDatabaseUtilities getPublicChatForThreadID : message . uniqueThreadId transaction : transaction ] ;
2019-10-09 02:18:11 +02:00
} ] ;
2019-10-15 01:29:41 +02:00
if ( publicChat ! = nil ) {
2019-08-19 05:41:23 +02:00
NSString * userHexEncodedPublicKey = OWSIdentityManager . sharedManager . identityKeyPair . hexEncodedPublicKey ;
2019-08-22 02:12:48 +02:00
NSString * displayName = SSKEnvironment . shared . profileManager . localProfileName ;
2019-08-20 07:34:59 +02:00
if ( displayName = = nil ) { displayName = @ "Anonymous" ; }
2019-09-11 06:07:51 +02:00
TSQuotedMessage * quote = message . quotedMessage ;
2019-10-02 05:34:34 +02:00
uint64_t quoteID = quote . timestamp ;
NSString * quoteeHexEncodedPublicKey = quote . authorId ;
2019-10-02 07:44:44 +02:00
__block uint64_t quotedMessageServerID = 0 ;
if ( quoteID ! = 0 ) {
[ self . dbConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
quotedMessageServerID = [ LKDatabaseUtilities getServerIDForQuoteWithID : quoteID quoteeHexEncodedPublicKey : quoteeHexEncodedPublicKey threadID : messageSend . thread . uniqueId transaction : transaction ] ;
} ] ;
}
2019-10-21 01:12:39 +02:00
NSString * body = ( message . body ! = nil && message . body . length > 0 ) ? message . body : [ NSString stringWithFormat : @ "%@" , @ ( message . timestamp ) ] ; // Workaround for the fact that the back - end doesn ' t accept messages without a body
LKGroupMessage * groupMessage = [ [ LKGroupMessage alloc ] initWithHexEncodedPublicKey : userHexEncodedPublicKey displayName : displayName body : body type : LKPublicChatAPI . publicChatMessageType
2019-10-02 05:50:44 +02:00
timestamp : message . timestamp quotedMessageTimestamp : quoteID quoteeHexEncodedPublicKey : quoteeHexEncodedPublicKey quotedMessageBody : quote . body quotedMessageServerID : quotedMessageServerID signatureData : nil signatureVersion : 0 ] ;
2019-10-21 02:43:46 +02:00
OWSLinkPreview * linkPreview = message . linkPreview ;
if ( linkPreview ! = nil ) {
TSAttachmentStream * attachment = [ TSAttachmentStream fetchObjectWithUniqueID : linkPreview . imageAttachmentId ] ;
if ( attachment ! = nil ) {
[ groupMessage addAttachmentWithKind : @ "preview" server : publicChat . server serverID : attachment . serverId contentType : attachment . contentType size : attachment . byteCount fileName : attachment . sourceFilename flags : 0 width : @ ( attachment . imageSize . width ) . unsignedIntegerValue height : @ ( attachment . imageSize . height ) . unsignedIntegerValue caption : attachment . caption url : attachment . downloadURL linkPreviewURL : linkPreview . urlString linkPreviewTitle : linkPreview . title ] ;
}
}
2019-10-18 02:32:36 +02:00
for ( NSString * attachmentID in message . attachmentIds ) {
TSAttachmentStream * attachment = [ TSAttachmentStream fetchObjectWithUniqueID : attachmentID ] ;
if ( attachment = = nil ) { continue ; }
2019-10-23 04:35:15 +02:00
NSUInteger width = attachment . shouldHaveImageSize ? @ ( attachment . imageSize . width ) . unsignedIntegerValue : 0 ;
NSUInteger height = attachment . shouldHaveImageSize ? @ ( attachment . imageSize . height ) . unsignedIntegerValue : 0 ;
[ groupMessage addAttachmentWithKind : @ "attachment" server : publicChat . server serverID : attachment . serverId contentType : attachment . contentType size : attachment . byteCount fileName : attachment . sourceFilename flags : 0 width : width height : height caption : attachment . caption url : attachment . downloadURL linkPreviewURL : nil linkPreviewTitle : nil ] ;
2019-10-18 02:32:36 +02:00
}
2019-10-15 01:29:41 +02:00
[ [ LKPublicChatAPI sendMessage : groupMessage toGroup : publicChat . channel onServer : publicChat . server ]
2019-08-28 02:04:15 +02:00
. thenOn ( OWSDispatch . sendingQueue , ^ ( LKGroupMessage * groupMessage ) {
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2019-10-03 07:19:39 +02:00
[ message saveGroupChatServerID : groupMessage . serverID in : transaction ] ;
2019-08-28 02:04:15 +02:00
[ OWSPrimaryStorage . sharedManager setIDForMessageWithServerID : groupMessage . serverID to : message . uniqueId in : transaction ] ;
} ] ;
2019-08-19 05:41:23 +02:00
[ self messageSendDidSucceed : messageSend deviceMessages : deviceMessages wasSentByUD : false wasSentByWebsocket : false ] ;
} )
2019-08-20 07:34:59 +02:00
. catchOn ( OWSDispatch . sendingQueue , ^ ( NSError * error ) { // The snode is unreachable
2019-08-19 05:41:23 +02:00
failedMessageSend ( error ) ;
} ) retainUntilComplete ] ;
} else {
NSDictionary * signalMessageInfo = deviceMessages . firstObject ;
SSKProtoEnvelopeType type = ( ( NSNumber * ) signalMessageInfo [ @ "type" ] ) . integerValue ;
uint64_t timestamp = message . timestamp ;
NSString * senderID = OWSIdentityManager . sharedManager . identityKeyPair . hexEncodedPublicKey ;
uint32_t senderDeviceID = OWSDevicePrimaryDeviceId ;
NSString * content = signalMessageInfo [ @ "content" ] ;
NSString * recipientID = signalMessageInfo [ @ "destination" ] ;
uint64_t ttl = ( ( NSNumber * ) signalMessageInfo [ @ "ttl" ] ) . unsignedIntegerValue ;
BOOL isPing = ( ( NSNumber * ) signalMessageInfo [ @ "isPing" ] ) . boolValue ;
LKSignalMessage * signalMessage = [ [ LKSignalMessage alloc ] initWithType : type timestamp : timestamp senderID : senderID senderDeviceID : senderDeviceID content : content recipientID : recipientID ttl : ttl isPing : isPing ] ;
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
// Update the PoW calculation status
[ message saveIsCalculatingProofOfWork : YES withTransaction : transaction ] ;
// Update the message and thread if needed
if ( signalMessage . type = = TSFriendRequestMessageType ) {
[ message . thread saveFriendRequestStatus : LKThreadFriendRequestStatusRequestSending withTransaction : transaction ] ;
[ message saveFriendRequestStatus : LKMessageFriendRequestStatusSendingOrFailed withTransaction : transaction ] ;
}
} ] ;
// Convenience
void ( ^ onP2PSuccess ) ( ) = ^ ( ) { message . isP2P = YES ; } ;
void ( ^ handleError ) ( NSError * error ) = ^ ( NSError * error ) {
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
// Update the message and thread if needed
if ( signalMessage . type = = TSFriendRequestMessageType ) {
[ message . thread saveFriendRequestStatus : LKThreadFriendRequestStatusNone withTransaction : transaction ] ;
[ message saveFriendRequestStatus : LKMessageFriendRequestStatusSendingOrFailed withTransaction : transaction ] ;
}
// Update the PoW calculation status
[ message saveIsCalculatingProofOfWork : NO withTransaction : transaction ] ;
} ] ;
// Handle the error
failedMessageSend ( error ) ;
} ;
// Send the message using the Loki API
[ [ LKAPI sendSignalMessage : signalMessage onP2PSuccess : onP2PSuccess ]
. thenOn ( OWSDispatch . sendingQueue , ^ ( id result ) {
2019-05-22 04:24:20 +02:00
NSSet < AnyPromise * > * promises = ( NSSet < AnyPromise * > * ) result ;
2019-05-22 04:29:14 +02:00
__block BOOL isSuccess = NO ;
2019-05-22 04:24:20 +02:00
NSUInteger promiseCount = promises . count ;
2019-05-22 04:29:14 +02:00
__block NSUInteger errorCount = 0 ;
2019-05-22 04:24:20 +02:00
for ( AnyPromise * promise in promises ) {
[ promise
2019-08-20 07:34:59 +02:00
. thenOn ( OWSDispatch . sendingQueue , ^ ( id result ) {
2019-05-22 04:24:20 +02:00
if ( isSuccess ) { return ; } // Succeed as soon as the first promise succeeds
2019-09-04 07:55:17 +02:00
[ LKAnalytics . shared track : @ "Sent Message Using Swarm API" ] ;
2019-05-22 04:24:20 +02:00
isSuccess = YES ;
2019-06-12 04:36:27 +02:00
if ( signalMessage . type = = TSFriendRequestMessageType ) {
2019-05-22 04:24:20 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2019-05-24 08:23:27 +02:00
// Update the thread
2019-05-24 08:25:25 +02:00
[ message . thread saveFriendRequestStatus : LKThreadFriendRequestStatusRequestSent withTransaction : transaction ] ;
2019-05-24 08:23:27 +02:00
[ message . thread removeOldOutgoingFriendRequestMessagesIfNeededWithTransaction : transaction ] ;
// Update the message
2019-05-24 08:25:25 +02:00
[ message saveFriendRequestStatus : LKMessageFriendRequestStatusPending withTransaction : transaction ] ;
2019-05-22 04:24:20 +02:00
NSTimeInterval expirationInterval = 72 * kHourInterval ;
NSDate * expirationDate = [ [ NSDate new ] dateByAddingTimeInterval : expirationInterval ] ;
[ message saveFriendRequestExpiresAt : [ NSDate ows_millisecondsSince1970ForDate : expirationDate ] withTransaction : transaction ] ;
} ] ;
}
// Invoke the completion handler
[ self messageSendDidSucceed : messageSend deviceMessages : deviceMessages wasSentByUD : false wasSentByWebsocket : false ] ;
} )
2019-08-20 07:34:59 +02:00
. catchOn ( OWSDispatch . sendingQueue , ^ ( NSError * error ) {
2019-05-22 04:24:20 +02:00
errorCount + = 1 ;
if ( errorCount ! = promiseCount ) { return ; } // Only error out if all promises failed
2019-09-04 07:55:17 +02:00
[ LKAnalytics . shared track : @ "Failed to Send Message Using Swarm API" ] ;
2019-05-24 03:24:27 +02:00
handleError ( error ) ;
2019-05-22 04:24:20 +02:00
} ) retainUntilComplete ] ;
2019-05-08 04:37:38 +02:00
}
2019-05-24 03:24:27 +02:00
} )
2019-08-20 07:34:59 +02:00
. catchOn ( OWSDispatch . sendingQueue , ^ ( NSError * error ) { // The snode is unreachable
2019-05-24 03:24:27 +02:00
handleError ( error ) ;
2019-05-08 04:37:38 +02:00
} ) retainUntilComplete ] ;
2019-08-19 05:41:23 +02:00
}
2018-05-18 20:35:00 +02:00
}
2018-03-02 05:31:41 +01:00
2018-10-03 14:55:14 +02:00
- ( void ) messageSendDidSucceed : ( OWSMessageSend * ) messageSend
2018-05-18 20:35:00 +02:00
deviceMessages : ( NSArray < NSDictionary * > * ) deviceMessages
2018-11-14 14:30:25 +01:00
wasSentByUD : ( BOOL ) wasSentByUD
wasSentByWebsocket : ( BOOL ) wasSentByWebsocket
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug ( messageSend ) ;
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( deviceMessages ) ;
2018-05-18 20:35:00 +02:00
2018-10-03 14:55:14 +02:00
SignalRecipient * recipient = messageSend . recipient ;
2018-10-31 16:42:41 +01:00
OWSLogInfo ( @ "successfully sent message: %@ timestamp: %llu, wasSentByUD: %d" ,
messageSend . message . class , messageSend . message . timestamp , wasSentByUD ) ;
2018-05-18 20:35:00 +02:00
2018-10-03 14:55:14 +02:00
if ( messageSend . isLocalNumber && deviceMessages . count = = 0 ) {
2018-08-27 18:51:32 +02:00
OWSLogInfo ( @ "Sent a message with no device messages; clearing 'mayHaveLinkedDevices'." ) ;
2018-05-18 20:35:00 +02:00
// 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 . ) .
2018-11-13 22:35:06 +01:00
[ OWSDeviceManager . sharedManager clearMayHaveLinkedDevices ] ;
2018-05-18 20:35:00 +02:00
}
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 ) {
2018-10-10 16:27:29 +02:00
[ messageSend . message updateWithSentRecipient : messageSend . recipient . uniqueId
wasSentByUD : wasSentByUD
transaction : transaction ] ;
2018-07-13 21:23:08 +02:00
2018-07-16 17:25:10 +02:00
// If we ' ve just delivered a message to a user , we know they
// have a valid Signal account .
2018-07-17 16:09:01 +02:00
[ SignalRecipient markRecipientAsRegisteredAndGet : recipient . recipientId transaction : transaction ] ;
2018-05-18 20:35:00 +02:00
} ] ;
2018-05-18 19:55:22 +02:00
2018-10-04 20:39:40 +02:00
messageSend . success ( ) ;
2018-05-18 20:35:00 +02:00
} ) ;
}
2018-05-18 19:55:22 +02:00
2018-10-03 14:55:14 +02:00
- ( void ) messageSendDidFail : ( OWSMessageSend * ) messageSend
2018-05-18 20:35:00 +02:00
deviceMessages : ( NSArray < NSDictionary * > * ) deviceMessages
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
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug ( messageSend ) ;
OWSAssertDebug ( messageSend . thread || [ messageSend . message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ) ;
2018-09-06 19:01:24 +02:00
OWSAssertDebug ( deviceMessages ) ;
OWSAssertDebug ( responseError ) ;
2016-10-24 19:54:00 +02:00
2018-10-03 14:55:14 +02:00
TSOutgoingMessage * message = messageSend . message ;
SignalRecipient * recipient = messageSend . recipient ;
2018-10-23 16:54:33 +02:00
OWSLogInfo ( @ "failed to send message: %@, timestamp: %llu, to recipient: %@" ,
message . class ,
message . timestamp ,
recipient . uniqueId ) ;
2018-05-18 19:55:22 +02:00
2018-05-18 20:35:00 +02:00
void ( ^ retrySend ) ( void ) = ^ void ( ) {
2018-10-03 14:55:14 +02:00
if ( messageSend . remainingAttempts <= 0 ) {
2018-10-04 20:39:40 +02:00
return messageSend . failure ( 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 ] , ^ {
2018-08-27 18:51:32 +02:00
OWSLogDebug ( @ "Retrying: %@" , message . debugDescription ) ;
2019-11-07 04:28:55 +01:00
[ self sendMessage : messageSend ] ;
2018-05-18 20:35:00 +02:00
} ) ;
} ;
2016-10-14 23:00:29 +02:00
2018-07-17 16:09:01 +02:00
void ( ^ handle404 ) ( void ) = ^ {
2018-08-27 18:51:32 +02:00
OWSLogWarn ( @ "Unregistered recipient: %@" , recipient . uniqueId ) ;
2018-07-17 16:09:01 +02:00
dispatch_async ( [ OWSDispatch sendingQueue ] , ^ {
2019-01-08 21:10:32 +01:00
if ( ! [ messageSend . message isKindOfClass : [ OWSOutgoingSyncMessage class ] ] ) {
TSThread * _Nullable thread = messageSend . thread ;
OWSAssertDebug ( thread ) ;
[ self unregisteredRecipient : recipient message : message thread : thread ] ;
}
2018-07-17 16:09:01 +02:00
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 ] ;
2018-10-04 20:39:40 +02:00
messageSend . failure ( error ) ;
2018-07-17 16:09:01 +02:00
} ) ;
} ;
2018-05-18 20:35:00 +02:00
switch ( statusCode ) {
2019-05-24 08:23:27 +02:00
case 0 : { // Loki
NSError * error = OWSErrorMakeFailedToSendOutgoingMessageError ( ) ;
[ error setIsRetryable : NO ] ;
return messageSend . failure ( error ) ;
}
2018-05-18 20:35:00 +02:00
case 401 : {
2018-08-27 18:55:37 +02:00
OWSLogWarn ( @ "Unable to send due to invalid credentials. Did the user's client get de-authed by "
@ "registering elsewhere?" ) ;
2018-05-18 20:35:00 +02:00
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 ] ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-05-18 20:35:00 +02:00
}
case 404 : {
2018-07-17 16:09:01 +02:00
handle404 ( ) ;
return ;
2018-05-18 20:35:00 +02:00
}
case 409 : {
// Mismatched devices
2018-08-27 18:51:32 +02:00
OWSLogWarn ( @ "Mismatched devices for recipient: %@ (%zd)" , recipient . uniqueId , deviceMessages . count ) ;
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 ] ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-05-18 20:35:00 +02:00
}
2018-07-17 16:09:01 +02:00
NSNumber * _Nullable errorCode = responseJson [ @ "code" ] ;
if ( [ @ ( 404 ) isEqual : errorCode ] ) {
// Some 404 s are returned as 409.
handle404 ( ) ;
return ;
}
2018-05-30 16:03:14 +02:00
[ self handleMismatchedDevicesWithResponseJson : responseJson recipient : recipient completion : retrySend ] ;
2018-10-23 16:31:29 +02:00
if ( messageSend . isLocalNumber ) {
// Don ' t use websocket ; it may have obsolete cached state .
[ messageSend setHasWebsocketSendFailed : YES ] ;
}
2018-05-18 20:35:00 +02:00
break ;
}
case 410 : {
// Stale devices
2018-08-27 18:51:32 +02:00
OWSLogWarn ( @ "Stale devices for recipient: %@" , recipient . uniqueId ) ;
2018-05-18 20:35:00 +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-08-27 18:00:28 +02:00
OWSLogWarn ( @ "Stale devices but server didn't specify devices in response." ) ;
2018-05-18 20:35:00 +02:00
NSError * error = OWSErrorMakeUnableToProcessServerResponseError ( ) ;
[ error setIsRetryable : YES ] ;
2018-10-04 20:39:40 +02:00
return messageSend . failure ( error ) ;
2018-05-18 20:35:00 +02:00
}
2018-05-30 16:03:14 +02:00
[ self handleStaleDevicesWithResponseJson : responseJson recipientId : recipient . uniqueId completion : retrySend ] ;
2018-10-23 16:31:29 +02:00
if ( messageSend . isLocalNumber ) {
// Don ' t use websocket ; it may have obsolete cached state .
[ messageSend setHasWebsocketSendFailed : YES ] ;
}
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-09-06 19:01:24 +02:00
OWSAssertDebug ( responseJson ) ;
OWSAssertDebug ( recipient ) ;
OWSAssertDebug ( completionHandler ) ;
2018-05-30 16:03:14 +02:00
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 ) {
2018-12-21 20:15:19 +01:00
NSString * localNumber = self . tsAccountManager . localNumber ;
2017-11-10 17:04:40 +01:00
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-07-25 17:42:50 +02:00
readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * 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-10-18 17:20:47 +02:00
[ recipient updateRegisteredRecipientWithDevicesToAdd : missingDevices
devicesToRemove : extraDevices
transaction : transaction ] ;
2018-10-18 16:06:03 +02:00
2018-01-30 21:05:04 +01:00
if ( extraDevices && extraDevices . count > 0 ) {
2018-10-18 17:20:47 +02:00
OWSLogInfo ( @ "Deleting sessions for extra devices: %@" , extraDevices ) ;
2018-01-30 21:05:04 +01:00
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 ] ;
}
2017-04-07 01:11:04 +02:00
}
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
2018-12-21 20:15:19 +01:00
success : ( void ( ^ ) ( void ) ) successParam
2018-10-24 16:53:23 +02:00
failure : ( RetryableFailureHandler ) failure
2016-10-14 23:00:29 +02:00
{
2018-12-21 20:15:19 +01:00
dispatch_block _t success = ^ {
TSThread * _Nullable thread = message . thread ;
if ( thread && [ thread isKindOfClass : [ TSContactThread class ] ] &&
[ thread . contactIdentifier isEqualToString : self . tsAccountManager . localNumber ] ) {
OWSAssertDebug ( message . recipientIds . count = = 1 ) ;
// Don ' t mark self - sent messages as read ( or sent ) until the sync transcript is sent .
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
for ( NSString * recipientId in message . sendingRecipientIds ) {
[ message updateWithReadRecipientId : recipientId
readTimestamp : message . timestamp
transaction : transaction ] ;
}
} ] ;
}
successParam ( ) ;
} ;
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
} ] ;
2018-10-24 16:53:23 +02:00
if ( ! message . shouldSyncTranscript ) {
return success ( ) ;
}
2019-02-21 21:21:00 +01:00
BOOL shouldSendTranscript = ( AreRecipientUpdatesEnabled ( ) || ! message . hasSyncedTranscript ) ;
2019-02-21 17:25:45 +01:00
if ( ! shouldSendTranscript ) {
return success ( ) ;
}
2019-02-21 21:21:00 +01:00
BOOL isRecipientUpdate = message . hasSyncedTranscript ;
2019-02-21 17:25:45 +01:00
[ self
sendSyncTranscriptForMessage : message
isRecipientUpdate : isRecipientUpdate
success : ^ {
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
[ message updateWithHasSyncedTranscript : YES transaction : transaction ] ;
} ] ;
success ( ) ;
}
failure : failure ] ;
2016-10-14 23:00:29 +02:00
}
- ( void ) sendSyncTranscriptForMessage : ( TSOutgoingMessage * ) message
2019-02-21 17:25:45 +01:00
isRecipientUpdate : ( BOOL ) isRecipientUpdate
2018-10-24 16:53:23 +02:00
success : ( void ( ^ ) ( void ) ) success
failure : ( RetryableFailureHandler ) failure
2016-10-08 01:17:38 +02:00
{
2016-10-14 23:00:29 +02:00
OWSOutgoingSentMessageTranscript * sentMessageTranscript =
2019-02-21 17:25:45 +01:00
[ [ OWSOutgoingSentMessageTranscript alloc ] initWithOutgoingMessage : message isRecipientUpdate : isRecipientUpdate ] ;
2016-10-14 23:00:29 +02:00
2018-10-03 14:55:14 +02:00
NSString * recipientId = self . tsAccountManager . localNumber ;
2018-09-17 15:27:58 +02:00
__block SignalRecipient * recipient ;
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2018-09-17 15:51:44 +02:00
recipient = [ SignalRecipient markRecipientAsRegisteredAndGet : recipientId transaction : transaction ] ;
2018-09-17 15:27:58 +02:00
} ] ;
2018-10-03 14:55:14 +02:00
OWSMessageSend * messageSend = [ [ OWSMessageSend alloc ] initWithMessage : sentMessageTranscript
2018-10-04 20:39:40 +02:00
thread : message . thread
recipient : recipient
2018-11-07 20:16:51 +01:00
senderCertificate : nil
udAccess : nil
2018-10-04 20:39:40 +02:00
localNumber : self . tsAccountManager . localNumber
2016-10-08 01:17:38 +02:00
success : ^ {
2018-08-27 18:00:28 +02:00
OWSLogInfo ( @ "Successfully sent sync transcript." ) ;
2018-10-24 16:53:23 +02:00
success ( ) ;
2016-10-08 01:17:38 +02:00
}
2017-04-14 16:25:52 +02:00
failure : ^ ( NSError * error ) {
2018-08-27 18:00:28 +02:00
OWSLogInfo ( @ "Failed to send sync transcript: %@ (isRetryable: %d)" , error , [ error isRetryable ] ) ;
2018-10-24 16:53:23 +02:00
failure ( error ) ;
2016-10-08 01:17:38 +02:00
} ] ;
2019-11-07 04:28:55 +01:00
[ self sendMessage : messageSend ] ;
2016-10-08 01:17:38 +02:00
}
2018-11-11 04:12:53 +01:00
- ( NSArray < NSDictionary * > * ) throws_deviceMessagesForMessageSend : ( OWSMessageSend * ) messageSend
2016-10-14 23:00:29 +02:00
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug ( messageSend . message ) ;
OWSAssertDebug ( messageSend . recipient ) ;
SignalRecipient * recipient = messageSend . recipient ;
2017-09-27 23:13:29 +02:00
2016-10-14 23:00:29 +02:00
NSMutableArray * messagesArray = [ NSMutableArray arrayWithCapacity : recipient . devices . count ] ;
2017-08-01 23:01:07 +02:00
2019-09-30 04:08:55 +02:00
NSData * _Nullable plainText = [ messageSend . message buildPlainTextData : recipient ] ;
2018-08-01 23:13:01 +02:00
if ( ! plainText ) {
OWSRaiseException ( InvalidMessageException , @ "Failed to build message proto" ) ;
}
2018-10-03 14:55:14 +02:00
OWSLogDebug (
@ "built message: %@ plainTextData.length: %lu" , [ messageSend . message class ] , ( unsigned long ) plainText . length ) ;
2016-10-14 23:00:29 +02:00
2018-10-22 19:57:21 +02:00
OWSLogVerbose ( @ "building device messages for: %@ %@ (isLocalNumber: %d, isUDSend: %d)" ,
recipient . recipientId ,
recipient . devices ,
messageSend . isLocalNumber ,
messageSend . isUDSend ) ;
2018-10-09 15:11:06 +02:00
2019-11-07 06:16:56 +01:00
NSString * userHexEncodedPublicKey = OWSIdentityManager . sharedManager . identityKeyPair . hexEncodedPublicKey ;
__block NSMutableSet < NSString * > * recipientIDs = [ NSSet setWithObject : recipient . uniqueId ] ;
if ( [ messageSend . message isKindOfClass : OWSOutgoingSyncMessage . class ] ) {
[ OWSPrimaryStorage . sharedManager . dbReadWriteConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
NSString * masterHexEncodedPublicKey = [ LKDatabaseUtilities getMasterHexEncodedPublicKeyFor : userHexEncodedPublicKey in : transaction ] ? : userHexEncodedPublicKey ;
NSSet < NSString * > * linkedDeviceHexEncodedPublicKeys = [ LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor : userHexEncodedPublicKey in : transaction ] ;
recipientIDs = [ recipientIDs setByAddingObjectsFromSet : linkedDeviceHexEncodedPublicKeys ] . mutableCopy ;
} ] ;
}
2018-10-09 15:11:06 +02:00
2018-11-11 04:12:53 +01:00
if ( messageSend . isLocalNumber ) {
2019-11-07 06:16:56 +01:00
[ recipientIDs removeObject : userHexEncodedPublicKey ] ;
2018-10-09 15:11:06 +02:00
}
2019-11-07 06:16:56 +01:00
for ( NSString * recipientID in recipientIDs ) {
2016-10-14 23:00:29 +02:00
@ try {
2018-10-18 19:50:48 +02:00
// This may involve blocking network requests , so we do it _before _
// we open a transaction .
2019-05-09 06:00:40 +02:00
2019-09-25 04:22:34 +02:00
// Loki : Both for friend request messages and device link messages we don ' t require a session
2019-09-09 06:50:30 +02:00
BOOL isFriendRequest = [ messageSend . message isKindOfClass : LKFriendRequestMessage . class ] ;
2019-09-25 04:22:34 +02:00
BOOL isDeviceLinkMessage = [ messageSend . message isKindOfClass : LKDeviceLinkMessage . class ] ;
if ( ! isFriendRequest && ! isDeviceLinkMessage ) {
2019-11-07 06:16:56 +01:00
[ self throws_ensureRecipientHasSessionForMessageSend : messageSend deviceId : @ ( OWSDevicePrimaryDeviceId ) ] ;
2019-05-07 05:59:34 +02:00
}
2018-10-18 16:06:03 +02:00
2018-11-29 23:30:07 +01:00
__block NSDictionary * _Nullable 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 {
2018-10-30 16:21:26 +01:00
messageDict = [ self throws_encryptedMessageForMessageSend : messageSend
2019-11-07 06:16:56 +01:00
recipientID : recipientID
2018-10-30 16:21:26 +01:00
plainText : plainText
transaction : transaction ] ;
2018-01-30 21:05:04 +01:00
} @ catch ( NSException * exception ) {
encryptionException = exception ;
}
} ] ;
2017-09-13 22:48:38 +02:00
2017-01-31 15:46:25 +01:00
if ( encryptionException ) {
2018-08-27 18:51:32 +02:00
OWSLogInfo ( @ "Exception during encryption: %@" , 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 ] ) {
2018-07-25 17:42:50 +02:00
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2018-10-18 17:20:47 +02:00
[ recipient updateRegisteredRecipientWithDevicesToAdd : nil
2019-11-07 06:16:56 +01:00
devicesToRemove : @ [ @ ( OWSDevicePrimaryDeviceId ) ]
2018-10-18 17:20:47 +02:00
transaction : transaction ] ;
2018-07-25 17:42:50 +02:00
} ] ;
2016-10-14 23:00:29 +02:00
} else {
@ throw exception ;
}
}
}
return [ messagesArray copy ] ;
}
2018-10-30 16:21:26 +01:00
- ( void ) throws_ensureRecipientHasSessionForMessageSend : ( OWSMessageSend * ) messageSend deviceId : ( NSNumber * ) deviceId
2016-10-14 23:00:29 +02:00
{
2018-10-03 20:01:55 +02:00
OWSAssertDebug ( messageSend ) ;
2018-10-03 23:04:41 +02:00
OWSAssertDebug ( deviceId ) ;
2017-09-27 23:13:29 +02:00
2018-10-03 20:01:55 +02:00
OWSPrimaryStorage * storage = self . primaryStorage ;
SignalRecipient * recipient = messageSend . recipient ;
NSString * recipientId = recipient . recipientId ;
OWSAssertDebug ( recipientId . length > 0 ) ;
2018-07-17 16:09:01 +02:00
2018-10-18 16:06:03 +02:00
__block BOOL hasSession ;
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
hasSession = [ storage containsSession : recipientId deviceId : [ deviceId intValue ] protocolContext : transaction ] ;
} ] ;
if ( hasSession ) {
return ;
}
2019-01-04 21:06:52 +01:00
// Discard "typing indicator" messages if there is no existing session with the user .
BOOL canSafelyBeDiscarded = messageSend . message . isOnline ;
if ( canSafelyBeDiscarded ) {
OWSRaiseException ( NoSessionForTransientMessageException , @ "No session for transient message." ) ;
}
2019-05-10 08:32:20 +02:00
2019-05-14 04:33:48 +02:00
PreKeyBundle * _Nullable bundle = [ storage getPreKeyBundleForContact : recipientId ] ;
2019-11-06 23:27:15 +01:00
__block NSException * exception ;
2018-10-03 20:01:55 +02:00
2019-05-10 08:32:20 +02:00
/ * * Loki : Original code
2019-05-14 07:54:39 +02:00
* === === === === === =
2018-10-18 16:06:03 +02:00
__block dispatch_semaphore _t sema = dispatch_semaphore _create ( 0 ) ;
__block PreKeyBundle * _Nullable bundle ;
__block NSException * _Nullable exception ;
[ self makePrekeyRequestForMessageSend : messageSend
deviceId : deviceId
success : ^ ( PreKeyBundle * _Nullable responseBundle ) {
bundle = responseBundle ;
dispatch_semaphore _signal ( sema ) ;
2016-11-03 21:10:06 +01:00
}
2018-10-18 16:06:03 +02:00
failure : ^ ( NSUInteger statusCode ) {
if ( statusCode = = 404 ) {
// Can ' t throw exception from within callback as it ' s probabably a different thread .
exception = [ NSException exceptionWithName : OWSMessageSenderInvalidDeviceException
reason : @ "Device not registered"
userInfo : nil ] ;
} else if ( 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 ] ;
}
dispatch_semaphore _signal ( sema ) ;
} ] ;
dispatch_semaphore _wait ( sema , DISPATCH_TIME _FOREVER ) ;
if ( exception ) {
@ throw exception ;
}
2019-05-14 05:39:07 +02:00
* === === === === === =
2019-05-10 08:32:20 +02:00
* /
2016-10-14 23:00:29 +02:00
2018-10-18 16:06:03 +02:00
if ( ! bundle ) {
NSString * missingPrekeyBundleException = @ "missingPrekeyBundleException" ;
OWSRaiseException (
missingPrekeyBundleException , @ "Can't get a prekey bundle from the server with required information" ) ;
} else {
SessionBuilder * builder = [ [ SessionBuilder alloc ] initWithSessionStore : storage
preKeyStore : storage
signedPreKeyStore : storage
identityKeyStore : self . identityManager
recipientId : recipientId
deviceId : [ deviceId intValue ] ] ;
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2016-10-14 23:00:29 +02:00
@ try {
2018-10-30 16:21:26 +01:00
[ builder throws_processPrekeyBundle : bundle protocolContext : transaction ] ;
2019-05-13 03:40:33 +02:00
2019-11-06 23:27:15 +01:00
// Loki : Discard the pre key bundle here since the session has been established
2019-05-14 04:33:48 +02:00
[ storage removePreKeyBundleForContact : recipientId transaction : transaction ] ;
2018-10-18 16:06:03 +02:00
} @ catch ( NSException * caughtException ) {
exception = caughtException ;
2016-10-14 23:00:29 +02:00
}
2018-10-18 16:06:03 +02:00
} ] ;
if ( exception ) {
if ( [ exception . name isEqualToString : UntrustedIdentityKeyException ] ) {
OWSRaiseExceptionWithUserInfo ( UntrustedIdentityKeyException ,
( @ { TSInvalidPreKeyBundleKey : bundle , TSInvalidRecipientKey : recipientId } ) ,
@ "" ) ;
}
@ throw exception ;
2016-10-14 23:00:29 +02:00
}
}
2018-10-03 23:04:41 +02:00
}
2018-10-18 18:12:13 +02:00
2018-10-18 16:06:03 +02:00
- ( void ) makePrekeyRequestForMessageSend : ( OWSMessageSend * ) messageSend
deviceId : ( NSNumber * ) deviceId
success : ( void ( ^ ) ( PreKeyBundle * _Nullable ) ) success
failure : ( void ( ^ ) ( NSUInteger ) ) failure {
OWSAssertDebug ( messageSend ) ;
OWSAssertDebug ( deviceId ) ;
SignalRecipient * recipient = messageSend . recipient ;
NSString * recipientId = recipient . recipientId ;
OWSAssertDebug ( recipientId . length > 0 ) ;
2018-10-26 20:07:17 +02:00
OWSRequestMaker * requestMaker = [ [ OWSRequestMaker alloc ] initWithLabel : @ "Prekey Fetch"
requestFactoryBlock : ^ ( SMKUDAccessKey * _Nullable udAccessKey ) {
2018-10-19 18:25:02 +02:00
return [ OWSRequestFactory recipientPrekeyRequestWithRecipient : recipientId
deviceId : [ deviceId stringValue ]
2018-10-25 20:45:12 +02:00
udAccessKey : udAccessKey ] ;
2018-10-19 18:25:02 +02:00
}
udAuthFailureBlock : ^ {
2018-10-30 21:15:27 +01:00
// Note the UD auth failure so subsequent retries
// to this recipient also use basic auth .
2018-10-19 18:25:02 +02:00
[ messageSend setHasUDAuthFailed ] ;
}
websocketFailureBlock : ^ {
2018-10-30 21:15:27 +01:00
// Note the websocket failure so subsequent retries
// to this recipient also use REST .
2018-10-19 18:25:02 +02:00
messageSend . hasWebsocketSendFailed = YES ;
}
recipientId : recipientId
2018-10-30 17:06:20 +01:00
udAccess : messageSend . udAccess
2018-10-22 18:31:28 +02:00
canFailoverUDAuth : YES ] ;
2018-10-19 18:25:02 +02:00
[ [ requestMaker makeRequestObjc ]
. then ( ^ ( OWSRequestMakerResult * result ) {
// We _do not_ want to dispatch to the sendingQueue here ; we ' re
// using a semaphore on the sendingQueue to block on this request .
const id responseObject = result . responseObject ;
PreKeyBundle * _Nullable bundle =
[ PreKeyBundle preKeyBundleFromDictionary : responseObject forDeviceNumber : deviceId ] ;
success ( bundle ) ;
} )
. catch ( ^ ( NSError * error ) {
// We _do not_ want to dispatch to the sendingQueue here ; we ' re
// using a semaphore on the sendingQueue to block on this request .
NSUInteger statusCode = 0 ;
if ( [ error . domain isEqualToString : TSNetworkManagerErrorDomain ] ) {
statusCode = error . code ;
} else {
OWSFailDebug ( @ "Unexpected error: %@" , error ) ;
}
2018-10-18 16:06:03 +02:00
2018-10-19 18:25:02 +02:00
failure ( statusCode ) ;
} ) retainUntilComplete ] ;
2018-10-18 16:06:03 +02:00
}
2018-10-03 23:04:41 +02:00
2019-09-25 04:22:34 +02:00
- ( nullable NSDictionary * ) throws_encryptedFriendRequestOrDeviceLinkMessageForMessageSend : ( OWSMessageSend * ) messageSend
2019-05-07 05:59:34 +02:00
deviceId : ( NSNumber * ) deviceId
plainText : ( NSData * ) plainText
{
OWSAssertDebug ( messageSend ) ;
OWSAssertDebug ( deviceId ) ;
OWSAssertDebug ( plainText ) ;
SignalRecipient * recipient = messageSend . recipient ;
NSString * recipientId = recipient . recipientId ;
2019-05-22 05:24:13 +02:00
TSOutgoingMessage * message = messageSend . message ;
2019-05-07 05:59:34 +02:00
2019-05-10 05:38:00 +02:00
FallBackSessionCipher * cipher = [ [ FallBackSessionCipher alloc ] initWithRecipientId : recipientId identityKeyStore : self . identityManager ] ;
2019-05-07 05:59:34 +02:00
// This will return nil if encryption failed
NSData * _Nullable serializedMessage = [ cipher encryptWithMessage : [ plainText paddedMessageBody ] ] ;
if ( ! serializedMessage ) {
OWSFailDebug ( @ "Failed to encrypt friend message to: %@" , recipientId ) ;
return nil ;
}
OWSMessageServiceParams * messageParams =
[ [ OWSMessageServiceParams alloc ] initWithType : TSFriendRequestMessageType
recipientId : recipientId
device : [ deviceId intValue ]
content : serializedMessage
isSilent : false
isOnline : false
2019-05-22 05:24:13 +02:00
registrationId : 0
2019-05-24 08:07:00 +02:00
ttl : message . ttl
isPing : false ] ;
2019-05-07 05:59:34 +02:00
NSError * error ;
NSDictionary * jsonDict = [ MTLJSONAdapter JSONDictionaryFromModel : messageParams error : & error ] ;
if ( error ) {
OWSProdError ( [ OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson ] ) ;
return nil ;
}
return jsonDict ;
}
2018-11-29 23:30:07 +01:00
- ( nullable NSDictionary * ) throws_encryptedMessageForMessageSend : ( OWSMessageSend * ) messageSend
2019-11-07 06:16:56 +01:00
recipientID : ( NSString * ) recipientID
2018-11-29 23:30:07 +01:00
plainText : ( NSData * ) plainText
transaction : ( YapDatabaseReadWriteTransaction * ) transaction
2018-10-03 23:04:41 +02:00
{
OWSAssertDebug ( messageSend ) ;
2019-11-07 06:16:56 +01:00
OWSAssertDebug ( recipientID ) ;
2018-10-03 23:04:41 +02:00
OWSAssertDebug ( plainText ) ;
OWSAssertDebug ( transaction ) ;
OWSPrimaryStorage * storage = self . primaryStorage ;
TSOutgoingMessage * message = messageSend . message ;
2019-05-07 05:59:34 +02:00
2019-09-25 04:22:34 +02:00
// Loki : Both for friend request messages and device link messages we use fallback encryption as we don ' t necessarily have a session yet
2019-09-17 08:51:38 +02:00
BOOL isFriendRequest = [ messageSend . message isKindOfClass : LKFriendRequestMessage . class ] ;
2019-09-25 04:22:34 +02:00
BOOL isDeviceLinkMessage = [ messageSend . message isKindOfClass : LKDeviceLinkMessage . class ] ;
if ( isFriendRequest || isDeviceLinkMessage ) {
2019-11-07 06:16:56 +01:00
return [ self throws_encryptedFriendRequestOrDeviceLinkMessageForMessageSend : messageSend deviceId : @ ( OWSDevicePrimaryDeviceId ) plainText : plainText ] ;
2019-05-07 05:59:34 +02:00
}
2018-10-03 23:04:41 +02:00
// This may throw an exception .
2019-11-07 06:16:56 +01:00
if ( ! [ storage containsSession : recipientID deviceId : @ ( OWSDevicePrimaryDeviceId ) . intValue protocolContext : transaction ] ) {
2018-10-18 16:06:03 +02:00
NSString * missingSessionException = @ "missingSessionException" ;
OWSRaiseException ( missingSessionException ,
@ "Unexpectedly missing session for recipient: %@, device: %@" ,
2019-11-07 06:16:56 +01:00
recipientID ,
@ ( OWSDevicePrimaryDeviceId ) ) ;
2018-10-18 16:06:03 +02:00
}
2016-10-14 23:00:29 +02:00
SessionCipher * cipher = [ [ SessionCipher alloc ] initWithSessionStore : storage
preKeyStore : storage
signedPreKeyStore : storage
2018-10-03 23:04:41 +02:00
identityKeyStore : self . identityManager
2019-11-07 06:16:56 +01:00
recipientId : recipientID
deviceId : @ ( OWSDevicePrimaryDeviceId ) . intValue ] ;
2017-01-23 00:09:38 +01:00
2018-10-03 23:04:41 +02:00
NSData * _Nullable serializedMessage ;
TSWhisperMessageType messageType ;
2018-10-05 16:41:10 +02:00
if ( messageSend . isUDSend ) {
2018-10-03 23:04:41 +02:00
NSError * error ;
SMKSecretSessionCipher * _Nullable secretCipher =
[ [ SMKSecretSessionCipher alloc ] initWithSessionStore : self . primaryStorage
preKeyStore : self . primaryStorage
signedPreKeyStore : self . primaryStorage
identityStore : self . identityManager
error : & error ] ;
if ( error || ! secretCipher ) {
OWSRaiseException ( @ "SecretSessionCipherFailure" , @ "Can't create secret session cipher." ) ;
}
2017-01-23 00:09:38 +01:00
2019-11-07 06:16:56 +01:00
serializedMessage = [ secretCipher throwswrapped_encryptMessageWithRecipientId : recipientID
deviceId : @ ( OWSDevicePrimaryDeviceId ) . intValue
2018-10-30 16:28:01 +01:00
paddedPlaintext : [ plainText paddedMessageBody ]
senderCertificate : messageSend . senderCertificate
protocolContext : transaction
error : & error ] ;
2018-10-28 19:18:04 +01:00
SCKRaiseIfExceptionWrapperError ( error ) ;
2018-11-29 23:30:07 +01:00
if ( ! serializedMessage || error ) {
OWSFailDebug ( @ "error while UD encrypting message: %@" , error ) ;
return nil ;
}
2018-10-03 23:04:41 +02:00
messageType = TSUnidentifiedSenderMessageType ;
} else {
2018-10-05 16:32:32 +02:00
// This may throw an exception .
2018-10-03 23:04:41 +02:00
id < CipherMessage > encryptedMessage =
2018-10-30 16:21:26 +01:00
[ cipher throws_encryptMessage : [ plainText paddedMessageBody ] protocolContext : transaction ] ;
2018-10-03 23:04:41 +02:00
serializedMessage = encryptedMessage . serialized ;
messageType = [ self messageTypeForCipherMessage : encryptedMessage ] ;
}
2016-10-14 23:00:29 +02:00
2018-10-03 20:01:55 +02:00
BOOL isSilent = message . isSilent ;
2018-10-31 00:18:17 +01:00
BOOL isOnline = message . isOnline ;
2019-05-24 08:07:00 +02:00
2019-11-07 01:59:11 +01:00
LKAddressMessage * addressMessage = [ message as : [ LKAddressMessage class ] ] ;
2019-05-24 08:07:00 +02:00
BOOL isPing = addressMessage ! = nil && addressMessage . isPing ;
2018-01-30 21:05:04 +01:00
OWSMessageServiceParams * messageParams =
[ [ OWSMessageServiceParams alloc ] initWithType : messageType
2019-11-07 06:16:56 +01:00
recipientId : recipientID
device : @ ( OWSDevicePrimaryDeviceId ) . intValue
2018-01-30 21:05:04 +01:00
content : serializedMessage
isSilent : isSilent
2018-10-31 00:18:17 +01:00
isOnline : isOnline
2019-05-22 05:24:13 +02:00
registrationId : [ cipher throws_remoteRegistrationId : transaction ]
2019-05-24 08:07:00 +02:00
ttl : message . ttl
isPing : isPing ] ;
2016-10-14 23:00:29 +02:00
NSError * error ;
NSDictionary * jsonDict = [ MTLJSONAdapter JSONDictionaryFromModel : messageParams error : & error ] ;
2019-05-22 05:24:13 +02:00
2016-10-14 23:00:29 +02:00
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
{
2018-09-28 16:56:53 +02:00
switch ( cipherMessage . cipherMessageType ) {
case CipherMessageType_Whisper :
return TSEncryptedWhisperMessageType ;
case CipherMessageType_Prekey :
return TSPreKeyWhisperMessageType ;
default :
return TSUnknownMessageType ;
2016-10-14 23:00:29 +02:00
}
}
2018-10-05 16:32:32 +02:00
- ( void ) saveInfoMessageForGroupMessage : ( TSOutgoingMessage * ) message inThread : ( TSThread * ) thread
{
2018-10-04 21:22:42 +02:00
OWSAssertDebug ( message ) ;
OWSAssertDebug ( thread ) ;
if ( message . groupMetaMessage = = TSGroupMetaMessageDeliver ) {
// TODO : Why is this necessary ?
[ message save ] ;
} else if ( message . groupMetaMessage = = TSGroupMetaMessageQuit ) {
2018-10-03 23:41:43 +02:00
// MJK TODO - remove senderTimestamp
2018-10-05 16:32:32 +02:00
[ [ [ TSInfoMessage alloc ] initWithTimestamp : message . timestamp
inThread : thread
messageType : TSInfoMessageTypeGroupQuit
customMessage : message . customMessage ] save ] ;
} else {
2018-10-03 23:41:43 +02:00
// MJK TODO - remove senderTimestamp
2018-10-05 16:32:32 +02:00
[ [ [ TSInfoMessage alloc ] initWithTimestamp : message . timestamp
inThread : thread
messageType : TSInfoMessageTypeGroupUpdate
customMessage : message . customMessage ] save ] ;
}
}
2016-10-14 23:00:29 +02:00
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
2018-10-20 19:51:48 +02:00
@ implementation OutgoingMessagePreparer
# pragma mark - Dependencies
+ ( YapDatabaseConnection * ) dbConnection
{
return SSKEnvironment . shared . primaryStorage . dbReadWriteConnection ;
}
# pragma mark -
2019-01-17 17:56:21 +01:00
+ ( NSArray < NSString * > * ) prepareMessageForSending : ( TSOutgoingMessage * ) message
transaction : ( YapDatabaseReadWriteTransaction * ) transaction
2018-10-20 19:51:48 +02:00
{
2019-01-17 17:56:21 +01:00
OWSAssertDebug ( message ) ;
OWSAssertDebug ( transaction ) ;
NSMutableArray < NSString * > * attachmentIds = [ NSMutableArray new ] ;
if ( message . attachmentIds ) {
[ attachmentIds addObjectsFromArray : message . attachmentIds ] ;
}
2018-10-20 19:51:48 +02:00
if ( message . quotedMessage ) {
2019-01-17 17:56:21 +01: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 .
NSArray < TSAttachmentStream * > * quotedThumbnailAttachments =
2018-10-20 19:51:48 +02:00
[ message . quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction : transaction ] ;
2019-01-17 17:56:21 +01:00
for ( TSAttachmentStream * attachment in quotedThumbnailAttachments ) {
[ attachmentIds addObject : attachment . uniqueId ] ;
}
2018-10-20 19:51:48 +02:00
}
if ( message . contactShare . avatarAttachmentId ! = nil ) {
2019-01-17 17:56:21 +01:00
TSAttachment * attachment = [ message . contactShare avatarAttachmentWithTransaction : transaction ] ;
if ( [ attachment isKindOfClass : [ TSAttachmentStream class ] ] ) {
[ attachmentIds addObject : attachment . uniqueId ] ;
} else {
OWSFailDebug ( @ "unexpected avatarAttachment: %@" , attachment ) ;
}
}
if ( message . linkPreview . imageAttachmentId ! = nil ) {
TSAttachment * attachment =
[ TSAttachment fetchObjectWithUniqueID : message . linkPreview . imageAttachmentId transaction : transaction ] ;
if ( [ attachment isKindOfClass : [ TSAttachmentStream class ] ] ) {
[ attachmentIds addObject : attachment . uniqueId ] ;
2018-10-20 19:51:48 +02:00
} else {
2019-01-17 17:56:21 +01:00
OWSFailDebug ( @ "unexpected attachment: %@" , attachment ) ;
2018-10-20 19:51:48 +02:00
}
}
// All outgoing messages should be saved at the time they are enqueued .
[ message saveWithTransaction : transaction ] ;
// When we start a message send , all "failed" recipients should be marked as "sending" .
[ message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction : transaction ] ;
2019-01-17 17:56:21 +01:00
return attachmentIds ;
2018-10-20 19:51:48 +02:00
}
2018-11-02 16:33:05 +01:00
+ ( void ) prepareAttachments : ( NSArray < OWSOutgoingAttachmentInfo * > * ) attachmentInfos
inMessage : ( TSOutgoingMessage * ) outgoingMessage
completionHandler : ( void ( ^ ) ( NSError * _Nullable error ) ) completionHandler
2018-10-20 19:51:48 +02:00
{
2018-11-02 16:33:05 +01:00
OWSAssertDebug ( attachmentInfos . count > 0 ) ;
OWSAssertDebug ( outgoingMessage ) ;
2018-10-20 19:51:48 +02:00
dispatch_async ( [ OWSDispatch attachmentsQueue ] , ^ {
2018-11-02 16:33:05 +01:00
NSMutableArray < TSAttachmentStream * > * attachmentStreams = [ NSMutableArray new ] ;
for ( OWSOutgoingAttachmentInfo * attachmentInfo in attachmentInfos ) {
TSAttachmentStream * attachmentStream =
[ [ TSAttachmentStream alloc ] initWithContentType : attachmentInfo . contentType
byteCount : ( UInt32 ) attachmentInfo . dataSource . dataLength
2018-11-06 23:30:09 +01:00
sourceFilename : attachmentInfo . sourceFilename
2018-11-07 17:42:28 +01:00
caption : attachmentInfo . caption
albumMessageId : attachmentInfo . albumMessageId ] ;
2018-11-02 16:33:05 +01:00
if ( outgoingMessage . isVoiceMessage ) {
attachmentStream . attachmentType = TSAttachmentTypeVoiceMessage ;
}
2018-10-20 19:51:48 +02:00
2018-11-02 16:33:05 +01:00
if ( ! [ attachmentStream writeDataSource : attachmentInfo . dataSource ] ) {
OWSProdError ( [ OWSAnalyticsEvents messageSenderErrorCouldNotWriteAttachment ] ) ;
NSError * error = OWSErrorMakeWriteAttachmentDataError ( ) ;
completionHandler ( error ) ;
return ;
}
[ attachmentStreams addObject : attachmentStream ] ;
2018-10-20 19:51:48 +02:00
}
[ self . dbConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * _Nonnull transaction ) {
2018-11-02 16:33:05 +01:00
for ( TSAttachmentStream * attachmentStream in attachmentStreams ) {
[ outgoingMessage . attachmentIds addObject : attachmentStream . uniqueId ] ;
if ( attachmentStream . sourceFilename ) {
outgoingMessage . attachmentFilenameMap [ attachmentStream . uniqueId ] = attachmentStream . sourceFilename ;
}
2018-10-20 19:51:48 +02:00
}
[ outgoingMessage saveWithTransaction : transaction ] ;
2018-11-07 18:00:34 +01:00
for ( TSAttachmentStream * attachmentStream in attachmentStreams ) {
[ attachmentStream saveWithTransaction : transaction ] ;
}
2018-10-20 19:51:48 +02:00
} ] ;
completionHandler ( nil ) ;
} ) ;
}
@ end
2016-10-08 01:17:38 +02:00
NS_ASSUME _NONNULL _END