session-ios/SignalServiceKit/src/Messages/OWSMessageManager.m

2052 lines
98 KiB
Mathematica
Raw Normal View History

//
2019-01-04 15:19:41 +01:00
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
2015-12-07 03:31:43 +01:00
#import "OWSMessageManager.h"
#import "AppContext.h"
#import "AppReadiness.h"
#import "ContactsManagerProtocol.h"
#import "MimeTypeUtil.h"
2018-10-16 19:30:25 +02:00
#import "NSNotificationCenter+OWS.h"
#import "NSString+SSK.h"
#import "NotificationsProtocol.h"
2018-11-07 23:49:25 +01:00
#import "OWSAttachmentDownloads.h"
2017-04-03 20:42:04 +02:00
#import "OWSBlockingManager.h"
#import "OWSCallMessageHandler.h"
2018-05-01 14:52:59 +02:00
#import "OWSContact.h"
#import "OWSDevice.h"
#import "OWSDevicesService.h"
#import "OWSDisappearingConfigurationUpdateInfoMessage.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesJob.h"
2019-05-21 03:40:29 +02:00
#import "LKEphemeralMessage.h"
#import "LKSessionRequestMessage.h"
2019-09-24 08:03:31 +02:00
#import "LKDeviceLinkMessage.h"
#import "OWSIdentityManager.h"
2017-02-16 00:32:27 +01:00
#import "OWSIncomingMessageFinder.h"
#import "OWSIncomingSentMessageTranscript.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "OWSMessageSender.h"
2018-01-30 21:05:04 +01:00
#import "OWSMessageUtils.h"
2018-10-11 21:29:01 +02:00
#import "OWSOutgoingReceiptManager.h"
#import "OWSPrimaryStorage+SessionStore.h"
#import "OWSPrimaryStorage+Loki.h"
#import "OWSPrimaryStorage.h"
#import "OWSReadReceiptManager.h"
#import "OWSRecordTranscriptJob.h"
#import "OWSSyncGroupsMessage.h"
#import "OWSSyncGroupsRequestMessage.h"
#import "ProfileManagerProtocol.h"
#import "SSKEnvironment.h"
#import "TSAccountManager.h"
#import "TSAttachment.h"
#import "TSAttachmentPointer.h"
#import "TSAttachmentStream.h"
#import "TSContactThread.h"
2015-12-07 03:31:43 +01:00
#import "TSDatabaseView.h"
#import "TSGroupModel.h"
#import "TSGroupThread.h"
#import "TSIncomingMessage.h"
2015-12-07 03:31:43 +01:00
#import "TSInfoMessage.h"
#import "TSNetworkManager.h"
#import "TSOutgoingMessage.h"
#import "TSQuotedMessage.h"
2018-09-25 19:09:55 +02:00
#import <SignalCoreKit/Cryptography.h>
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSDate+OWS.h>
2020-02-17 01:24:16 +01:00
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
2019-05-21 03:40:29 +02:00
#import <SignalServiceKit/NSObject+Casting.h>
#import <SignalServiceKit/SignalRecipient.h>
2018-06-07 07:57:59 +02:00
#import <SignalServiceKit/SignalServiceKit-Swift.h>
2017-12-19 03:42:50 +01:00
#import <YapDatabase/YapDatabase.h>
#import "OWSDispatch.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSQueues.h"
2015-12-07 03:31:43 +01:00
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageManager ()
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder;
@end
#pragma mark -
@implementation OWSMessageManager
2015-12-07 03:31:43 +01:00
+ (instancetype)sharedManager
{
OWSAssertDebug(SSKEnvironment.shared.messageManager);
2015-12-07 03:31:43 +01:00
return SSKEnvironment.shared.messageManager;
}
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
{
2015-12-07 03:31:43 +01:00
self = [super init];
if (!self) {
return self;
2015-12-07 03:31:43 +01:00
}
_primaryStorage = primaryStorage;
_dbConnection = primaryStorage.newDatabaseConnection;
_incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithPrimaryStorage:primaryStorage];
OWSSingletonAssert();
2015-12-07 03:31:43 +01:00
return self;
}
- (void)dealloc {
2019-05-20 03:20:03 +02:00
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark - Dependencies
- (id<OWSCallMessageHandler>)callMessageHandler
{
OWSAssertDebug(SSKEnvironment.shared.callMessageHandler);
return SSKEnvironment.shared.callMessageHandler;
}
- (id<ContactsManagerProtocol>)contactsManager
{
OWSAssertDebug(SSKEnvironment.shared.contactsManager);
return SSKEnvironment.shared.contactsManager;
}
- (SSKMessageSenderJobQueue *)messageSenderJobQueue
{
return SSKEnvironment.shared.messageSenderJobQueue;
}
- (OWSBlockingManager *)blockingManager
{
OWSAssertDebug(SSKEnvironment.shared.blockingManager);
return SSKEnvironment.shared.blockingManager;
}
- (OWSIdentityManager *)identityManager
{
OWSAssertDebug(SSKEnvironment.shared.identityManager);
return SSKEnvironment.shared.identityManager;
}
- (TSNetworkManager *)networkManager
{
OWSAssertDebug(SSKEnvironment.shared.networkManager);
return SSKEnvironment.shared.networkManager;
}
2018-10-12 17:49:50 +02:00
- (OWSOutgoingReceiptManager *)outgoingReceiptManager
{
2018-10-11 21:29:01 +02:00
OWSAssertDebug(SSKEnvironment.shared.outgoingReceiptManager);
2018-10-11 18:51:58 +02:00
2018-10-11 21:29:01 +02:00
return SSKEnvironment.shared.outgoingReceiptManager;
2018-10-11 18:51:58 +02:00
}
2018-10-18 22:58:02 +02:00
- (id<OWSSyncManagerProtocol>)syncManager
{
OWSAssertDebug(SSKEnvironment.shared.syncManager);
return SSKEnvironment.shared.syncManager;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
2018-10-30 17:06:20 +01:00
- (id<ProfileManagerProtocol>)profileManager
{
return SSKEnvironment.shared.profileManager;
}
- (id<OWSTypingIndicators>)typingIndicators
{
return SSKEnvironment.shared.typingIndicators;
}
2018-11-07 23:49:25 +01:00
- (OWSAttachmentDownloads *)attachmentDownloads
{
return SSKEnvironment.shared.attachmentDownloads;
}
2018-10-11 18:51:58 +02:00
#pragma mark -
- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:OWSPrimaryStorage.sharedManager.dbNotificationObject];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedExternallyNotification
2018-01-11 16:04:03 +01:00
object:nil];
}
- (void)yapDatabaseModified:(NSNotification *)notification
{
if (AppReadiness.isAppReady) {
[OWSMessageUtils.sharedManager updateApplicationBadgeCount];
} else {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[OWSMessageUtils.sharedManager updateApplicationBadgeCount];
}];
});
}
}
#pragma mark - Blocking
- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope
{
OWSAssertDebug(envelope);
return [self.blockingManager isRecipientIdBlocked:envelope.source];
}
- (BOOL)isDataMessageBlocked:(SSKProtoDataMessage *)dataMessage envelope:(SSKProtoEnvelope *)envelope
{
2018-09-15 16:17:08 +02:00
OWSAssertDebug(dataMessage);
OWSAssertDebug(envelope);
if (dataMessage.group) {
return [self.blockingManager isGroupIdBlocked:dataMessage.group.id];
} else {
BOOL senderBlocked = [self isEnvelopeSenderBlocked:envelope];
// If the envelopeSender was blocked, we never should have gotten as far as decrypting the dataMessage.
2018-09-15 16:17:08 +02:00
OWSAssertDebug(!senderBlocked);
return senderBlocked;
}
}
#pragma mark - message handling
- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope
plaintextData:(NSData *_Nullable)plaintextData
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
2019-11-15 03:56:35 +01:00
serverID:(uint64_t)serverID
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
2018-11-26 16:24:36 +01:00
if (!self.tsAccountManager.isRegistered) {
2018-08-30 16:31:01 +02:00
OWSFailDebug(@"Not registered.");
return;
}
2017-02-10 01:35:10 +01:00
2020-02-15 00:01:21 +01:00
OWSLogInfo(@"Handling decrypted envelope: %@.", [self descriptionForEnvelope:envelope]);
2015-12-07 03:31:43 +01:00
2020-01-31 07:01:29 +01:00
if (!wasReceivedByUD) {
if (!envelope.hasSource || envelope.source.length < 1) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Incoming envelope with invalid source.");
2020-01-31 07:01:29 +01:00
return;
}
if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Incoming envelope with invalid source device.");
2020-01-31 07:01:29 +01:00
return;
}
2018-10-05 15:28:53 +02:00
}
2018-02-16 02:45:28 +01:00
2018-09-15 16:17:08 +02:00
OWSAssertDebug(![self isEnvelopeSenderBlocked:envelope]);
2015-12-07 03:31:43 +01:00
2020-02-15 00:01:21 +01:00
// Loki: Ignore any friend requests from before restoration
// The envelope type is set during UD decryption.
uint64_t restorationTime = [NSNumber numberWithDouble:[OWSPrimaryStorage.sharedManager getRestorationTime]].unsignedLongLongValue;
if (envelope.type == SSKProtoEnvelopeTypeFriendRequest && envelope.timestamp < restorationTime * 1000) {
[LKLogger print:@"[Loki] Ignoring friend request received before restoration."];
return;
}
[self checkForUnknownLinkedDevice:envelope transaction:transaction];
switch (envelope.type) {
case SSKProtoEnvelopeTypeFriendRequest:
case SSKProtoEnvelopeTypeCiphertext:
case SSKProtoEnvelopeTypePrekeyBundle:
2018-10-04 17:33:58 +02:00
case SSKProtoEnvelopeTypeUnidentifiedSender:
2018-08-30 16:31:01 +02:00
if (!plaintextData) {
OWSFailDebug(@"missing decrypted data for envelope: %@", [self descriptionForEnvelope:envelope]);
2018-08-30 16:31:01 +02:00
return;
}
2018-12-02 23:30:31 +01:00
[self throws_handleEnvelope:envelope
plaintextData:plaintextData
wasReceivedByUD:wasReceivedByUD
2019-11-15 03:56:35 +01:00
transaction:transaction
serverID:serverID];
break;
case SSKProtoEnvelopeTypeReceipt:
OWSAssertDebug(!plaintextData);
[self handleDeliveryReceipt:envelope transaction:transaction];
break;
2018-04-16 20:48:29 +02:00
// Other messages are just dismissed for now.
case SSKProtoEnvelopeTypeKeyExchange:
OWSLogWarn(@"Received Key Exchange Message, not supported");
break;
case SSKProtoEnvelopeTypeUnknown:
OWSLogWarn(@"Received an unknown message type");
break;
default:
OWSLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type);
break;
}
}
- (void)handleDeliveryReceipt:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
// Old-style delivery notices don't include a "delivery timestamp".
2017-09-27 20:19:26 +02:00
[self processDeliveryReceiptsFromRecipientId:envelope.source
sentTimestamps:@[
@(envelope.timestamp),
]
deliveryTimestamp:nil
transaction:transaction];
}
2017-09-27 20:19:26 +02:00
// deliveryTimestamp is an optional parameter, since legacy
// delivery receipts don't have a "delivery timestamp". Those
// messages repurpose the "timestamp" field to indicate when the
// corresponding message was originally sent.
- (void)processDeliveryReceiptsFromRecipientId:(NSString *)recipientId
sentTimestamps:(NSArray<NSNumber *> *)sentTimestamps
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (recipientId.length < 1) {
OWSFailDebug(@"Empty recipientId.");
return;
}
if (sentTimestamps.count < 1) {
OWSFailDebug(@"Missing sentTimestamps.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
for (NSNumber *nsTimestamp in sentTimestamps) {
uint64_t timestamp = [nsTimestamp unsignedLongLongValue];
NSArray<TSOutgoingMessage *> *messages
= (NSArray<TSOutgoingMessage *> *)[TSInteraction interactionsWithTimestamp:timestamp
ofClass:[TSOutgoingMessage class]
withTransaction:transaction];
if (messages.count < 1) {
2017-09-27 20:19:26 +02:00
// The service sends delivery receipts for "unpersisted" messages
// like group updates, so these errors are expected to a certain extent.
2017-09-27 20:19:26 +02:00
//
// TODO: persist "early" delivery receipts.
OWSLogInfo(@"Missing message for delivery receipt: %llu", timestamp);
} else {
if (messages.count > 1) {
OWSLogInfo(@"More than one message (%lu) for delivery receipt: %llu",
2018-07-18 03:08:53 +02:00
(unsigned long)messages.count,
2017-11-08 20:04:51 +01:00
timestamp);
}
for (TSOutgoingMessage *outgoingMessage in messages) {
2018-04-23 16:30:51 +02:00
[outgoingMessage updateWithDeliveredRecipient:recipientId
deliveryTimestamp:deliveryTimestamp
transaction:transaction];
}
2017-09-22 21:15:04 +02:00
}
}
}
- (void)throws_handleEnvelope:(SSKProtoEnvelope *)envelope
plaintextData:(NSData *)plaintextData
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
2019-11-15 03:56:35 +01:00
serverID:(uint64_t)serverID
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!plaintextData) {
OWSFailDebug(@"Missing plaintextData.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
if (envelope.timestamp < 1) {
OWSFailDebug(@"Invalid timestamp.");
return;
}
if (envelope.source.length < 1) {
OWSFailDebug(@"Missing source.");
return;
}
if (envelope.sourceDevice < 1) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Invalid source device.");
2018-08-30 16:31:01 +02:00
return;
}
2017-02-16 00:32:27 +01:00
BOOL duplicateEnvelope = [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp
sourceId:envelope.source
sourceDeviceId:envelope.sourceDevice
transaction:transaction];
2017-02-16 00:32:27 +01:00
if (duplicateEnvelope) {
2020-02-15 00:01:21 +01:00
OWSLogInfo(@"Ignoring previously received envelope from: %@ with timestamp: %llu.",
envelopeAddress(envelope),
envelope.timestamp);
2017-02-16 00:32:27 +01:00
return;
}
2019-07-24 01:20:33 +02:00
// Loki: Handle friend request acceptance if needed
2020-02-15 00:01:21 +01:00
// The envelope type is set during UD decryption.
[self handleFriendRequestAcceptanceIfNeededWithEnvelope:envelope transaction:transaction];
2018-08-03 21:50:27 +02:00
if (envelope.content != nil) {
NSError *error;
SSKProtoContent *_Nullable contentProto = [SSKProtoContent parseData:plaintextData error:&error];
if (error || !contentProto) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Could not parse proto due to error: %@.", error);
return;
}
2020-02-15 00:01:21 +01:00
OWSLogInfo(@"Handling content: <Content: %@>.", [self descriptionForContent:contentProto]);
2019-11-27 04:54:45 +01:00
// Loki: Workaround for duplicate sync transcript issue
if (contentProto.syncMessage != nil && contentProto.syncMessage.sent != nil) {
BOOL isDuplicate = [LKAPI isDuplicateSyncMessage:contentProto.syncMessage.sent from:envelope.source];
if (isDuplicate) { return; }
}
2019-10-31 06:52:22 +01:00
// Loki: Handle pre key bundle message if needed
if (contentProto.prekeyBundleMessage != nil) {
2020-03-31 07:34:31 +02:00
[LKLogger print:[NSString stringWithFormat:@"[Loki] Received a pre key bundle message from: %@.", envelope.source]];
2019-11-07 02:21:49 +01:00
PreKeyBundle *_Nullable bundle = [contentProto.prekeyBundleMessage getPreKeyBundleWithTransaction:transaction];
if (bundle == nil) {
OWSFailDebug(@"Failed to create a pre key bundle.");
2020-01-30 10:16:31 +01:00
return;
}
[self.primaryStorage setPreKeyBundle:bundle forContact:envelope.source transaction:transaction];
2020-02-15 00:01:21 +01:00
// Loki: If we received a friend request, but we were already friends with this user, reset the session
// The envelope type is set during UD decryption.
if (envelope.type == SSKProtoEnvelopeTypeFriendRequest) {
TSContactThread *thread = [TSContactThread getThreadWithContactId:envelope.source transaction:transaction];
if (thread && thread.isContactFriend) {
[self resetSessionWithContact:envelope.source transaction:transaction];
2020-02-04 00:53:49 +01:00
// Let our other devices know that we have reset the session
[SSKEnvironment.shared.syncManager syncContact:envelope.source transaction:transaction];
}
}
}
2019-10-31 06:52:22 +01:00
// Loki: Handle address message if needed
if (contentProto.lokiAddressMessage) {
2019-05-24 07:20:49 +02:00
NSString *address = contentProto.lokiAddressMessage.ptpAddress;
uint32_t port = contentProto.lokiAddressMessage.ptpPort;
[LKP2PAPI didReceiveLokiAddressMessageForContact:envelope.source address:address port:port receivedThroughP2P:envelope.isPtpMessage];
}
// Loki: Handle device linking message if needed
if (contentProto.lokiDeviceLinkMessage != nil) {
NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.masterHexEncodedPublicKey;
NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkMessage.slaveHexEncodedPublicKey;
NSData *masterSignature = contentProto.lokiDeviceLinkMessage.masterSignature;
NSData *slaveSignature = contentProto.lokiDeviceLinkMessage.slaveSignature;
if (masterSignature != nil) { // Authorization
2020-03-31 07:34:31 +02:00
[LKLogger print:[NSString stringWithFormat:@"[Loki] Received a device linking authorization from: %@", envelope.source]]; // Not masterHexEncodedPublicKey
[LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature];
2019-12-02 03:10:01 +01:00
// Set any profile info
if (contentProto.dataMessage) {
2019-12-02 01:01:07 +01:00
SSKProtoDataMessage *dataMessage = contentProto.dataMessage;
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey];
}
} else if (slaveSignature != nil) { // Request
2020-03-31 07:34:31 +02:00
[LKLogger print: [NSString stringWithFormat:@"[Loki] Received a device linking request from: %@", envelope.source]]; // Not slaveHexEncodedPublicKey
if (LKDeviceLinkingSession.current == nil) {
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.unexpectedDeviceLinkRequestReceived object:nil];
}
[LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature];
}
} else if (contentProto.syncMessage) {
[self throws_handleIncomingEnvelope:envelope
withSyncMessage:contentProto.syncMessage
2019-11-15 03:56:35 +01:00
transaction:transaction
serverID:serverID];
[[OWSDeviceManager sharedManager] setHasReceivedSyncMessage];
} else if (contentProto.dataMessage) {
2018-12-02 23:30:31 +01:00
[self handleIncomingEnvelope:envelope
withDataMessage:contentProto.dataMessage
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
} else if (contentProto.callMessage) {
[self handleIncomingEnvelope:envelope withCallMessage:contentProto.callMessage];
} else if (contentProto.typingMessage) {
[self handleIncomingEnvelope:envelope withTypingMessage:contentProto.typingMessage transaction:transaction];
} else if (contentProto.nullMessage) {
OWSLogInfo(@"Received null message.");
} else if (contentProto.receiptMessage) {
[self handleIncomingEnvelope:envelope
withReceiptMessage:contentProto.receiptMessage
transaction:transaction];
} else {
OWSLogWarn(@"Ignoring envelope. Content with no known payload");
}
2018-08-03 21:50:27 +02:00
} else if (envelope.legacyMessage != nil) { // DEPRECATED - Remove after all clients have been upgraded.
NSError *error;
SSKProtoDataMessage *_Nullable dataMessageProto = [SSKProtoDataMessage parseData:plaintextData error:&error];
if (error || !dataMessageProto) {
OWSFailDebug(@"could not parse proto: %@", error);
return;
}
OWSLogInfo(@"handling message: <DataMessage: %@ />", [self descriptionForDataMessage:dataMessageProto]);
2018-12-02 23:30:31 +01:00
[self handleIncomingEnvelope:envelope
withDataMessage:dataMessageProto
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
} else {
2018-07-25 22:00:25 +02:00
OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorEnvelopeNoActionablePayload], envelope);
2015-12-07 03:31:43 +01:00
}
}
- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withDataMessage:(SSKProtoDataMessage *)dataMessage
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
2020-02-15 00:01:21 +01:00
// Loki: Don't process session request messages any further
if ((dataMessage.flags & SSKProtoDataMessageFlagsSessionRequest) != 0) { return; }
2020-02-15 00:01:21 +01:00
// Loki: Don't process session restore messages any further
if ((dataMessage.flags & SSKProtoDataMessageFlagsSessionRestore) != 0) { return; }
if ([self isDataMessageBlocked:dataMessage envelope:envelope]) {
2020-02-15 00:01:21 +01:00
NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@.", envelope.source];
if (dataMessage.group) {
logMessage = [logMessage stringByAppendingFormat:@" in group: %@", dataMessage.group.id];
}
2018-09-15 16:17:08 +02:00
OWSLogError(@"%@", logMessage);
return;
}
2018-02-02 16:56:16 +01:00
if (dataMessage.hasTimestamp) {
if (dataMessage.timestamp <= 0) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Ignoring data message with invalid timestamp: %@.", envelope.source);
2018-02-02 16:56:16 +01:00
return;
}
// This prevents replay attacks by the service.
if (dataMessage.timestamp != envelope.timestamp) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Ignoring data message with non-matching timestamp: %@.", envelope.source);
2018-02-02 16:56:16 +01:00
return;
}
}
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:envelope.source];
2018-02-02 16:56:16 +01:00
if (dataMessage.group) {
2017-10-04 16:06:38 +02:00
TSGroupThread *_Nullable groupThread =
[TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction];
2017-10-04 16:06:38 +02:00
2018-09-12 23:06:21 +02:00
if (groupThread) {
if (dataMessage.group.type != SSKProtoGroupContextTypeUpdate) {
2020-01-30 05:51:46 +01:00
if (![groupThread isLocalUserInGroupWithTransaction:transaction]) {
2018-09-15 16:17:08 +02:00
OWSLogInfo(@"Ignoring messages for left group.");
2018-09-12 23:06:21 +02:00
return;
}
}
} else {
2017-10-04 16:06:38 +02:00
// Unknown group.
if (dataMessage.group.type == SSKProtoGroupContextTypeUpdate) {
2017-10-04 16:06:38 +02:00
// Accept group updates for unknown groups.
} else if (dataMessage.group.type == SSKProtoGroupContextTypeDeliver) {
2017-10-04 16:06:38 +02:00
[self sendGroupInfoRequest:dataMessage.group.id envelope:envelope transaction:transaction];
return;
2017-10-04 16:06:38 +02:00
} else {
2020-02-15 00:01:21 +01:00
OWSLogInfo(@"Ignoring group message for unknown group from: %@.", envelope.source);
return;
}
2015-12-07 03:31:43 +01:00
}
}
if ((dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0) {
[self handleEndSessionMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction];
} else if ((dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0) {
[self handleExpirationTimerUpdateMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction];
} else if ((dataMessage.flags & SSKProtoDataMessageFlagsProfileKeyUpdate) != 0) {
[self handleProfileKeyMessageWithEnvelope:envelope dataMessage:dataMessage];
2019-11-20 06:27:34 +01:00
} else if ((dataMessage.flags & SSKProtoDataMessageFlagsUnlinkDevice) != 0) {
[self handleUnlinkDeviceMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
} else if (dataMessage.attachments.count > 0) {
2018-12-02 23:30:31 +01:00
[self handleReceivedMediaWithEnvelope:envelope
dataMessage:dataMessage
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
2015-12-07 03:31:43 +01:00
} else {
2018-12-02 23:30:31 +01:00
[self handleReceivedTextMessageWithEnvelope:envelope
dataMessage:dataMessage
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
if ([self isDataMessageGroupAvatarUpdate:dataMessage]) {
OWSLogVerbose(@"Data message had group avatar attachment");
[self handleReceivedGroupAvatarUpdateWithEnvelope:envelope dataMessage:dataMessage transaction:transaction];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
}
2018-10-11 18:51:58 +02:00
2018-10-11 18:52:49 +02:00
// Send delivery receipts for "valid data" messages received via UD.
if (wasReceivedByUD) {
2018-10-11 21:29:01 +02:00
[self.outgoingReceiptManager enqueueDeliveryReceiptForEnvelope:envelope];
2018-10-11 18:52:49 +02:00
}
2018-10-11 18:51:58 +02:00
}
- (void)sendGroupInfoRequest:(NSData *)groupId
envelope:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
if (groupId.length < 1) {
2018-08-30 16:31:01 +02:00
OWSFailDebug(@"Invalid groupId.");
return;
}
// FIXME: https://github.com/signalapp/Signal-iOS/issues/1340
OWSLogInfo(@"Sending group info request: %@", envelopeAddress(envelope));
NSString *recipientId = envelope.source;
TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction];
OWSSyncGroupsRequestMessage *syncGroupsRequestMessage =
[[OWSSyncGroupsRequestMessage alloc] initWithThread:thread groupId:groupId];
[self.messageSenderJobQueue addMessage:syncGroupsRequestMessage transaction:transaction];
}
- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withReceiptMessage:(SSKProtoReceiptMessage *)receiptMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!receiptMessage) {
OWSFailDebug(@"Missing receiptMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
NSArray<NSNumber *> *sentTimestamps = receiptMessage.timestamp;
switch (receiptMessage.type) {
case SSKProtoReceiptMessageTypeDelivery:
OWSLogVerbose(@"Processing receipt message with delivery receipts.");
2017-09-27 20:19:26 +02:00
[self processDeliveryReceiptsFromRecipientId:envelope.source
sentTimestamps:sentTimestamps
deliveryTimestamp:@(envelope.timestamp)
transaction:transaction];
return;
case SSKProtoReceiptMessageTypeRead:
OWSLogVerbose(@"Processing receipt message with read receipts.");
[OWSReadReceiptManager.sharedManager processReadReceiptsFromRecipientId:envelope.source
sentTimestamps:sentTimestamps
readTimestamp:envelope.timestamp];
break;
default:
OWSLogInfo(@"Ignoring receipt message of unknown type: %d.", (int)receiptMessage.type);
return;
}
}
- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withCallMessage:(SSKProtoCallMessage *)callMessage
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!callMessage) {
OWSFailDebug(@"Missing callMessage.");
return;
}
if ([callMessage hasProfileKey]) {
NSData *profileKey = [callMessage profileKey];
NSString *recipientId = envelope.source;
[self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId];
}
2017-09-20 17:48:37 +02:00
// By dispatching async, we introduce the possibility that these messages might be lost
// if the app exits before this block is executed. This is fine, since the call by
// definition will end if the app exits.
dispatch_async(dispatch_get_main_queue(), ^{
if (callMessage.offer) {
[self.callMessageHandler receivedOffer:callMessage.offer fromCallerId:envelope.source];
} else if (callMessage.answer) {
[self.callMessageHandler receivedAnswer:callMessage.answer fromCallerId:envelope.source];
} else if (callMessage.iceUpdate.count > 0) {
for (SSKProtoCallMessageIceUpdate *iceUpdate in callMessage.iceUpdate) {
[self.callMessageHandler receivedIceUpdate:iceUpdate fromCallerId:envelope.source];
}
} else if (callMessage.hangup) {
OWSLogVerbose(@"Received CallMessage with Hangup.");
[self.callMessageHandler receivedHangup:callMessage.hangup fromCallerId:envelope.source];
} else if (callMessage.busy) {
[self.callMessageHandler receivedBusy:callMessage.busy fromCallerId:envelope.source];
} else {
OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorCallMessageNoActionablePayload], envelope);
}
});
}
- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withTypingMessage:(SSKProtoTypingMessage *)typingMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!typingMessage) {
OWSFailDebug(@"Missing typingMessage.");
return;
}
if (typingMessage.timestamp != envelope.timestamp) {
OWSFailDebug(@"typingMessage has invalid timestamp.");
return;
}
2018-11-07 16:09:11 +01:00
NSString *localNumber = self.tsAccountManager.localNumber;
if ([localNumber isEqualToString:envelope.source]) {
OWSLogVerbose(@"Ignoring typing indicators from self or linked device.");
2018-11-07 16:56:40 +01:00
return;
2018-12-18 22:23:25 +01:00
} else if ([self.blockingManager isRecipientIdBlocked:envelope.source]
|| (typingMessage.hasGroupID && [self.blockingManager isGroupIdBlocked:typingMessage.groupID])) {
NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@", envelope.source];
if (typingMessage.hasGroupID) {
logMessage = [logMessage stringByAppendingFormat:@" in group: %@", typingMessage.groupID];
}
OWSLogError(@"%@", logMessage);
return;
2018-11-07 16:09:11 +01:00
}
TSThread *_Nullable thread;
if (typingMessage.hasGroupID) {
TSGroupThread *groupThread = [TSGroupThread threadWithGroupId:typingMessage.groupID transaction:transaction];
2020-01-30 05:51:46 +01:00
if (![groupThread isLocalUserInGroupWithTransaction:transaction]) {
OWSLogInfo(@"Ignoring messages for left group.");
return;
}
thread = groupThread;
} else {
thread = [TSContactThread getThreadWithContactId:envelope.source transaction:transaction];
}
if (!thread) {
// This isn't neccesarily an error. We might not yet know about the thread,
// in which case we don't need to display the typing indicators.
OWSLogWarn(@"Could not locate thread for typingMessage.");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
switch (typingMessage.action) {
case SSKProtoTypingMessageActionStarted:
[self.typingIndicators didReceiveTypingStartedMessageInThread:thread
recipientId:envelope.source
deviceId:envelope.sourceDevice];
break;
case SSKProtoTypingMessageActionStopped:
[self.typingIndicators didReceiveTypingStoppedMessageInThread:thread
recipientId:envelope.source
deviceId:envelope.sourceDevice];
break;
default:
OWSFailDebug(@"Typing message has unexpected action.");
break;
}
});
}
- (void)handleReceivedGroupAvatarUpdateWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
TSGroupThread *_Nullable groupThread =
[TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction];
if (!groupThread) {
OWSFailDebug(@"Missing group for group avatar update");
return;
}
2018-11-07 23:49:25 +01:00
TSAttachmentPointer *_Nullable avatarPointer =
[TSAttachmentPointer attachmentPointerFromProto:dataMessage.group.avatar albumMessage:nil];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-11-07 23:49:25 +01:00
if (!avatarPointer) {
OWSLogWarn(@"received unsupported group avatar envelope");
return;
}
[self.attachmentDownloads downloadAttachmentPointer:avatarPointer
2018-11-02 17:11:15 +01:00
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
[groupThread updateAvatarWithAttachmentStream:attachmentStream];
}
2017-05-10 16:05:01 +02:00
failure:^(NSError *error) {
OWSLogError(@"failed to fetch attachments for group avatar sent at: %llu. with error: %@",
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
envelope.timestamp,
error);
}];
}
- (void)handleReceivedMediaWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction];
if (!thread) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Ignoring media message for unknown group.");
return;
}
2018-12-03 22:28:01 +01:00
TSIncomingMessage *_Nullable message = [self handleReceivedEnvelope:envelope
withDataMessage:dataMessage
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-11-07 23:49:25 +01:00
if (!message) {
return;
}
2018-11-07 23:49:25 +01:00
[message saveWithTransaction:transaction];
2020-02-15 00:01:21 +01:00
OWSLogDebug(@"Incoming attachment message: %@.", message.debugDescription);
2018-11-07 23:49:25 +01:00
[self.attachmentDownloads downloadAttachmentsForMessage:message
transaction:transaction
2018-11-02 17:11:15 +01:00
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
2020-02-15 00:01:21 +01:00
OWSLogDebug(@"Successfully fetched attachments: %lu for message: %@.",
2018-11-02 17:11:15 +01:00
(unsigned long)attachmentStreams.count,
2018-11-07 23:49:25 +01:00
message);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2017-05-10 16:05:01 +02:00
failure:^(NSError *error) {
2020-02-15 00:01:21 +01:00
OWSLogError(@"Failed to fetch attachments for message: %@ with error: %@.", message, error);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}];
2015-12-07 03:31:43 +01:00
}
- (void)throws_handleIncomingEnvelope:(SSKProtoEnvelope *)envelope
withSyncMessage:(SSKProtoSyncMessage *)syncMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
2019-11-15 03:56:35 +01:00
serverID:(uint64_t)serverID
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!syncMessage) {
OWSFailDebug(@"Missing syncMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
2019-11-08 04:59:36 +01:00
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSSet<NSString *> *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction];
if (![linkedDeviceHexEncodedPublicKeys contains:^BOOL(NSString *hexEncodedPublicKey) {
return [hexEncodedPublicKey isEqual:envelope.source];
}]) {
OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorSyncMessageFromUnknownSource], envelope);
return;
}
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction];
BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source];
if (syncMessage.sent) {
OWSIncomingSentMessageTranscript *transcript =
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction];
SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message;
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
2019-12-02 01:01:07 +01:00
// Loki: Try to update using the provided profile
if (wasSentByMasterDevice) {
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey transaction:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:masterHexEncodedPublicKey];
}
NSString *destination = syncMessage.sent.destination;
if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) {
// If we observe a linked device sending our profile key to another
// user, we can infer that that user belongs in our profile whitelist.
if (dataMessage.group) {
[self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id];
} else {
[self.profileManager addUserToProfileWhitelist:destination];
}
}
if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:0
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.dbConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *transaction) {
TSGroupThread *_Nullable groupThread =
[TSGroupThread threadWithGroupId:dataMessage.group.id
transaction:transaction];
if (!groupThread) {
OWSFailDebug(@"ignoring sync group avatar update for unknown group.");
return;
2019-11-27 06:26:15 +01:00
}
[groupThread updateAvatarWithAttachmentStream:attachmentStream
transaction:transaction];
}];
2019-11-27 06:26:15 +01:00
}
transaction:transaction];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
} else {
2020-01-30 05:51:46 +01:00
if (transcript.isGroupUpdate) {
// TODO: This code is pretty much a duplicate of the code in OWSRecordTranscriptJob
TSGroupThread *newGroupThread = [TSGroupThread getOrCreateThreadWithGroupId:transcript.dataMessage.group.id groupType:closedGroup transaction:transaction];
TSGroupModel *newGroupModel = [[TSGroupModel alloc] initWithTitle:transcript.dataMessage.group.name
memberIds:transcript.dataMessage.group.members
image:nil
groupId:transcript.dataMessage.group.id
groupType:closedGroup
adminIds:transcript.dataMessage.group.admins];
NSString *updateMessage = [newGroupThread.groupModel getInfoStringAboutUpdateTo:newGroupModel contactsManager:self.contactsManager];
newGroupThread.groupModel = newGroupModel;
[newGroupThread saveWithTransaction:transaction];
// Loki: Try to establish sessions with all members when a group is created or updated
[self establishSessionsWithMembersIfNeeded: transcript.dataMessage.group.members forThread:newGroupThread transaction:transaction];
[[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:transcript.dataMessage.expireTimer
thread:newGroupThread
createdByRemoteRecipientId:nil
createdInExistingGroup:YES
transaction:transaction];
TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:newGroupThread
messageType:TSInfoMessageTypeGroupUpdate
customMessage:updateMessage];
[infoMessage saveWithTransaction:transaction];
} else if (transcript.isGroupQuit) {
TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupId:transcript.dataMessage.group.id groupType:closedGroup transaction:transaction];
[groupThread leaveGroupWithTransaction:transaction];
TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:groupThread
messageType:TSInfoMessageTypeGroupQuit
customMessage:NSLocalizedString(@"GROUP_YOU_LEFT", nil)];
[infoMessage saveWithTransaction:transaction];
2020-01-30 05:51:46 +01:00
} else {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
serverID:(serverID ?: 0)
attachmentHandler:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogDebug(@"successfully fetched transcript attachments: %lu",
(unsigned long)attachmentStreams.count);
}
transaction:transaction];
2019-11-27 06:26:15 +01:00
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
} else if (syncMessage.request) {
if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) {
// We respond asynchronously because populating the sync message will
// create transactions and it's not practical (due to locking in the OWSIdentityManager)
// to plumb our transaction through.
//
// In rare cases this means we won't respond to the sync request, but that's
// acceptable.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[self.syncManager syncAllContacts] retainUntilComplete];
});
} else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[self.syncManager syncAllGroups] retainUntilComplete];
});
} else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) {
OWSLogInfo(@"Received request for block list");
[self.blockingManager syncBlockList];
} else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeConfiguration) {
2018-10-16 22:02:37 +02:00
[SSKEnvironment.shared.syncManager sendConfigurationSyncMessage];
} else {
OWSLogWarn(@"ignoring unsupported sync request message");
}
} else if (syncMessage.blocked) {
NSArray<NSString *> *blockedPhoneNumbers = [syncMessage.blocked.numbers copy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2018-05-11 16:36:40 +02:00
[self.blockingManager setBlockedPhoneNumbers:blockedPhoneNumbers sendSyncMessage:NO];
});
2016-09-01 16:28:35 +02:00
} else if (syncMessage.read.count > 0) {
2018-08-30 16:31:01 +02:00
OWSLogInfo(@"Received %lu read receipt(s)", (unsigned long)syncMessage.read.count);
[OWSReadReceiptManager.sharedManager processReadReceiptsFromLinkedDevice:syncMessage.read
readTimestamp:envelope.timestamp
transaction:transaction];
} else if (syncMessage.verified) {
OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination);
[self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction];
} else if (syncMessage.contacts != nil) {
2019-12-02 01:01:07 +01:00
if (wasSentByMasterDevice && syncMessage.contacts.data.length > 0) {
2020-02-28 03:58:45 +01:00
[LKLogger print:@"[Loki] Received contact sync message."];
NSData *data = syncMessage.contacts.data;
ContactParser *parser = [[ContactParser alloc] initWithData:data];
NSArray<NSString *> *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys];
// Try to establish sessions
for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus;
switch (friendRequestStatus) {
case LKThreadFriendRequestStatusNone: {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
2020-02-28 03:58:45 +01:00
LKFriendRequestMessage *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey transaction:transaction];
thread.isForceHidden = true;
[thread saveWithTransaction:transaction];
[messageSender sendMessage:automatedFriendRequestMessage
success:^{
[automatedFriendRequestMessage remove];
thread.isForceHidden = false;
[thread save];
}
failure:^(NSError *error) {
[automatedFriendRequestMessage remove];
thread.isForceHidden = false;
[thread save];
}];
break;
}
case LKThreadFriendRequestStatusRequestReceived: {
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction];
// The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread]
LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction];
break;
}
default: break; // Do nothing
}
}
}
2020-02-12 06:31:37 +01:00
} else if (syncMessage.groups != nil) {
if (wasSentByMasterDevice && syncMessage.groups.data.length > 0) {
2020-02-28 03:58:45 +01:00
[LKLogger print:@"[Loki] Received group sync message."];
2020-02-12 06:31:37 +01:00
NSData *data = syncMessage.groups.data;
2020-02-12 07:08:27 +01:00
GroupParser *parser = [[GroupParser alloc] initWithData:data];
2020-02-12 07:14:52 +01:00
NSArray<TSGroupModel *> *groupModels = [parser parseGroupModels];
for (TSGroupModel *groupModel in groupModels) {
TSGroupThread *thread = [TSGroupThread threadWithGroupId:groupModel.groupId transaction:transaction];
if (thread == nil) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
[thread saveWithTransaction:transaction];
[self establishSessionsWithMembersIfNeeded:groupModel.groupMemberIds forThread:thread transaction:transaction];
TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:thread
messageType:TSInfoMessageTypeGroupUpdate
customMessage:@"You have joined the group."];
[infoMessage saveWithTransaction:transaction];
}
2020-02-12 07:14:52 +01:00
}
2020-02-12 06:31:37 +01:00
}
} else if (syncMessage.openGroups != nil) {
if (wasSentByMasterDevice && syncMessage.openGroups.count > 0) {
2020-03-31 07:34:31 +02:00
[LKLogger print:@"[Loki] Received open group sync message."];
for (SSKProtoSyncMessageOpenGroups* openGroup in syncMessage.openGroups) {
2020-02-25 06:19:56 +01:00
[LKPublicChatManager.shared addChatWithServer:openGroup.url channel:openGroup.channel];
}
}
} else {
OWSLogWarn(@"Ignoring unsupported sync message.");
}
}
- (void)handleEndSessionMessageWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
[self resetSessionWithContact:envelope.source transaction:transaction];
}
- (void)resetSessionWithContact:(NSString *)hexEncodedPublicKey
transaction:(YapDatabaseReadWriteTransaction *)transaction {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// MJK TODO - safe to remove senderTimestamp
2019-05-20 03:20:03 +02:00
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
inThread:thread
2019-05-20 03:20:03 +02:00
messageType:TSInfoMessageTypeLokiSessionResetInProgress] saveWithTransaction:transaction];
2019-06-13 03:06:05 +02:00
// Loki: Archive all our sessions
2019-05-22 05:09:01 +02:00
// Ref: SignalServiceKit/Loki/Docs/SessionReset.md
[self.primaryStorage archiveAllSessionsForContact:hexEncodedPublicKey protocolContext:transaction];
2019-05-22 05:09:01 +02:00
// Loki: Set our session reset state
2020-02-17 01:24:16 +01:00
thread.sessionResetStatus = LKSessionResetStatusRequestReceived;
[thread saveWithTransaction:transaction];
2019-05-22 05:09:01 +02:00
// Loki: Send an empty message to trigger the session reset code for both parties
2019-05-27 05:11:25 +02:00
LKEphemeralMessage *emptyMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:emptyMessage transaction:transaction];
2020-03-31 07:34:31 +02:00
[LKLogger print:[NSString stringWithFormat:@"[Loki] Session reset received from %@.", hexEncodedPublicKey]];
2015-12-07 03:31:43 +01:00
}
- (void)handleExpirationTimerUpdateMessageWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction];
if (!thread) {
OWSFailDebug(@"ignoring expiring messages update for unknown group.");
return;
}
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration;
if (dataMessage.hasExpireTimer && dataMessage.expireTimer > 0) {
OWSLogInfo(
@"Expiring messages duration turned to %u for thread %@", (unsigned int)dataMessage.expireTimer, thread);
disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:dataMessage.expireTimer];
} else {
OWSLogInfo(@"Expiring messages have been turned off for thread %@", thread);
disappearingMessagesConfiguration = [[OWSDisappearingMessagesConfiguration alloc]
initWithThreadId:thread.uniqueId
enabled:NO
durationSeconds:OWSDisappearingMessagesConfigurationDefaultExpirationDuration];
}
OWSAssertDebug(disappearingMessagesConfiguration);
[disappearingMessagesConfiguration saveWithTransaction:transaction];
NSString *name = [dataMessage.profile displayName] ?: [self.contactsManager displayNameForPhoneIdentifier:envelope.source transaction:transaction];
// MJK TODO - safe to remove senderTimestamp
OWSDisappearingConfigurationUpdateInfoMessage *message =
[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:name
createdInExistingGroup:NO];
[message saveWithTransaction:transaction];
2015-12-07 03:31:43 +01:00
}
- (void)handleProfileKeyMessageWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
NSString *recipientId = envelope.source;
if (!dataMessage.hasProfileKey) {
OWSFailDebug(@"received profile key message without profile key from: %@", envelopeAddress(envelope));
return;
}
NSData *profileKey = dataMessage.profileKey;
if (profileKey.length != kAES256_KeyByteLength) {
2018-09-28 16:56:53 +02:00
OWSFailDebug(@"received profile key of unexpected length: %lu, from: %@",
(unsigned long)profileKey.length,
envelopeAddress(envelope));
return;
}
if (dataMessage.profile == nil) {
OWSFailDebug(@"received profile key message without loki profile attached from: %@", envelopeAddress(envelope));
return;
}
2018-09-17 15:27:58 +02:00
id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
[profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:dataMessage.profile.profilePicture];
}
2019-11-20 06:27:34 +01:00
- (void)handleUnlinkDeviceMessageWithEnvelope:(SSKProtoEnvelope *)envelope dataMessage:(SSKProtoDataMessage *)dataMessage transaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *senderHexEncodedPublicKey = envelope.source;
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction];
if (![masterHexEncodedPublicKey isEqual:senderHexEncodedPublicKey]) { return; }
NSSet<LKDeviceLink *> *deviceLinks = [LKDatabaseUtilities getDeviceLinksFor:senderHexEncodedPublicKey in:transaction];
if (![deviceLinks contains:^BOOL(LKDeviceLink *deviceLink) {
return [deviceLink.master.hexEncodedPublicKey isEqual:senderHexEncodedPublicKey] && [deviceLink.slave.hexEncodedPublicKey isEqual:userHexEncodedPublicKey];
}]) {
return;
}
[LKFileServerAPI getDeviceLinksAssociatedWith:userHexEncodedPublicKey].thenOn(dispatch_get_main_queue(), ^(NSSet<LKDeviceLink *> *deviceLinks) {
2019-11-21 00:10:34 +01:00
if ([deviceLinks contains:^BOOL(LKDeviceLink *deviceLink) {
2019-11-20 06:27:34 +01:00
return [deviceLink.master.hexEncodedPublicKey isEqual:senderHexEncodedPublicKey] && [deviceLink.slave.hexEncodedPublicKey isEqual:userHexEncodedPublicKey];
}]) {
[NSUserDefaults.standardUserDefaults setBool:YES forKey:@"wasUnlinked"];
2019-11-21 00:10:34 +01:00
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.dataNukeRequested object:nil];
2019-11-20 06:27:34 +01:00
}
});
}
- (void)handleReceivedTextMessageWithEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
2018-12-02 23:30:31 +01:00
[self handleReceivedEnvelope:envelope
withDataMessage:dataMessage
wasReceivedByUD:wasReceivedByUD
transaction:transaction];
2015-12-07 03:31:43 +01:00
}
- (void)handleGroupInfoRequest:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
2017-05-10 16:05:01 +02:00
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return;
}
if (dataMessage.group.type != SSKProtoGroupContextTypeRequestInfo) {
OWSFailDebug(@"Unexpected group message type.");
return;
}
2017-05-10 16:05:01 +02:00
NSData *groupId = dataMessage.group ? dataMessage.group.id : nil;
2017-05-10 16:05:01 +02:00
if (!groupId) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"Group info request is missing group id.");
2017-05-10 16:05:01 +02:00
return;
}
2018-08-30 16:31:01 +02:00
OWSLogInfo(@"Received 'Request Group Info' message for group: %@ from: %@", groupId, envelope.source);
2017-05-10 16:05:01 +02:00
TSGroupThread *_Nullable gThread = [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction];
if (!gThread) {
OWSLogWarn(@"Unknown group: %@", groupId);
return;
}
// Ensure sender is in the group.
if (![gThread isUserInGroup:envelope.source transaction:transaction]) {
OWSLogWarn(@"Ignoring 'Request Group Info' message for non-member of group. %@ not in %@",
envelope.source,
gThread.groupModel.groupMemberIds);
return;
}
// Ensure we are in the group.
2020-01-30 05:51:46 +01:00
if (![gThread isLocalUserInGroupWithTransaction:transaction]) {
OWSLogWarn(@"Ignoring 'Request Group Info' message for group we no longer belong to.");
return;
}
2017-05-10 16:05:01 +02:00
NSString *updateGroupInfo =
[gThread.groupModel getInfoStringAboutUpdateTo:gThread.groupModel contactsManager:self.contactsManager];
uint32_t expiresInSeconds = [gThread disappearingMessagesDurationWithTransaction:transaction];
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:gThread
2018-08-31 18:43:05 +02:00
groupMetaMessage:TSGroupMetaMessageUpdate
expiresInSeconds:expiresInSeconds];
[message updateWithCustomMessage:updateGroupInfo transaction:transaction];
// Only send this group update to the requester.
2018-04-24 22:38:35 +02:00
[message updateWithSendingToSingleGroupRecipient:envelope.source transaction:transaction];
if (gThread.groupModel.groupImage) {
2019-01-07 15:57:19 +01:00
NSData *_Nullable data = UIImagePNGRepresentation(gThread.groupModel.groupImage);
OWSAssertDebug(data);
if (data) {
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"];
[self.messageSenderJobQueue addMediaMessage:message
dataSource:dataSource
contentType:OWSMimeTypeImagePng
sourceFilename:nil
caption:nil
albumMessageId:nil
isTemporaryAttachment:YES];
}
} else {
[self.messageSenderJobQueue addMessage:message transaction:transaction];
}
2017-05-10 16:05:01 +02:00
}
- (TSIncomingMessage *_Nullable)handleReceivedEnvelope:(SSKProtoEnvelope *)envelope
withDataMessage:(SSKProtoDataMessage *)dataMessage
2018-12-02 23:30:31 +01:00
wasReceivedByUD:(BOOL)wasReceivedByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return nil;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return nil;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return nil;
}
uint64_t timestamp = envelope.timestamp;
NSString *body = dataMessage.body;
NSData *groupId = dataMessage.group ? dataMessage.group.id : nil;
2018-07-13 20:03:28 +02:00
OWSContact *_Nullable contact = [OWSContacts contactForDataMessage:dataMessage transaction:transaction];
2018-10-02 20:34:26 +02:00
NSNumber *_Nullable serverTimestamp = (envelope.hasServerTimestamp ? @(envelope.serverTimestamp) : nil);
2015-12-07 03:31:43 +01:00
if (dataMessage.group.type == SSKProtoGroupContextTypeRequestInfo) {
[self handleGroupInfoRequest:envelope dataMessage:dataMessage transaction:transaction];
2017-05-10 16:05:01 +02:00
return nil;
}
2020-02-15 00:01:21 +01:00
// The envelope source is set during UD decryption.
2020-02-19 01:11:07 +01:00
if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil) { // Handled in LokiPublicChatPoller for open group messages
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[LKAPI getDestinationsFor:envelope.source inTransaction:transaction].ensureOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
dispatch_semaphore_signal(semaphore);
}).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) {
dispatch_semaphore_signal(semaphore);
}) retainUntilComplete];
2020-02-25 10:32:05 +01:00
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
}
if (groupId.length > 0) {
NSMutableSet *newMemberIds = [NSMutableSet setWithArray:dataMessage.group.members];
NSMutableSet *removedMemberIds = [NSMutableSet new];
2020-01-29 04:58:28 +01:00
for (NSString *recipientId in newMemberIds) {
if (![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:recipientId]) {
OWSLogVerbose(
@"incoming group update has invalid group member: %@", [self descriptionForEnvelope:envelope]);
OWSFailDebug(@"incoming group update has invalid group member");
return nil;
}
}
NSString *hexEncodedPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source);
// Group messages create the group if it doesn't already exist.
//
// We distinguish between the old group state (if any) and the new group state.
TSGroupThread *_Nullable oldGroupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction];
if (oldGroupThread) {
2020-02-15 00:01:21 +01:00
// Loki: Determine removed members
removedMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds];
[removedMemberIds minusSet:newMemberIds];
[removedMemberIds removeObject:hexEncodedPublicKey];
}
// Only set the display name here, the logic for updating profile pictures is handled when we're setting profile key
[self handleProfileNameUpdateIfNeeded:dataMessage recipientId:hexEncodedPublicKey transaction:transaction];
2019-11-18 05:59:50 +01:00
switch (dataMessage.group.type) {
case SSKProtoGroupContextTypeUpdate: {
2020-02-28 03:58:45 +01:00
if (oldGroupThread && ![oldGroupThread isUserAdminInGroup:hexEncodedPublicKey transaction:transaction]) {
2020-01-29 04:58:28 +01:00
[LKLogger print:[NSString stringWithFormat:@"[Loki] Received a group update from a non-admin user for %@; ignoring.", [LKGroupUtilities getEncodedGroupID:groupId]]];
return nil;
}
// Ensures that the thread exists but doesn't update it.
TSGroupThread *newGroupThread =
[TSGroupThread getOrCreateThreadWithGroupId:groupId groupType:oldGroupThread.groupModel.groupType transaction:transaction];
TSGroupModel *newGroupModel = [[TSGroupModel alloc] initWithTitle:dataMessage.group.name
memberIds:newMemberIds.allObjects
2017-10-04 16:06:38 +02:00
image:oldGroupThread.groupModel.groupImage
groupId:dataMessage.group.id
groupType:oldGroupThread.groupModel.groupType
adminIds:dataMessage.group.admins];
newGroupModel.removedMembers = removedMemberIds;
NSString *updateGroupInfo = [newGroupThread.groupModel getInfoStringAboutUpdateTo:newGroupModel
contactsManager:self.contactsManager];
newGroupThread.groupModel = newGroupModel;
[newGroupThread saveWithTransaction:transaction];
2020-01-29 04:58:28 +01:00
// Loki: Try to establish sessions with all members when a group is created or updated
[self establishSessionsWithMembersIfNeeded: newMemberIds.allObjects forThread:newGroupThread transaction:transaction];
[[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer
thread:newGroupThread
createdByRemoteRecipientId:nil
createdInExistingGroup:YES
transaction:transaction];
// MJK TODO - should be safe to remove senderTimestamp
TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:newGroupThread
messageType:TSInfoMessageTypeGroupUpdate
customMessage:updateGroupInfo];
[infoMessage saveWithTransaction:transaction];
2017-10-04 16:06:38 +02:00
return nil;
}
case SSKProtoGroupContextTypeQuit: {
if (!oldGroupThread) {
2018-09-18 22:44:31 +02:00
OWSLogWarn(@"ignoring quit group message from unknown group.");
return nil;
}
newMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds];
[newMemberIds removeObject:hexEncodedPublicKey];
oldGroupThread.groupModel.groupMemberIds = [newMemberIds.allObjects mutableCopy];
[oldGroupThread saveWithTransaction:transaction];
2018-10-26 19:21:08 +02:00
NSString *nameString =
[self.contactsManager displayNameForPhoneIdentifier:hexEncodedPublicKey transaction:transaction];
NSString *updateGroupInfo =
[NSString stringWithFormat:NSLocalizedString(@"GROUP_MEMBER_LEFT", @""), nameString];
// MJK TODO - should be safe to remove senderTimestamp
[[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:oldGroupThread
messageType:TSInfoMessageTypeGroupUpdate
customMessage:updateGroupInfo] saveWithTransaction:transaction];
2017-10-04 16:06:38 +02:00
return nil;
}
case SSKProtoGroupContextTypeDeliver: {
if (!oldGroupThread) {
OWSFailDebug(@"ignoring deliver group message from unknown group.");
return nil;
}
[[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer
thread:oldGroupThread
createdByRemoteRecipientId:hexEncodedPublicKey
createdInExistingGroup:NO
transaction:transaction];
2018-04-07 21:26:22 +02:00
TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage
thread:oldGroupThread
transaction:transaction];
2019-01-15 16:36:21 +01:00
NSError *linkPreviewError;
2019-01-14 17:00:24 +01:00
OWSLinkPreview *_Nullable linkPreview =
2019-01-14 22:44:18 +01:00
[OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage
body:body
2019-01-15 16:36:21 +01:00
transaction:transaction
error:&linkPreviewError];
if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) {
OWSLogError(@"linkPreviewError: %@", linkPreviewError);
}
2019-01-14 17:00:24 +01:00
OWSLogDebug(@"incoming message from: %@ for group: %@ with timestamp: %lu",
envelopeAddress(envelope),
groupId,
(unsigned long)timestamp);
// Legit usage of senderTimestamp when creating an incoming group message record
2017-10-04 16:06:38 +02:00
TSIncomingMessage *incomingMessage =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp
inThread:oldGroupThread
authorId:hexEncodedPublicKey
sourceDeviceId:envelope.sourceDevice
messageBody:body
attachmentIds:@[]
expiresInSeconds:dataMessage.expireTimer
quotedMessage:quotedMessage
2018-10-02 20:34:26 +02:00
contactShare:contact
2019-01-14 17:00:24 +01:00
linkPreview:linkPreview
serverTimestamp:serverTimestamp
wasReceivedByUD:wasReceivedByUD];
2019-05-27 07:06:54 +02:00
2019-10-31 06:52:22 +01:00
// Loki: Parse Loki specific properties if needed
2019-05-27 07:06:54 +02:00
if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; }
2019-10-31 06:52:22 +01:00
if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { incomingMessage.groupChatServerID = dataMessage.publicChatInfo.serverID; }
2018-11-07 23:49:25 +01:00
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments
albumMessage:incomingMessage];
for (TSAttachmentPointer *pointer in attachmentPointers) {
[pointer saveWithTransaction:transaction];
[incomingMessage.attachmentIds addObject:pointer.uniqueId];
}
2019-09-10 05:26:58 +02:00
// Loki: Don't process friend requests in group chats
2018-11-07 23:49:25 +01:00
if (body.length == 0 && attachmentPointers.count < 1 && !contact) {
2020-02-15 00:01:21 +01:00
OWSLogWarn(@"Ignoring empty incoming message from: %@ for group: %@ with timestamp: %lu.",
hexEncodedPublicKey,
2018-11-07 23:49:25 +01:00
groupId,
(unsigned long)timestamp);
return nil;
}
2019-10-09 02:16:10 +02:00
2019-10-08 06:02:03 +02:00
// Loki: Cache the user hex encoded public key (for mentions)
2019-11-13 03:23:55 +01:00
dispatch_async(dispatch_get_main_queue(), ^{
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[LKAPI populateUserHexEncodedPublicKeyCacheIfNeededFor:oldGroupThread.uniqueId in:transaction];
[LKAPI cache:incomingMessage.authorId for:oldGroupThread.uniqueId];
}];
2019-11-13 03:12:25 +01:00
});
2019-10-08 06:02:03 +02:00
2017-10-04 16:06:38 +02:00
[self finalizeIncomingMessage:incomingMessage
thread:oldGroupThread
masterThread:oldGroupThread
2017-10-04 16:06:38 +02:00
envelope:envelope
transaction:transaction];
2019-08-30 07:57:34 +02:00
2019-10-08 06:02:03 +02:00
// Loki: Map the message ID to the message server ID if needed
2019-08-30 07:57:34 +02:00
if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) {
[self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction];
}
2017-10-04 16:06:38 +02:00
return incomingMessage;
}
default: {
2020-02-15 00:01:21 +01:00
OWSLogWarn(@"Ignoring unknown group message type: %d.", (int)dataMessage.group.type);
return nil;
}
}
} else {
2019-10-04 06:52:59 +02:00
2020-02-15 00:01:21 +01:00
// Loki: A message from a slave device should appear as if it came from the master device; the underlying
// friend request logic, however, should still be specific to the slave device.
2019-11-06 06:09:20 +01:00
// Loki: Get the master hex encoded public key and thread
NSString *hexEncodedPublicKey = envelope.source;
NSString *masterHexEncodedPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source);
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
TSContactThread *masterThread = [TSContactThread getOrCreateThreadWithContactId:masterHexEncodedPublicKey transaction:transaction];
2017-10-04 16:06:38 +02:00
2020-02-15 00:01:21 +01:00
OWSLogDebug(@"Incoming message from: %@ with timestamp: %lu.", hexEncodedPublicKey, (unsigned long)timestamp);
[[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer
thread:masterThread
2019-10-04 06:03:38 +02:00
createdByRemoteRecipientId:hexEncodedPublicKey
createdInExistingGroup:NO
transaction:transaction];
2018-04-07 21:26:22 +02:00
TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage
thread:masterThread
2018-04-07 21:26:22 +02:00
transaction:transaction];
2019-01-15 16:36:21 +01:00
NSError *linkPreviewError;
2019-01-14 17:00:24 +01:00
OWSLinkPreview *_Nullable linkPreview =
2019-01-15 16:36:21 +01:00
[OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage
body:body
transaction:transaction
error:&linkPreviewError];
if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) {
OWSLogError(@"linkPreviewError: %@", linkPreviewError);
}
2019-01-14 17:00:24 +01:00
// Legit usage of senderTimestamp when creating incoming message from received envelope
TSIncomingMessage *incomingMessage =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp
inThread:masterThread
authorId:masterThread.contactIdentifier
sourceDeviceId:envelope.sourceDevice
messageBody:body
attachmentIds:@[]
expiresInSeconds:dataMessage.expireTimer
quotedMessage:quotedMessage
2018-10-02 20:34:26 +02:00
contactShare:contact
2019-01-14 17:00:24 +01:00
linkPreview:linkPreview
serverTimestamp:serverTimestamp
wasReceivedByUD:wasReceivedByUD];
2019-10-17 07:39:22 +02:00
2019-09-12 03:22:42 +02:00
NSString *rawDisplayName = dataMessage.profile.displayName;
2019-10-17 07:39:22 +02:00
NSString *displayName = nil;
2019-09-12 03:22:42 +02:00
if (rawDisplayName != nil && rawDisplayName.length > 0) {
2019-10-17 07:39:22 +02:00
displayName = [NSString stringWithFormat:@"%@ (...%@)", rawDisplayName, [incomingMessage.authorId substringFromIndex:incomingMessage.authorId.length - 8]];
}
[self.profileManager updateProfileForContactWithID:thread.contactIdentifier displayName:displayName with:transaction];
[self handleProfileKeyUpdateIfNeeded:dataMessage recipientId:thread.contactIdentifier];
2019-11-18 05:59:50 +01:00
2019-10-31 06:52:22 +01:00
// Loki: Parse Loki specific properties if needed
2019-05-27 07:06:54 +02:00
if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; }
2018-11-07 23:49:25 +01:00
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments albumMessage:incomingMessage];
for (TSAttachmentPointer *pointer in attachmentPointers) {
[pointer saveWithTransaction:transaction];
[incomingMessage.attachmentIds addObject:pointer.uniqueId];
}
// Loki: Do this before the check below
2019-10-31 04:24:53 +01:00
[self handleFriendRequestMessageIfNeededWithEnvelope:envelope data:dataMessage message:incomingMessage thread:thread transaction:transaction];
2018-11-07 23:49:25 +01:00
if (body.length == 0 && attachmentPointers.count < 1 && !contact) {
2020-02-15 00:01:21 +01:00
OWSLogWarn(@"Ignoring empty incoming message from: %@ with timestamp: %lu.",
2019-10-04 06:03:38 +02:00
hexEncodedPublicKey,
2018-11-07 23:49:25 +01:00
(unsigned long)timestamp);
return nil;
}
2019-05-27 01:50:37 +02:00
// Loki: If we received a message from a contact in the last 2 minutes that wasn't P2P, then we need to ping them.
// We assume this occurred because they don't have our P2P details.
2019-10-04 06:03:38 +02:00
if (!envelope.isPtpMessage && hexEncodedPublicKey != nil) {
2019-05-27 01:50:37 +02:00
uint64_t timestamp = envelope.timestamp;
uint64_t now = NSDate.ows_millisecondTimeStamp;
uint64_t ageInSeconds = (now - timestamp) / 1000;
2019-10-04 06:03:38 +02:00
if (ageInSeconds <= 120) { [LKP2PAPI pingContact:hexEncodedPublicKey]; }
2019-05-27 01:50:37 +02:00
}
2018-11-07 23:49:25 +01:00
2017-10-04 16:06:38 +02:00
[self finalizeIncomingMessage:incomingMessage
thread:thread
masterThread:thread
2017-10-04 16:06:38 +02:00
envelope:envelope
transaction:transaction];
2019-09-09 08:51:18 +02:00
2017-10-04 16:06:38 +02:00
return incomingMessage;
}
2017-10-04 16:06:38 +02:00
}
2015-12-07 03:31:43 +01:00
- (void)handleProfileNameUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction {
2019-12-02 01:01:07 +01:00
if (dataMessage != nil && dataMessage.profile != nil) {
[self.profileManager updateProfileForContactWithID:recipientId displayName:dataMessage.profile.displayName with:transaction];
}
}
- (void)handleProfileKeyUpdateIfNeeded:(SSKProtoDataMessage *)dataMessage recipientId:(NSString *)recipientId {
2019-12-02 01:01:07 +01:00
if (dataMessage != nil && [dataMessage hasProfileKey]) {
NSData *profileKey = [dataMessage profileKey];
NSString *url = dataMessage.profile != nil ? dataMessage.profile.profilePicture : nil;
if (profileKey.length == kAES256_KeyByteLength) {
[self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:url];
} else {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Unexpected profile key length:%lu on message from:%@", (unsigned long)profileKey.length, recipientId);
}
}
}
2020-01-30 01:12:11 +01:00
- (void)establishSessionsWithMembersIfNeeded:(NSArray *)members forThread:(TSGroupThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
for (NSString *member in members) {
if ([member isEqualToString:userHexEncodedPublicKey] ) { continue; }
2020-01-30 01:12:11 +01:00
BOOL hasSession = [self.primaryStorage containsSession:member deviceId:1 protocolContext:transaction];
2020-01-29 04:58:28 +01:00
if (hasSession) { continue; }
2020-01-30 01:12:11 +01:00
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:member transaction:transaction];
LKSessionRequestMessage *message = [[LKSessionRequestMessage alloc] initWithThread:contactThread];
2020-01-29 04:58:28 +01:00
[self.messageSenderJobQueue addMessage:message transaction:transaction];
}
}
- (BOOL)canFriendRequestBeAutoAcceptedForThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2019-11-07 01:08:32 +01:00
NSString *senderHexEncodedPublicKey = thread.contactIdentifier;
2019-07-24 01:20:33 +02:00
if (thread.hasCurrentUserSentFriendRequest) {
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
// and send a friend request accepted message back to Bob. We don't check that sending the
// friend request accepted message succeeded. Even if it doesn't, the thread's current friend
// request status will be set to LKThreadFriendRequestStatusFriends for Alice making it possible
// for Alice to send messages to Bob. When Bob receives a message, his thread's friend request status
// will then be set to LKThreadFriendRequestStatusFriends. If we do check for a successful send
// before updating Alice's thread's friend request status to LKThreadFriendRequestStatusFriends,
// we can end up in a deadlock where both users' threads' friend request statuses are
// LKThreadFriendRequestStatusRequestSent.
return YES;
}
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
2019-11-07 01:08:32 +01:00
NSSet<NSString *> *userLinkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction];
if ([userLinkedDeviceHexEncodedPublicKeys containsObject:senderHexEncodedPublicKey]) {
// Auto-accept any friend requests from the user's own linked devices
return YES;
}
2019-11-07 01:08:32 +01:00
NSSet<TSContactThread *> *senderLinkedDeviceThreads = [LKDatabaseUtilities getLinkedDeviceThreadsFor:senderHexEncodedPublicKey in:transaction];
2019-11-07 01:59:11 +01:00
if ([senderLinkedDeviceThreads contains:^BOOL(TSContactThread *thread) {
return thread.isContactFriend;
}]) {
// Auto-accept if the user is friends with any of the sender's linked devices.
return YES;
}
return NO;
}
- (void)handleFriendRequestMessageIfNeededWithEnvelope:(SSKProtoEnvelope *)envelope data:(SSKProtoDataMessage *)data message:(TSIncomingMessage *)message thread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction {
if (envelope.isGroupChatMessage) {
2020-03-31 07:34:31 +02:00
return [LKLogger print:@"[Loki] Ignoring friend request in group chat."];
}
2020-02-15 00:01:21 +01:00
// The envelope type is set during UD decryption.
if (envelope.type != SSKProtoEnvelopeTypeFriendRequest) {
2020-03-31 07:34:31 +02:00
return [LKLogger print:@"[Loki] Ignoring friend request logic for non friend request type envelope."];
}
if ([self canFriendRequestBeAutoAcceptedForThread:thread transaction:transaction]) {
2019-05-24 08:25:25 +02:00
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction];
__block TSOutgoingMessage *existingFriendRequestMessage;
[thread enumerateInteractionsWithTransaction:transaction usingBlock:^(TSInteraction *interaction, YapDatabaseReadTransaction *transaction) {
if ([interaction isKindOfClass:TSOutgoingMessage.class] && ((TSOutgoingMessage *)interaction).isFriendRequest) {
existingFriendRequestMessage = (TSOutgoingMessage *)interaction;
}
}];
if (existingFriendRequestMessage != nil) {
2019-05-24 08:25:25 +02:00
[existingFriendRequestMessage saveFriendRequestStatus:LKMessageFriendRequestStatusAccepted withTransaction:transaction];
}
2019-09-24 06:20:22 +02:00
// The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread]
LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread];
[self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction];
} else if (!thread.isContactFriend) {
// Checking that the sender of the message isn't already a friend is necessary because otherwise
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his
// friend request status is reset to LKThreadFriendRequestStatusNone. Bob now sends Alice a friend
// request. Alice's thread's friend request status is reset to
// LKThreadFriendRequestStatusRequestReceived.
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestReceived withTransaction:transaction];
// Except for the message.friendRequestStatus = LKMessageFriendRequestStatusPending line below, all of this is to ensure that
// there's only ever one message with status LKMessageFriendRequestStatusPending in a thread (where a thread is the combination
// of all threads belonging to the linked devices of a user).
NSString *senderID = ((TSIncomingMessage *)message).authorId;
2019-11-07 04:28:55 +01:00
NSSet<TSContactThread *> *linkedDeviceThreads = [LKDatabaseUtilities getLinkedDeviceThreadsFor:senderID in:transaction];
2019-11-07 01:08:32 +01:00
for (TSContactThread *thread in linkedDeviceThreads) {
[thread enumerateInteractionsWithTransaction:transaction usingBlock:^(TSInteraction *interaction, YapDatabaseReadTransaction *transaction) {
if (![interaction isKindOfClass:TSIncomingMessage.class]) { return; }
TSIncomingMessage *message = (TSIncomingMessage *)interaction;
if (message.friendRequestStatus != LKMessageFriendRequestStatusNone) {
[message saveFriendRequestStatus:LKMessageFriendRequestStatusNone withTransaction:transaction];
}
}];
2019-10-31 04:24:53 +01:00
}
message.friendRequestStatus = LKMessageFriendRequestStatusPending; // Don't save yet. This is done in finalizeIncomingMessage:thread:masterThread:envelope:transaction.
2019-07-24 01:20:33 +02:00
}
}
- (void)handleFriendRequestAcceptanceIfNeededWithEnvelope:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction {
// If we get an envelope that isn't a friend request, then we can infer that we had to use
// Signal cipher decryption and thus that we have a session with the other person.
2020-02-15 00:01:21 +01:00
// The envelope type is set during UD decryption.
if (envelope.isGroupChatMessage || envelope.type == SSKProtoEnvelopeTypeFriendRequest) return;
2019-10-31 06:52:22 +01:00
// Currently this uses `envelope.source` but with sync messages we'll need to use the message sender ID
2019-07-24 01:20:33 +02:00
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction];
2020-01-29 04:58:28 +01:00
// We shouldn't be able to skip from none to friends under normal circumstances
if (thread.friendRequestStatus == LKThreadFriendRequestStatusNone) { return; }
2019-07-24 01:20:33 +02:00
// Become happy friends and go on great adventures
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction];
2020-02-14 03:21:26 +01:00
TSOutgoingMessage *existingFriendRequestMessage = [[thread getLastInteractionWithTransaction:transaction] as:TSOutgoingMessage.class];
2020-02-04 07:19:44 +01:00
if (existingFriendRequestMessage != nil && existingFriendRequestMessage.isFriendRequest) {
[existingFriendRequestMessage saveFriendRequestStatus:LKMessageFriendRequestStatusAccepted withTransaction:transaction];
}
2019-07-24 01:20:33 +02:00
// Send our P2P details
LKAddressMessage *_Nullable onlineMessage = [LKP2PAPI onlineBroadcastMessageForThread:thread];
if (onlineMessage != nil) {
[self.messageSenderJobQueue addMessage:onlineMessage transaction:transaction];
2019-05-16 04:08:37 +02:00
}
}
- (void)finalizeIncomingMessage:(TSIncomingMessage *)incomingMessage
thread:(TSThread *)thread
masterThread:(TSThread *)masterThread
envelope:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return;
}
if (!thread) {
OWSFailDebug(@"Missing thread.");
return;
}
if (!incomingMessage) {
OWSFailDebug(@"Missing incomingMessage.");
return;
}
if (!transaction) {
OWSFail(@"Missing transaction.");
return;
}
2019-05-16 04:08:37 +02:00
2017-10-04 16:06:38 +02:00
[incomingMessage saveWithTransaction:transaction];
2019-05-20 05:01:04 +02:00
2019-10-31 06:52:22 +01:00
// Loki: Remove any old incoming messages
2019-05-20 05:01:04 +02:00
if (incomingMessage.isFriendRequest) {
2019-05-24 08:23:27 +02:00
[thread removeOldIncomingFriendRequestMessagesIfNeededWithTransaction:transaction];
2019-05-20 05:01:04 +02:00
}
// Any messages sent from the current user - from this device or another - should be automatically marked as read.
if ([(masterThread.contactIdentifier ?: envelope.source) isEqualToString:self.tsAccountManager.localNumber]) {
2017-10-04 16:06:38 +02:00
// Don't send a read receipt for messages sent by ourselves.
[incomingMessage markAsReadAtTimestamp:envelope.timestamp sendReadReceipt:NO transaction:transaction];
2017-10-04 16:06:38 +02:00
}
2019-01-22 17:45:00 +01:00
// Download the "non-message body" attachments.
NSMutableArray<NSString *> *otherAttachmentIds = [incomingMessage.allAttachmentIds mutableCopy];
if (incomingMessage.attachmentIds) {
[otherAttachmentIds removeObjectsInArray:incomingMessage.attachmentIds];
2019-01-17 17:56:21 +01:00
}
for (NSString *attachmentId in otherAttachmentIds) {
2019-01-28 16:28:26 +01:00
TSAttachment *_Nullable attachment =
[TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
if (![attachment isKindOfClass:[TSAttachmentPointer class]]) {
OWSLogInfo(@"Skipping attachment stream.");
2019-01-17 17:56:21 +01:00
continue;
}
2019-01-28 16:28:26 +01:00
TSAttachmentPointer *_Nullable attachmentPointer = (TSAttachmentPointer *)attachment;
2018-11-07 23:49:25 +01:00
2019-01-17 17:56:21 +01:00
OWSLogDebug(@"Downloading attachment for message: %lu", (unsigned long)incomingMessage.timestamp);
// Use a separate download for each attachment so that:
//
// * We update the message as each comes in.
// * Failures don't interfere with successes.
[self.attachmentDownloads downloadAttachmentPointer:attachmentPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSAttachmentStream *_Nullable attachmentStream = attachmentStreams.firstObject;
OWSAssertDebug(attachmentStream);
if (attachmentStream && incomingMessage.quotedMessage.thumbnailAttachmentPointerId.length > 0 &&
[attachmentStream.uniqueId
isEqualToString:incomingMessage.quotedMessage.thumbnailAttachmentPointerId]) {
[incomingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[incomingMessage saveWithTransaction:transaction];
} else {
2019-01-22 16:17:11 +01:00
// We touch the message to trigger redraw of any views displaying it,
// since the attachment might be a contact avatar, etc.
2018-08-30 16:31:01 +02:00
[incomingMessage touchWithTransaction:transaction];
2019-01-17 17:56:21 +01:00
}
}];
2019-01-17 17:56:21 +01:00
}
failure:^(NSError *error) {
2020-02-15 00:01:21 +01:00
OWSLogWarn(@"Failed to download attachment for message: %lu with error: %@.",
2019-01-17 17:56:21 +01:00
(unsigned long)incomingMessage.timestamp,
error);
}];
}
2019-01-17 17:56:21 +01:00
2017-10-04 16:06:38 +02:00
// In case we already have a read receipt for this new message (this happens sometimes).
[OWSReadReceiptManager.sharedManager applyEarlyReadReceiptsForIncomingMessage:incomingMessage
transaction:transaction];
2017-10-04 16:06:38 +02:00
// Update thread preview in inbox
[masterThread touchWithTransaction:transaction];
2018-09-17 15:27:58 +02:00
[SSKEnvironment.shared.notificationsManager notifyUserForIncomingMessage:incomingMessage
inThread:masterThread
2018-09-17 15:27:58 +02:00
transaction:transaction];
dispatch_async(dispatch_get_main_queue(), ^{
[self.typingIndicators didReceiveIncomingMessageInThread:masterThread
recipientId:(masterThread.contactIdentifier ?: envelope.source)
deviceId:envelope.sourceDevice];
});
2015-12-07 03:31:43 +01:00
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#pragma mark - helpers
2015-12-07 03:31:43 +01:00
- (BOOL)isDataMessageGroupAvatarUpdate:(SSKProtoDataMessage *)dataMessage
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-08-30 16:31:01 +02:00
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return NO;
}
return (dataMessage.group != nil && dataMessage.group.type == SSKProtoGroupContextTypeUpdate
&& dataMessage.group.avatar != nil);
2015-12-07 03:31:43 +01:00
}
/**
* @returns
* Group or Contact thread for message, creating a new contact thread if necessary,
* but never creating a new group thread.
*/
- (nullable TSThread *)threadForEnvelope:(SSKProtoEnvelope *)envelope
dataMessage:(SSKProtoDataMessage *)dataMessage
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2018-08-30 16:31:01 +02:00
if (!envelope) {
OWSFailDebug(@"Missing envelope.");
return nil;
}
if (!dataMessage) {
OWSFailDebug(@"Missing dataMessage.");
return nil;
}
if (!transaction) {
2018-09-11 00:56:22 +02:00
OWSFail(@"Missing transaction.");
2018-08-30 16:31:01 +02:00
return nil;
}
if (dataMessage.group) {
NSData *groupId = dataMessage.group.id;
OWSAssertDebug(groupId.length > 0);
TSGroupThread *_Nullable groupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction];
// This method should only be called from a code path that has already verified
// that this is a "known" group.
OWSAssertDebug(groupThread);
return groupThread;
} else {
return [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction];
}
}
#pragma mark -
- (void)checkForUnknownLinkedDevice:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(envelope);
OWSAssertDebug(transaction);
NSString *localNumber = self.tsAccountManager.localNumber;
if (![localNumber isEqualToString:envelope.source]) {
return;
}
2018-10-30 21:15:27 +01:00
// Consult the device list cache we use for message sending
// whether or not we know about this linked device.
SignalRecipient *_Nullable recipient =
2018-10-31 20:39:38 +01:00
[SignalRecipient registeredRecipientForRecipientId:localNumber mustHaveDevices:NO transaction:transaction];
if (!recipient) {
2019-11-15 03:56:35 +01:00
// OWSFailDebug(@"No local SignalRecipient.");
} else {
BOOL isRecipientDevice = [recipient.devices containsObject:@(envelope.sourceDevice)];
if (!isRecipientDevice) {
OWSLogInfo(@"Message received from unknown linked device; adding to local SignalRecipient: %lu.",
(unsigned long) envelope.sourceDevice);
[recipient updateRegisteredRecipientWithDevicesToAdd:@[ @(envelope.sourceDevice) ]
devicesToRemove:nil
transaction:transaction];
}
}
2018-10-30 21:15:27 +01:00
// Consult the device list cache we use for the "linked device" UI
// whether or not we know about this linked device.
NSMutableSet<NSNumber *> *deviceIdSet = [NSMutableSet new];
for (OWSDevice *device in [OWSDevice currentDevicesWithTransaction:transaction]) {
[deviceIdSet addObject:@(device.deviceId)];
}
BOOL isInDeviceList = [deviceIdSet containsObject:@(envelope.sourceDevice)];
if (!isInDeviceList) {
OWSLogInfo(@"Message received from unknown linked device; refreshing device list: %lu.",
(unsigned long) envelope.sourceDevice);
[OWSDevicesService refreshDevices];
dispatch_async(dispatch_get_main_queue(), ^{
[self.profileManager fetchLocalUsersProfile];
});
}
}
2015-12-07 03:31:43 +01:00
@end
NS_ASSUME_NONNULL_END