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

515 lines
16 KiB
Mathematica
Raw Normal View History

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageReceiver.h"
#import "AppContext.h"
#import "AppReadiness.h"
2017-09-20 17:48:37 +02:00
#import "NSArray+OWS.h"
2018-04-17 21:23:05 +02:00
#import "NotificationsProtocol.h"
#import "OWSBackgroundTask.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSMessageDecrypter.h"
#import "OWSPrimaryStorage+Loki.h"
#import "OWSQueues.h"
2017-12-19 04:56:02 +01:00
#import "OWSStorage.h"
2019-05-29 04:40:47 +02:00
#import "OWSIdentityManager.h"
#import "SSKEnvironment.h"
#import "TSAccountManager.h"
#import "TSDatabaseView.h"
2018-04-17 21:23:05 +02:00
#import "TSErrorMessage.h"
#import "TSYapDatabaseObject.h"
2020-06-05 02:38:44 +02:00
#import <SessionCoreKit/Threading.h>
2020-06-05 05:43:06 +02:00
#import <SessionServiceKit/SessionServiceKit-Swift.h>
2017-12-12 16:31:05 +01:00
#import <YapDatabase/YapDatabaseAutoView.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
2017-12-12 16:31:05 +01:00
#import <YapDatabase/YapDatabaseViewTypes.h>
2020-06-05 02:38:44 +02:00
#import <SessionCoreKit/NSDate+OWS.h>
2020-06-05 05:43:06 +02:00
#import <SessionServiceKit/SessionServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
2017-09-20 17:48:37 +02:00
@interface OWSMessageDecryptJob : TSYapDatabaseObject
@property (nonatomic, readonly) NSDate *createdAt;
2017-09-27 17:30:23 +02:00
@property (nonatomic, readonly) NSData *envelopeData;
@property (nonatomic, readonly, nullable) SSKProtoEnvelope *envelopeProto;
2018-06-07 07:57:59 +02:00
- (instancetype)initWithEnvelopeData:(NSData *)envelopeData NS_DESIGNATED_INITIALIZER;
2017-09-27 17:30:23 +02:00
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
2017-11-15 19:15:48 +01:00
- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE;
@end
#pragma mark -
2017-09-20 17:48:37 +02:00
@implementation OWSMessageDecryptJob
+ (NSString *)collection
{
return @"OWSMessageProcessingJob";
}
2018-06-07 07:57:59 +02:00
- (instancetype)initWithEnvelopeData:(NSData *)envelopeData
{
OWSAssertDebug(envelopeData);
self = [super initWithUniqueId:[NSUUID new].UUIDString];
if (!self) {
return self;
}
2018-06-07 07:57:59 +02:00
_envelopeData = envelopeData;
_createdAt = [NSDate new];
return self;
}
2017-09-27 17:30:23 +02:00
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
}
- (nullable SSKProtoEnvelope *)envelopeProto
{
2018-06-07 07:57:59 +02:00
NSError *error;
SSKProtoEnvelope *_Nullable envelope = [SSKProtoEnvelope parseData:self.envelopeData error:&error];
2018-06-07 07:57:59 +02:00
if (error || envelope == nil) {
2018-10-04 17:33:58 +02:00
OWSFailDebug(@"failed to parse envelope with error: %@", error);
2018-06-07 07:57:59 +02:00
return nil;
}
return envelope;
}
@end
#pragma mark - Finder
NSString *const OWSMessageDecryptJobFinderExtensionName = @"OWSMessageProcessingJobFinderExtensionName2";
NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessingJobFinderExtensionGroup2";
2017-09-20 17:48:37 +02:00
@interface OWSMessageDecryptJobFinder : NSObject
@end
#pragma mark -
2017-09-20 17:48:37 +02:00
@interface OWSMessageDecryptJobFinder ()
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@end
#pragma mark -
2017-09-20 17:48:37 +02:00
@implementation OWSMessageDecryptJobFinder
- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection
{
OWSSingletonAssert();
self = [super init];
if (!self) {
return self;
}
_dbConnection = dbConnection;
2017-09-27 18:40:36 +02:00
[OWSMessageDecryptJobFinder registerLegacyClasses];
2017-09-27 17:30:23 +02:00
return self;
}
- (OWSMessageDecryptJob *_Nullable)nextJob
{
__block OWSMessageDecryptJob *_Nullable job = nil;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
2017-09-20 17:48:37 +02:00
YapDatabaseViewTransaction *viewTransaction = [transaction ext:OWSMessageDecryptJobFinderExtensionName];
OWSAssertDebug(viewTransaction != nil);
job = [viewTransaction firstObjectInGroup:OWSMessageDecryptJobFinderExtensionGroup];
}];
return job;
}
2018-06-07 07:57:59 +02:00
- (void)addJobForEnvelopeData:(NSData *)envelopeData
{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
2018-06-07 07:57:59 +02:00
OWSMessageDecryptJob *job = [[OWSMessageDecryptJob alloc] initWithEnvelopeData:envelopeData];
[job saveWithTransaction:transaction];
} error:nil];
}
- (void)removeJobWithId:(NSString *)uniqueId
{
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[transaction removeObjectForKey:uniqueId inCollection:[OWSMessageDecryptJob collection]];
} error:nil];
}
+ (YapDatabaseView *)databaseExtension
{
YapDatabaseViewSorting *sorting =
[YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction,
NSString *group,
NSString *collection1,
NSString *key1,
id object1,
NSString *collection2,
NSString *key2,
id object2) {
2017-09-20 17:48:37 +02:00
if (![object1 isKindOfClass:[OWSMessageDecryptJob class]]) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object1 class], collection1);
return NSOrderedSame;
}
2017-09-20 17:48:37 +02:00
OWSMessageDecryptJob *job1 = (OWSMessageDecryptJob *)object1;
2017-09-20 17:48:37 +02:00
if (![object2 isKindOfClass:[OWSMessageDecryptJob class]]) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object2 class], collection2);
return NSOrderedSame;
}
2017-09-20 17:48:37 +02:00
OWSMessageDecryptJob *job2 = (OWSMessageDecryptJob *)object2;
return [job1.createdAt compare:job2.createdAt];
}];
YapDatabaseViewGrouping *grouping =
[YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable(YapDatabaseReadTransaction *_Nonnull transaction,
NSString *_Nonnull collection,
NSString *_Nonnull key,
id _Nonnull object) {
2017-09-20 17:48:37 +02:00
if (![object isKindOfClass:[OWSMessageDecryptJob class]]) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"Unexpected object: %@ in collection: %@", object, collection);
return nil;
}
// Arbitrary string - all in the same group. We're only using the view for sorting.
2017-09-20 17:48:37 +02:00
return OWSMessageDecryptJobFinderExtensionGroup;
}];
YapDatabaseViewOptions *options = [YapDatabaseViewOptions new];
options.allowedCollections =
2017-09-20 17:48:37 +02:00
[[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OWSMessageDecryptJob collection]]];
return [[YapDatabaseAutoView alloc] initWithGrouping:grouping sorting:sorting versionTag:@"1" options:options];
}
2017-09-27 18:40:36 +02:00
+ (void)registerLegacyClasses
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
2017-09-27 18:59:38 +02:00
// We've renamed OWSMessageProcessingJob to OWSMessageDecryptJob.
2017-09-27 18:40:36 +02:00
[NSKeyedUnarchiver setClass:[OWSMessageDecryptJob class] forClassName:[OWSMessageDecryptJob collection]];
});
}
+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage
{
2017-09-27 18:40:36 +02:00
[self registerLegacyClasses];
YapDatabaseView *existingView = [storage registeredExtension:OWSMessageDecryptJobFinderExtensionName];
if (existingView) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"%@ was already initialized.", OWSMessageDecryptJobFinderExtensionName);
// already initialized
return;
}
[storage asyncRegisterExtension:[self databaseExtension] withName:OWSMessageDecryptJobFinderExtensionName];
}
@end
#pragma mark - Queue Processing
2017-09-20 17:48:37 +02:00
@interface OWSMessageDecryptQueue : NSObject
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
2017-09-20 17:48:37 +02:00
@property (nonatomic, readonly) OWSMessageDecryptJobFinder *finder;
@property (nonatomic) BOOL isDrainingQueue;
- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection
finder:(OWSMessageDecryptJobFinder *)finder NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
#pragma mark -
2017-09-20 17:48:37 +02:00
@implementation OWSMessageDecryptQueue
- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection finder:(OWSMessageDecryptJobFinder *)finder
{
OWSSingletonAssert();
self = [super init];
if (!self) {
return self;
}
_dbConnection = dbConnection;
_finder = finder;
_isDrainingQueue = NO;
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
2018-11-05 15:16:17 +01:00
if (CurrentAppContext().isMainApp) {
[self drainQueue];
}
2018-02-14 22:06:47 +01:00
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registrationStateDidChange:)
name:RegistrationStateDidChangeNotification
object:nil];
return self;
}
#pragma mark - Singletons
- (OWSMessageDecrypter *)messageDecrypter
{
OWSAssertDebug(SSKEnvironment.shared.messageDecrypter);
return SSKEnvironment.shared.messageDecrypter;
}
- (OWSBatchMessageProcessor *)batchMessageProcessor
{
OWSAssertDebug(SSKEnvironment.shared.batchMessageProcessor);
return SSKEnvironment.shared.batchMessageProcessor;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark - Notifications
- (void)registrationStateDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (CurrentAppContext().isMainApp) {
[self drainQueue];
}
}];
}
#pragma mark - Instance methods
- (dispatch_queue_t)serialQueue
{
static dispatch_queue_t queue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("org.whispersystems.message.decrypt", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
2018-06-07 07:57:59 +02:00
- (void)enqueueEnvelopeData:(NSData *)envelopeData
{
2018-06-07 07:57:59 +02:00
[self.finder addJobForEnvelopeData:envelopeData];
}
- (void)drainQueue
{
OWSAssertDebug(AppReadiness.isAppReady);
2018-02-14 22:06:47 +01:00
2020-02-15 00:01:21 +01:00
if (!CurrentAppContext().isMainApp) { return; }
if (!self.tsAccountManager.isRegisteredAndReady) { return; }
dispatch_async(self.serialQueue, ^{
2020-02-15 00:01:21 +01:00
if (self.isDrainingQueue) { return; }
self.isDrainingQueue = YES;
[self drainQueueWorkStep];
});
}
- (void)drainQueueWorkStep
{
AssertOnDispatchQueue(self.serialQueue);
OWSMessageDecryptJob *_Nullable job = [self.finder nextJob];
2020-02-15 00:01:21 +01:00
if (!job) {
self.isDrainingQueue = NO;
OWSLogVerbose(@"Queue is drained.");
return;
}
2018-08-02 21:18:40 +02:00
__block OWSBackgroundTask *_Nullable backgroundTask =
[OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[self processJob:job
completion:^(BOOL success) {
[self.finder removeJobWithId:job.uniqueId];
OWSLogVerbose(@"%@ job. %lu jobs left.",
success ? @"decrypted" : @"failed to decrypt",
(unsigned long)[OWSMessageDecryptJob numberOfKeysInCollection]);
[self drainQueueWorkStep];
OWSAssertDebug(backgroundTask);
backgroundTask = nil;
}];
}
2018-12-02 23:30:31 +01:00
- (BOOL)wasReceivedByUD:(SSKProtoEnvelope *)envelope
{
2020-02-15 00:01:21 +01:00
return (envelope.type == SSKProtoEnvelopeTypeUnidentifiedSender && (!envelope.hasSource || envelope.source.length < 1));
2018-12-02 23:30:31 +01:00
}
- (void)processJob:(OWSMessageDecryptJob *)job completion:(void (^)(BOOL))completion
{
AssertOnDispatchQueue(self.serialQueue);
OWSAssertDebug(job);
2018-08-07 20:36:36 +02:00
SSKProtoEnvelope *_Nullable envelope = job.envelopeProto;
2020-02-15 00:01:21 +01:00
2018-08-07 20:36:36 +02:00
if (!envelope) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Couldn't parse proto.");
2018-04-17 21:23:05 +02:00
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread];
[SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage
transaction:transaction];
} error:nil];
2018-04-17 21:23:05 +02:00
2018-04-16 20:48:29 +02:00
dispatch_async(self.serialQueue, ^{
completion(NO);
});
2020-02-15 00:01:21 +01:00
return;
}
2018-12-02 23:30:31 +01:00
// We use the original envelope for this check;
// the decryption process might rewrite the envelope.
BOOL wasReceivedByUD = [self wasReceivedByUD:envelope];
[self.messageDecrypter decryptEnvelope:envelope
2018-10-02 17:25:58 +02:00
envelopeData:job.envelopeData
2018-10-04 17:33:58 +02:00
successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) {
OWSAssertDebug(transaction);
2018-01-30 21:05:04 +01:00
2020-07-07 03:10:57 +02:00
if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result wrappedIn:envelope]) {
dispatch_async(self.serialQueue, ^{
completion(YES);
});
return;
}
2018-01-30 21:05:04 +01:00
// We persist the decrypted envelope data in the same transaction within which
// it was decrypted to prevent data loss. If the new job isn't persisted,
2018-01-30 21:05:04 +01:00
// the session state side effects of its decryption are also rolled back.
2018-10-02 17:25:58 +02:00
//
// NOTE: We use envelopeData from the decrypt result, not job.envelopeData,
2018-10-03 16:08:45 +02:00
// since the envelope may be altered by the decryption process in the UD case.
2018-10-04 17:33:58 +02:00
[self.batchMessageProcessor enqueueEnvelopeData:result.envelopeData
plaintextData:result.plaintextData
2018-12-02 23:30:31 +01:00
wasReceivedByUD:wasReceivedByUD
2018-01-30 21:05:04 +01:00
transaction:transaction];
dispatch_async(self.serialQueue, ^{
completion(YES);
});
}
failureBlock:^{
dispatch_async(self.serialQueue, ^{
completion(NO);
});
}];
}
@end
#pragma mark - OWSMessageReceiver
@interface OWSMessageReceiver ()
2017-09-20 17:48:37 +02:00
@property (nonatomic, readonly) OWSMessageDecryptQueue *processingQueue;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@end
#pragma mark -
@implementation OWSMessageReceiver
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
{
OWSSingletonAssert();
self = [super init];
2020-07-21 01:11:26 +02:00
if (!self) {
return self;
}
// For coherency we use the same dbConnection to persist and read the unprocessed envelopes
YapDatabaseConnection *dbConnection = [primaryStorage newDatabaseConnection];
2017-09-20 17:48:37 +02:00
OWSMessageDecryptJobFinder *finder = [[OWSMessageDecryptJobFinder alloc] initWithDBConnection:dbConnection];
2020-02-15 00:01:21 +01:00
OWSMessageDecryptQueue *processingQueue = [[OWSMessageDecryptQueue alloc] initWithDBConnection:dbConnection finder:finder];
_processingQueue = processingQueue;
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (CurrentAppContext().isMainApp) {
[self.processingQueue drainQueue];
}
}];
return self;
}
#pragma mark - class methods
+ (NSString *)databaseExtensionName
{
return OWSMessageDecryptJobFinderExtensionName;
}
+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage
{
[OWSMessageDecryptJobFinder asyncRegisterDatabaseExtension:storage];
}
#pragma mark - instance methods
2018-06-07 07:57:59 +02:00
- (void)handleReceivedEnvelopeData:(NSData *)envelopeData
{
2018-08-30 16:31:01 +02:00
if (envelopeData.length < 1) {
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Received an empty envelope.");
2018-08-30 16:31:01 +02:00
return;
}
// Drop any too-large messages on the floor. Well behaving clients should never send them.
NSUInteger kMaxEnvelopeByteCount = 250 * 1024;
2018-06-07 07:57:59 +02:00
if (envelopeData.length > kMaxEnvelopeByteCount) {
OWSProdError([OWSAnalyticsEvents messageReceiverErrorOversizeMessage]);
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Received an oversized message.");
return;
}
// Take note of any messages larger than we expect, but still process them.
// This likely indicates a misbehaving sending client.
NSUInteger kLargeEnvelopeWarningByteCount = 25 * 1024;
2018-06-07 07:57:59 +02:00
if (envelopeData.length > kLargeEnvelopeWarningByteCount) {
OWSProdError([OWSAnalyticsEvents messageReceiverErrorLargeMessage]);
2020-02-15 00:01:21 +01:00
OWSFailDebug(@"Received an unexpectedly large message.");
}
2018-06-07 07:57:59 +02:00
[self.processingQueue enqueueEnvelopeData:envelopeData];
[self.processingQueue drainQueue];
}
@end
NS_ASSUME_NONNULL_END