diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 342d37adb..378a5f3ca 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -5,7 +5,7 @@ BuildDetails CarthageVersion - 0.34.0 + 0.33.0 OSXVersion 10.15.3 WebRTCCommit diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 0cab0c1bc..17c205a96 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -10,24 +10,10 @@ public extension LokiAPI { fileprivate static let failureThreshold = 2 // MARK: Caching + internal static var swarmCache: [String:[LokiAPITarget]] = [:] private static let swarmCacheKey = "swarmCacheKey" private static let swarmCacheCollection = "swarmCacheCollection" - internal static var swarmCache: [String:[LokiAPITarget]] { - get { - var result: [String:[LokiAPITarget]]? = nil - storage.dbReadConnection.read { transaction in - result = transaction.object(forKey: swarmCacheKey, inCollection: swarmCacheCollection) as! [String:[LokiAPITarget]]? - } - return result ?? [:] - } - set { - storage.dbReadWriteConnection.readWrite { transaction in - transaction.setObject(newValue, forKey: swarmCacheKey, inCollection: swarmCacheCollection) - } - } - } - internal static func dropIfNeeded(_ target: LokiAPITarget, hexEncodedPublicKey: String) { let swarm = LokiAPI.swarmCache[hexEncodedPublicKey] if var swarm = swarm, let index = swarm.firstIndex(of: target) { diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index bb6a7ad0a..73763e2a7 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -105,9 +105,17 @@ public final class LokiAPI : NSObject { } public static func getDestinations(for hexEncodedPublicKey: String) -> Promise<[Destination]> { + var result: Promise<[Destination]>! + storage.dbReadConnection.readWrite { transaction in + result = getDestinations(for: hexEncodedPublicKey, in: transaction) + } + return result + } + + public static func getDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<[Destination]> { let (promise, seal) = Promise<[Destination]>.pending() - func getDestinations() { - storage.dbReadConnection.read { transaction in + func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) { + func getDestinationsInternal(in transaction: YapDatabaseReadTransaction) { var destinations: [Destination] = [] let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey let masterDestination = Destination(hexEncodedPublicKey: masterHexEncodedPublicKey, kind: .master) @@ -117,6 +125,13 @@ public final class LokiAPI : NSObject { destinations.append(contentsOf: slaveDestinations) seal.fulfill(destinations) } + if let transaction = transaction { + getDestinationsInternal(in: transaction) + } else { + storage.dbReadConnection.read { transaction in + getDestinationsInternal(in: transaction) + } + } } let timeSinceLastUpdate: TimeInterval if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[hexEncodedPublicKey] { @@ -125,23 +140,21 @@ public final class LokiAPI : NSObject { timeSinceLastUpdate = .infinity } if timeSinceLastUpdate > deviceLinkUpdateInterval { - storage.dbReadConnection.read { transaction in - let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey - LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey).done(on: DispatchQueue.global()) { _ in - getDestinations() + let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey + LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, in: transaction).done(on: DispatchQueue.global()) { _ in + getDestinations() + lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() + }.catch(on: DispatchQueue.global()) { error in + if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed { + // Don't immediately re-fetch in case of failure due to a parsing error lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() - }.catch(on: DispatchQueue.global()) { error in - if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed { - // Don't immediately re-fetch in case of failure due to a parsing error - lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() - getDestinations() - } else { - seal.reject(error) - } + getDestinations() + } else { + seal.reject(error) } } } else { - getDestinations() + getDestinations(in: transaction) } return promise } @@ -205,6 +218,12 @@ public final class LokiAPI : NSObject { return AnyPromise.from(promise) } + @objc(getDestinationsFor:inTransaction:) + public static func objc_getDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> AnyPromise { + let promise = getDestinations(for: hexEncodedPublicKey, in: transaction) + return AnyPromise.from(promise) + } + @objc(sendSignalMessage:onP2PSuccess:) public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, onP2PSuccess: @escaping () -> Void) -> AnyPromise { let promise = sendSignalMessage(signalMessage, onP2PSuccess: onP2PSuccess).mapValues { AnyPromise.from($0) }.map { Set($0) } diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift index 0f7af4d16..e56c58471 100644 --- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift @@ -26,18 +26,32 @@ public class LokiDotNetAPI : NSObject { /// To be overridden by subclasses. internal class var authTokenCollection: String { preconditionFailure("authTokenCollection is abstract and must be overridden.") } - private static func getAuthTokenFromDatabase(for server: String) -> String? { - var result: String? = nil - storage.dbReadConnection.read { transaction in - result = transaction.object(forKey: server, inCollection: authTokenCollection) as! String? + private static func getAuthTokenFromDatabase(for server: String, in transaction: YapDatabaseReadTransaction? = nil) -> String? { + func getAuthTokenInternal(in transaction: YapDatabaseReadTransaction) -> String? { + return transaction.object(forKey: server, inCollection: authTokenCollection) as! String? + } + if let transaction = transaction { + return getAuthTokenInternal(in: transaction) + } else { + var result: String? = nil + storage.dbReadConnection.read { transaction in + result = getAuthTokenInternal(in: transaction) + } + return result } - return result } - private static func setAuthToken(for server: String, to newValue: String) { - storage.dbReadWriteConnection.readWrite { transaction in + private static func setAuthToken(for server: String, to newValue: String, in transaction: YapDatabaseReadWriteTransaction? = nil) { + func setAuthTokenInternal(in transaction: YapDatabaseReadWriteTransaction) { transaction.setObject(newValue, forKey: server, inCollection: authTokenCollection) } + if let transaction = transaction { + setAuthTokenInternal(in: transaction) + } else { + storage.dbReadWriteConnection.readWrite { transaction in + setAuthTokenInternal(in: transaction) + } + } } // MARK: Lifecycle @@ -146,12 +160,12 @@ public class LokiDotNetAPI : NSObject { } // MARK: Internal API - internal static func getAuthToken(for server: String) -> Promise { - if let token = getAuthTokenFromDatabase(for: server) { + internal static func getAuthToken(for server: String, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise { + if let token = getAuthTokenFromDatabase(for: server, in: transaction) { return Promise.value(token) } else { return requestNewAuthToken(for: server).then(on: DispatchQueue.global()) { submitAuthToken($0, for: server) }.map { token -> String in - setAuthToken(for: server, to: token) + setAuthToken(for: server, to: token, in: transaction) return token } } diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift index 69cb55ee2..cc70bb566 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift @@ -19,20 +19,20 @@ public final class LokiFileServerAPI : LokiDotNetAPI { // MARK: Device Links (Public API) /// Gets the device links associated with the given hex encoded public key from the /// server and stores and returns the valid ones. - public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise> { - return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ]) + public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise> { + return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ], in: transaction) } /// Gets the device links associated with the given hex encoded public keys from the /// server and stores and returns the valid ones. - public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> Promise> { + public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise> { let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]" print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).") - return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise> in + return getAuthToken(for: server, in: transaction).then(on: DispatchQueue.global()) { token -> Promise> in let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1" let url = URL(string: "\(server)/users?\(queryParameters)")! let request = TSRequest(url: url) - return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map { $0.responseObject }.map { rawResponse -> Set in + return TSNetworkManager.shared().perform(request, withCompletionQueue: DispatchQueue.global()).map(on: DispatchQueue.global()) { $0.responseObject }.map(on: DispatchQueue.global()) { rawResponse -> Set in guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else { print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).") throw LokiDotNetAPIError.parsingFailed @@ -74,7 +74,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI { return deviceLink } }) - }.map { deviceLinks -> Set in + }.map(on: DispatchQueue.global()) { deviceLinks -> Set in storage.dbReadWriteConnection.readWrite { transaction in storage.setDeviceLinks(deviceLinks, in: transaction) } diff --git a/SignalServiceKit/src/Loki/API/LokiLongPoller.swift b/SignalServiceKit/src/Loki/API/LokiLongPoller.swift index 4d89ac412..ef1a0b1e0 100644 --- a/SignalServiceKit/src/Loki/API/LokiLongPoller.swift +++ b/SignalServiceKit/src/Loki/API/LokiLongPoller.swift @@ -83,13 +83,8 @@ public final class LokiLongPoller : NSObject { return LokiAPI.getRawMessages(from: target, usingLongPolling: true).then(on: DispatchQueue.global()) { [weak self] rawResponse -> Promise in guard let strongSelf = self, !strongSelf.hasStopped else { return Promise.value(()) } let messages = LokiAPI.parseRawMessagesResponse(rawResponse, from: target) - let hexEncodedPublicKeys = Set(messages.compactMap { $0.source }) - let promises = hexEncodedPublicKeys.map { LokiAPI.getDestinations(for: $0) } - return when(resolved: promises).then(on: DispatchQueue.global()) { _ -> Promise in - guard let strongSelf = self, !strongSelf.hasStopped else { return Promise.value(()) } - strongSelf.onMessagesReceived(messages) - return strongSelf.longPoll(target, seal: seal) - } + strongSelf.onMessagesReceived(messages) + return strongSelf.longPoll(target, seal: seal) } } } diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h index bcfd201bf..fef3b85ca 100644 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h +++ b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h @@ -9,11 +9,19 @@ NS_ASSUME_NONNULL_BEGIN @class SSKProtoEnvelope; @class YapDatabaseReadWriteTransaction; +@interface OWSMessageContentQueue : NSObject + +- (dispatch_queue_t)serialQueue; + +@end + // This class is used to write incoming (decrypted, unprocessed) // messages to a durable queue and then process them in batches, // in the order in which they were received. @interface OWSBatchMessageProcessor : NSObject +@property (nonatomic, readonly) OWSMessageContentQueue *processingQueue; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m index d6d74e69f..8e7735407 100644 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m +++ b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m @@ -237,7 +237,7 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo #pragma mark - Queue Processing -@interface OWSMessageContentQueue : NSObject +@interface OWSMessageContentQueue () @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @property (nonatomic, readonly) OWSMessageContentJobFinder *finder; @@ -431,10 +431,8 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo void (^reportFailure)(YapDatabaseReadWriteTransaction *transaction) = ^( YapDatabaseReadWriteTransaction *transaction) { - // TODO: Add analytics. TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; + [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage transaction:transaction]; }; @try { @@ -449,9 +447,9 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo serverID:0]; } } @catch (NSException *exception) { -// OWSFailDebug(@"Received an invalid envelope: %@", exception.debugDescription); reportFailure(transaction); } + [processedJobs addObject:job]; if (self.isAppInBackground) { @@ -473,7 +471,6 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo @interface OWSBatchMessageProcessor () -@property (nonatomic, readonly) OWSMessageContentQueue *processingQueue; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @end diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 06ac6d215..360ff7655 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -59,6 +59,8 @@ #import #import #import "OWSDispatch.h" +#import "OWSBatchMessageProcessor.h" +#import "OWSQueues.h" NS_ASSUME_NONNULL_BEGIN @@ -277,6 +279,13 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertDebug(![self isEnvelopeSenderBlocked:envelope]); + // Loki: Ignore any friend requests that we got before restoration + 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) { @@ -1391,6 +1400,19 @@ NS_ASSUME_NONNULL_BEGIN return nil; } + dispatch_queue_t messageProcessingQueue = SSKEnvironment.shared.batchMessageProcessor.processingQueue.serialQueue; + AssertOnDispatchQueue(messageProcessingQueue); + + if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source]) { + 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]; + 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]; diff --git a/SignalServiceKit/src/Messages/OWSMessageReceiver.m b/SignalServiceKit/src/Messages/OWSMessageReceiver.m index a08ebe946..cd23ecf8e 100644 --- a/SignalServiceKit/src/Messages/OWSMessageReceiver.m +++ b/SignalServiceKit/src/Messages/OWSMessageReceiver.m @@ -423,6 +423,15 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { OWSAssertDebug(transaction); + // Loki: Don't process any messages from ourself + ECKeyPair *_Nullable keyPair = OWSIdentityManager.sharedManager.identityKeyPair; + if (keyPair && [result.source isEqualToString:keyPair.hexEncodedPublicKey]) { + dispatch_async(self.serialQueue, ^{ + completion(YES); + }); + return; + } + // 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, // the session state side effects of its decryption are also rolled back.