diff --git a/Pods b/Pods index e9fb539a0..c20b80e39 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit e9fb539a0061d7e9f87ba48a724f99092232b41b +Subproject commit c20b80e3952ccdb215831c4063c79d479d1cd702 diff --git a/Signal/test/Models/AccountManagerTest.swift b/Signal/test/Models/AccountManagerTest.swift index 67590f6fb..8841b9f1d 100644 --- a/Signal/test/Models/AccountManagerTest.swift +++ b/Signal/test/Models/AccountManagerTest.swift @@ -104,7 +104,7 @@ class AccountManagerTest: SignalBaseTest { func testSuccessfulRegistration() { Environment.clearSharedForTests() - Environment.setCurrent(Release.releaseEnvironment()) + Environment.shared = Release.releaseEnvironment() let tsAccountManager = TokenObtainingTSAccountManager(networkManager: TSNetworkManager.shared(), primaryStorage: OWSPrimaryStorage.shared()) diff --git a/Signal/test/util/FunctionalUtilTest.m b/Signal/test/util/FunctionalUtilTest.m index c753628f0..f2d2ee6c4 100644 --- a/Signal/test/util/FunctionalUtilTest.m +++ b/Signal/test/util/FunctionalUtilTest.m @@ -1,3 +1,7 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + #import "FunctionalUtilTest.h" #import "FunctionalUtil.h" #import "TestUtil.h" @@ -24,19 +28,6 @@ test([[(@[@1,@2]) filter:^(NSNumber* x) { return x.intValue == 1; }] isEqualToArray:(@[@1])]); test([[(@[@1,@2]) filter:^(NSNumber* x) { return x.intValue == 2; }] isEqualToArray:(@[@2])]); } --(void) testSum { - test([(@[]) sumDouble] == 0); - test([(@[]) sumNSUInteger] == 0); - test([(@[]) sumNSInteger] == 0); - - test([(@[@1]) sumDouble] == 1); - test([(@[@2]) sumNSUInteger] == 2); - test([(@[@3]) sumNSInteger] == 3); - - test([(@[@1.5, @2.75]) sumDouble] == 4.25); - test([(@[@1, @3]) sumNSUInteger] == 4); - test([(@[@-1, @4]) sumNSInteger] == 3); -} -(void) testKeyedBy { test([[@[] keyedBy:^id(id value) { return @true; }] isEqual:@{}]); test([[@[@1] keyedBy:^id(id value) { return @true; }] isEqual:@{@true : @1}]); diff --git a/Signal/test/util/OWSOrphanDataCleanerTest.m b/Signal/test/util/OWSOrphanDataCleanerTest.m index fd9ba33d3..051d02c39 100644 --- a/Signal/test/util/OWSOrphanDataCleanerTest.m +++ b/Signal/test/util/OWSOrphanDataCleanerTest.m @@ -2,15 +2,15 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import "OWSDevice.h" #import "OWSOrphanDataCleaner.h" +#import "OWSDevice.h" #import "OWSPrimaryStorage.h" -#import "SSKBaseTest.h" #import "TSAttachmentStream.h" #import "TSContactThread.h" #import "TSIncomingMessage.h" +#import -@interface OWSOrphanDataCleanerTest : SSKBaseTest +@interface OWSOrphanDataCleanerTest : XCTestCase @end @@ -43,7 +43,9 @@ - (NSUInteger)numberOfItemsInAttachmentsFolder { - return [OWSOrphanDataCleaner filePathsInAttachmentsFolder].count; + XCTFail(@"Test broken to fix compilation"); + // return [OWSOrphanDataCleaner filePathsInAttachmentsFolder].count; + return 0; } - (TSIncomingMessage *)createIncomingMessageWithThread:(TSThread *)thread @@ -91,9 +93,11 @@ XCTAssertEqual(1, [TSIncomingMessage numberOfKeysInCollection]); XCTestExpectation *expectation = [self expectationWithDescription:@"Cleanup"]; - [OWSOrphanDataCleaner auditAndCleanupAsync:^{ - [expectation fulfill]; - }]; + + XCTFail(@"Test broken to fix compilation"); + // [OWSOrphanDataCleaner auditAndCleanupAsync:^{ + // [expectation fulfill]; + // }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { @@ -114,9 +118,10 @@ XCTAssertEqual(1, [TSIncomingMessage numberOfKeysInCollection]); XCTestExpectation *expectation = [self expectationWithDescription:@"Cleanup"]; - [OWSOrphanDataCleaner auditAndCleanupAsync:^{ - [expectation fulfill]; - }]; + XCTFail(@"Test broken to fix compilation"); + // [OWSOrphanDataCleaner auditAndCleanupAsync:^{ + // [expectation fulfill]; + // }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { @@ -134,7 +139,7 @@ TSAttachmentStream *attachmentStream = [self createAttachmentStream]; - NSString *orphanedFilePath = [attachmentStream filePath]; + NSString *orphanedFilePath = [attachmentStream originalFilePath]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:orphanedFilePath]; XCTAssert(fileExists); XCTAssertEqual(1, [self numberOfItemsInAttachmentsFolder]); @@ -142,9 +147,10 @@ // Do multiple cleanup passes. for (int i = 0; i < 2; i++) { XCTestExpectation *expectation = [self expectationWithDescription:@"Cleanup"]; - [OWSOrphanDataCleaner auditAndCleanupAsync:^{ - [expectation fulfill]; - }]; + XCTFail(@"Test broken to fix compilation"); + // [OWSOrphanDataCleaner auditAndCleanupAsync:^{ + // [expectation fulfill]; + // }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { @@ -168,15 +174,16 @@ __unused TSIncomingMessage *incomingMessage = [self createIncomingMessageWithThread:savedThread attachmentIds:@[ attachmentStream.uniqueId ]]; - NSString *attachmentFilePath = [attachmentStream filePath]; + NSString *attachmentFilePath = [attachmentStream originalFilePath]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentFilePath]; XCTAssert(fileExists); XCTAssertEqual(1, [self numberOfItemsInAttachmentsFolder]); XCTestExpectation *expectation = [self expectationWithDescription:@"Cleanup"]; - [OWSOrphanDataCleaner auditAndCleanupAsync:^{ - [expectation fulfill]; - }]; + XCTFail(@"Test broken to fix compilation"); + // [OWSOrphanDataCleaner auditAndCleanupAsync:^{ + // [expectation fulfill]; + // }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { @@ -197,15 +204,16 @@ [attachmentStream writeData:[NSData new] error:&error]; // Intentionally not saved, because we want a lingering file. - NSString *orphanedFilePath = [attachmentStream filePath]; + NSString *orphanedFilePath = [attachmentStream originalFilePath]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:orphanedFilePath]; XCTAssert(fileExists); XCTAssertEqual(1, [self numberOfItemsInAttachmentsFolder]); XCTestExpectation *expectation = [self expectationWithDescription:@"Cleanup"]; - [OWSOrphanDataCleaner auditAndCleanupAsync:^{ - [expectation fulfill]; - }]; + XCTFail(@"Test broken to fix compilation"); + // [OWSOrphanDataCleaner auditAndCleanupAsync:^{ + // [expectation fulfill]; + // }]; [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { diff --git a/Signal/test/util/SearcherTest.swift b/Signal/test/util/SearcherTest.swift index 31b8ff513..c0b34c4f2 100644 --- a/Signal/test/util/SearcherTest.swift +++ b/Signal/test/util/SearcherTest.swift @@ -106,7 +106,7 @@ class ConversationSearcherTest: SignalBaseTest { override func tearDown() { super.tearDown() - SSKEnvironment.setShared(originalEnvironment!) + SSKEnvironment.shared = originalEnvironment! } override func setUp() { @@ -123,7 +123,7 @@ class ConversationSearcherTest: SignalBaseTest { let testEnvironment: StubbableEnvironment = StubbableEnvironment(proxy: originalEnvironment!) testEnvironment.stubbedContactsManager = FakeContactsManager() - SSKEnvironment.setShared(testEnvironment) + SSKEnvironment.shared = testEnvironment self.dbConnection.readWrite { transaction in let bookModel = TSGroupModel(title: "Book Club", memberIds: [aliceRecipientId, bobRecipientId], image: nil, groupId: Randomness.generateRandomBytes(16)) diff --git a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift b/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift index cec609271..32f21407b 100644 --- a/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift +++ b/SignalMessaging/environment/migrations/OWS106EnsureProfileComplete.swift @@ -101,13 +101,12 @@ public class OWS106EnsureProfileComplete: OWSDatabaseMigration { case SignalServiceProfile.ValidationError.invalidIdentityKey(let description): Logger.warn("detected incomplete profile for \(localRecipientId) error: \(description)") // This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration. - TSPreKeyManager.registerPreKeys(with: .signedAndOneTime, - success: { - Logger.info("successfully uploaded pre-keys. Profile should be fixed.") - fulfill(()) + TSPreKeyManager.createPreKeys(success: { + Logger.info("successfully uploaded pre-keys. Profile should be fixed.") + fulfill(()) }, - failure: { _ in - reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error")) + failure: { _ in + reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.logTag) Unknown error")) }) default: reject(error) diff --git a/SignalServiceKit/src/Account/AccountServiceClient.swift b/SignalServiceKit/src/Account/AccountServiceClient.swift new file mode 100644 index 000000000..da9c5ee06 --- /dev/null +++ b/SignalServiceKit/src/Account/AccountServiceClient.swift @@ -0,0 +1,34 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +// TODO define actual type, and validate length +public typealias IdentityKey = Data + +/// based on libsignal-service-java's AccountManager class +@objc(SSKAccountServiceClient) +public class AccountServiceClient: NSObject { + + static var shared = AccountServiceClient() + + private let serviceClient: SignalServiceClient + + override init() { + self.serviceClient = SignalServiceRestClient() + } + + public func getPreKeysCount() -> Promise { + return serviceClient.getAvailablePreKeys() + } + + public func setPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { + return serviceClient.registerPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords) + } + + public func setSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise { + return serviceClient.setCurrentSignedPreKey(signedPreKey) + } +} diff --git a/SignalServiceKit/src/Account/CreatePreKeysOperation.swift b/SignalServiceKit/src/Account/CreatePreKeysOperation.swift new file mode 100644 index 000000000..a16a3f726 --- /dev/null +++ b/SignalServiceKit/src/Account/CreatePreKeysOperation.swift @@ -0,0 +1,49 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +@objc(SSKCreatePreKeysOperation) +public class CreatePreKeysOperation: OWSOperation { + + private var accountServiceClient: AccountServiceClient { + return AccountServiceClient.shared + } + + private var primaryStorage: OWSPrimaryStorage { + return OWSPrimaryStorage.shared() + } + + private var identityKeyManager: OWSIdentityManager { + return OWSIdentityManager.shared() + } + + public override func run() { + Logger.debug("") + + if self.identityKeyManager.identityKeyPair() == nil { + self.identityKeyManager.generateNewIdentityKey() + } + let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey + let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() + let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords() + + firstly { + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + self.primaryStorage.storePreKeyRecords(preKeyRecords) + + return self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords) + }.then { () -> Void in + signedPreKeyRecord.markAsAcceptedByService() + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) + }.then { () -> Void in + Logger.debug("done") + self.reportSuccess() + }.catch { error in + self.reportError(error) + }.retainUntilComplete() + } +} diff --git a/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift b/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift new file mode 100644 index 000000000..cb19afa9e --- /dev/null +++ b/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift @@ -0,0 +1,93 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +// We generate 100 one-time prekeys at a time. We should replenish +// whenever ~2/3 of them have been consumed. +let kEphemeralPreKeysMinimumCount: UInt = 35 + +@objc(SSKRefreshPreKeysOperation) +public class RefreshPreKeysOperation: OWSOperation { + + private var tsAccountManager: TSAccountManager { + return TSAccountManager.sharedInstance() + } + + private var accountServiceClient: AccountServiceClient { + return AccountServiceClient.shared + } + + private var primaryStorage: OWSPrimaryStorage { + return OWSPrimaryStorage.shared() + } + + private var identityKeyManager: OWSIdentityManager { + return OWSIdentityManager.shared() + } + + public override func run() { + Logger.debug("") + + guard tsAccountManager.isRegistered() else { + Logger.debug("skipping - not registered") + return + } + + firstly { + self.accountServiceClient.getPreKeysCount() + }.then(on: DispatchQueue.global()) { preKeysCount -> Promise in + Logger.debug("preKeysCount: \(preKeysCount)") + guard preKeysCount < kEphemeralPreKeysMinimumCount || self.primaryStorage.currentSignedPrekeyId() == nil else { + Logger.debug("Available keys sufficient: \(preKeysCount)") + return Promise(value: ()) + } + + let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey + let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() + let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords() + + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + self.primaryStorage.storePreKeyRecords(preKeyRecords) + + return self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords).then { () -> Void in + signedPreKeyRecord.markAsAcceptedByService() + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) + + TSPreKeyManager.clearPreKeyUpdateFailureCount() + TSPreKeyManager.clearSignedPreKeyRecords() + } + }.then { () -> Void in + Logger.debug("done") + self.reportSuccess() + }.catch { error in + self.reportError(error) + }.retainUntilComplete() + } + + public override func didSucceed() { + TSPreKeyManager.refreshPreKeysDidSucceed() + } + + override public func didFail(error: Error) { + switch error { + case let networkManagerError as NetworkManagerError: + guard !networkManagerError.isNetworkError else { + Logger.debug("don't report SPK rotation failure w/ network error") + return + } + + guard networkManagerError.statusCode >= 400 && networkManagerError.statusCode <= 599 else { + Logger.debug("don't report SPK rotation failure w/ non application error") + return + } + + TSPreKeyManager.incrementPreKeyUpdateFailureCount() + default: + Logger.debug("don't report SPK rotation failure w/ non NetworkManager error: \(error)") + } + } +} diff --git a/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift b/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift new file mode 100644 index 000000000..f35c74325 --- /dev/null +++ b/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +@objc(SSKRotateSignedPreKeyOperation) +public class RotateSignedPreKeyOperation: OWSOperation { + private var tsAccountManager: TSAccountManager { + return TSAccountManager.sharedInstance() + } + + private var accountServiceClient: AccountServiceClient { + return AccountServiceClient.shared + } + + private var primaryStorage: OWSPrimaryStorage { + return OWSPrimaryStorage.shared() + } + + public override func run() { + Logger.debug("") + + guard tsAccountManager.isRegistered() else { + Logger.debug("skipping - not registered") + return + } + + let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() + + firstly { + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + return self.accountServiceClient.setSignedPreKey(signedPreKeyRecord) + }.then(on: DispatchQueue.global()) { () -> Void in + Logger.info("Successfully uploaded signed PreKey") + signedPreKeyRecord.markAsAcceptedByService() + self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) + self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) + + TSPreKeyManager.clearPreKeyUpdateFailureCount() + TSPreKeyManager.clearSignedPreKeyRecords() + }.then { () -> Void in + Logger.debug("done") + self.reportSuccess() + }.catch { error in + self.reportError(error) + }.retainUntilComplete() + } + + override public func didFail(error: Error) { + switch error { + case let networkManagerError as NetworkManagerError: + guard !networkManagerError.isNetworkError else { + Logger.debug("don't report SPK rotation failure w/ network error") + return + } + + guard networkManagerError.statusCode >= 400 && networkManagerError.statusCode <= 599 else { + Logger.debug("don't report SPK rotation failure w/ non application error") + return + } + + TSPreKeyManager.incrementPreKeyUpdateFailureCount() + default: + Logger.debug("don't report SPK rotation failure w/ non NetworkManager error: \(error)") + } + } +} diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index bb835ed0a..d5dbca936 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -371,9 +371,7 @@ NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignaling case 204: { OWSLogInfo(@"Verification code accepted."); [self storeServerAuthToken:authToken signalingKey:signalingKey]; - [TSPreKeyManager registerPreKeysWithMode:RefreshPreKeysMode_SignedAndOneTime - success:successBlock - failure:failureBlock]; + [TSPreKeyManager createPreKeysWithSuccess:successBlock failure:failureBlock]; break; } default: { diff --git a/SignalServiceKit/src/Account/TSPreKeyManager.h b/SignalServiceKit/src/Account/TSPreKeyManager.h index 19061373a..7295c20ee 100644 --- a/SignalServiceKit/src/Account/TSPreKeyManager.h +++ b/SignalServiceKit/src/Account/TSPreKeyManager.h @@ -1,29 +1,29 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "TSAccountManager.h" -typedef NS_ENUM(NSInteger, RefreshPreKeysMode) { - // Refresh the signed prekey AND the one-time prekeys. - RefreshPreKeysMode_SignedAndOneTime, - // Only refresh the signed prekey, which should happen around every 48 hours. - // - // Most users will refresh their signed prekeys much more often than their - // one-time prekeys, so we use a "signed only" mode to avoid updating the - // one-time keys in this case. - // - // We do not need a "one-time only" mode. - RefreshPreKeysMode_SignedOnly, -}; - @interface TSPreKeyManager : NSObject +#pragma mark - State Tracking + + (BOOL)isAppLockedDueToPreKeyUpdateFailures; -+ (void)registerPreKeysWithMode:(RefreshPreKeysMode)mode - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; ++ (void)incrementPreKeyUpdateFailureCount; + ++ (void)clearPreKeyUpdateFailureCount; + ++ (void)clearSignedPreKeyRecords; + +// This should only be called from the TSPreKeyManager.operationQueue ++ (void)refreshPreKeysDidSucceed; + +#pragma mark - Check/Request Initiation + ++ (void)rotateSignedPreKeyWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler; + ++ (void)createPreKeysWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler; + (void)checkPreKeys; diff --git a/SignalServiceKit/src/Account/TSPreKeyManager.m b/SignalServiceKit/src/Account/TSPreKeyManager.m index 98671deab..b3f9d5fee 100644 --- a/SignalServiceKit/src/Account/TSPreKeyManager.m +++ b/SignalServiceKit/src/Account/TSPreKeyManager.m @@ -8,9 +8,9 @@ #import "NSURLSessionDataTask+StatusCode.h" #import "OWSIdentityManager.h" #import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSRequestFactory.h" #import "TSNetworkManager.h" #import "TSStorageHeaders.h" +#import // Time before deletion of signed prekeys (measured in seconds) #define kSignedPreKeysDeletionTime (7 * kDayInterval) @@ -21,10 +21,6 @@ // How often we check prekey state on app activation. #define kPreKeyCheckFrequencySeconds (12 * kHourInterval) -// We generate 100 one-time prekeys at a time. We should replenish -// whenever ~2/3 of them have been consumed. -static const NSUInteger kEphemeralPreKeysMinimumCount = 35; - // This global should only be accessed on prekeyQueue. static NSDate *lastPreKeyCheckTimestamp = nil; @@ -40,6 +36,8 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; @implementation TSPreKeyManager +#pragma mark - State Tracking + + (BOOL)isAppLockedDueToPreKeyUpdateFailures { // Only disable message sending if we have failed more than N times @@ -56,6 +54,8 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; // Record a prekey update failure. OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; int failureCount = [primaryStorage incrementPrekeyUpdateFailureCount]; + OWSLogInfo(@"new failureCount: %d", failureCount); + if (failureCount == 1 || ![primaryStorage firstPrekeyUpdateFailureDate]) { // If this is the "first" failure, record the timestamp of that // failure. @@ -70,15 +70,29 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; [primaryStorage clearPrekeyUpdateFailureCount]; } -// We should never dispatch sync to this queue. -+ (dispatch_queue_t)prekeyQueue ++ (void)refreshPreKeysDidSucceed +{ + lastPreKeyCheckTimestamp = [NSDate new]; +} + +#pragma mark - Check/Request Initiation + ++ (NSOperationQueue *)operationQueue { static dispatch_once_t onceToken; - static dispatch_queue_t queue; + static NSOperationQueue *operationQueue; + + // PreKey state lives in two places - on the client and on the service. + // Some of our pre-key operations depend on the service state, e.g. we need to check our one-time-prekey count + // before we decide to upload new ones. This potentially entails multiple async operations, all of which should + // complete before starting any other pre-key operation. That's why a dispatch_queue is insufficient for + // coordinating PreKey operations and instead we use NSOperation's on a serial NSOperationQueue. dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.signal.prekeyQueue", NULL); + operationQueue = [NSOperationQueue new]; + operationQueue.name = @"TSPreKeyManager"; + operationQueue.maxConcurrentOperationCount = 1; }); - return queue; + return operationQueue; } + (void)checkPreKeysIfNecessary @@ -88,119 +102,83 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; } OWSAssertDebug(CurrentAppContext().isMainAppAndActive); - // Update the prekey check timestamp. - dispatch_async(TSPreKeyManager.prekeyQueue, ^{ + if (!TSAccountManager.isRegistered) { + return; + } + + SSKRefreshPreKeysOperation *refreshOperation = [SSKRefreshPreKeysOperation new]; + + __weak SSKRefreshPreKeysOperation *weakRefreshOperation = refreshOperation; + NSBlockOperation *checkIfRefreshNecessaryOperation = [NSBlockOperation blockOperationWithBlock:^{ BOOL shouldCheck = (lastPreKeyCheckTimestamp == nil - || fabs([lastPreKeyCheckTimestamp timeIntervalSinceNow]) >= kPreKeyCheckFrequencySeconds); - if (shouldCheck) { - // Optimistically mark the prekeys as checked. This - // de-bounces prekey checks. - // - // If the check or key registration fails, the prekeys - // will be marked as _NOT_ checked. - // - // Note: [TSPreKeyManager checkPreKeys] will also - // optimistically mark them as checked. This - // redundancy is fine and precludes a race - // condition. - lastPreKeyCheckTimestamp = [NSDate date]; - - if ([TSAccountManager isRegistered]) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [TSPreKeyManager checkPreKeys]; - }); - } + || fabs([lastPreKeyCheckTimestamp timeIntervalSinceNow]) >= kPreKeyCheckFrequencySeconds); + if (!shouldCheck) { + [weakRefreshOperation cancel]; } - }); -} + }]; -+ (void)registerPreKeysWithMode:(RefreshPreKeysMode)mode - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - // We use prekeyQueue to serialize this logic and ensure that only - // one thread is "registering" or "clearing" prekeys at a time. - dispatch_async(TSPreKeyManager.prekeyQueue, ^{ - // Mark the prekeys as checked every time we try to register prekeys. - lastPreKeyCheckTimestamp = [NSDate date]; + [refreshOperation addDependency:checkIfRefreshNecessaryOperation]; + + SSKRotateSignedPreKeyOperation *rotationOperation = [SSKRotateSignedPreKeyOperation new]; - RefreshPreKeysMode modeCopy = mode; + __weak SSKRotateSignedPreKeyOperation *weakRotationOperation = rotationOperation; + NSBlockOperation *checkIfRotationNecessaryOperation = [NSBlockOperation blockOperationWithBlock:^{ OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - ECKeyPair *identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair]; - - if (!identityKeyPair) { - [[OWSIdentityManager sharedManager] generateNewIdentityKey]; - identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair]; - - // Switch modes if necessary. - modeCopy = RefreshPreKeysMode_SignedAndOneTime; + SignedPreKeyRecord *_Nullable signedPreKey = [primaryStorage currentSignedPreKey]; + + BOOL shouldCheck + = !signedPreKey || fabs(signedPreKey.generatedAt.timeIntervalSinceNow) >= kSignedPreKeyRotationTime; + if (!shouldCheck) { + [weakRotationOperation cancel]; } + }]; - SignedPreKeyRecord *signedPreKey = [primaryStorage generateRandomSignedRecord]; - // Store the new signed key immediately, before it is sent to the - // service to prevent race conditions and other edge cases. - [primaryStorage storeSignedPreKey:signedPreKey.Id signedPreKeyRecord:signedPreKey]; - - NSArray *preKeys = nil; - TSRequest *request; - NSString *description; - if (modeCopy == RefreshPreKeysMode_SignedAndOneTime) { - description = @"signed and one-time prekeys"; - PreKeyRecord *lastResortPreKey = [primaryStorage getOrGenerateLastResortKey]; - preKeys = [primaryStorage generatePreKeyRecords]; - // Store the new one-time keys immediately, before they are sent to the - // service to prevent race conditions and other edge cases. - [primaryStorage storePreKeyRecords:preKeys]; - - request = [OWSRequestFactory registerPrekeysRequestWithPrekeyArray:preKeys - identityKey:identityKeyPair.publicKey - signedPreKey:signedPreKey - preKeyLastResort:lastResortPreKey]; - } else { - description = @"just signed prekey"; - request = [OWSRequestFactory registerSignedPrekeyRequestWithSignedPreKeyRecord:signedPreKey]; - } + [rotationOperation addDependency:checkIfRotationNecessaryOperation]; - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogInfo(@"Successfully registered %@.", description); + // Order matters here - if we rotated *before* refreshing, we'd risk uploading + // two SPK's in a row since RefreshPreKeysOperation can also upload a new SPK. + [checkIfRotationNecessaryOperation addDependency:refreshOperation]; - // Mark signed prekey as accepted by service. - [signedPreKey markAsAcceptedByService]; - [primaryStorage storeSignedPreKey:signedPreKey.Id signedPreKeyRecord:signedPreKey]; + NSArray *operations = + @[ checkIfRefreshNecessaryOperation, refreshOperation, checkIfRotationNecessaryOperation, rotationOperation ]; + [self.operationQueue addOperations:operations waitUntilFinished:NO]; +} - // On success, update the "current" signed prekey state. - [primaryStorage setCurrentSignedPrekeyId:signedPreKey.Id]; ++ (void)createPreKeysWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + SSKCreatePreKeysOperation *operation = [SSKCreatePreKeysOperation new]; + [self.operationQueue addOperations:@[ operation ] waitUntilFinished:YES]; + NSError *_Nullable error = operation.failingError; + if (error) { + dispatch_async(dispatch_get_main_queue(), ^{ + failureHandler(error); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ successHandler(); + }); + } + }); +} - [TSPreKeyManager clearPreKeyUpdateFailureCount]; - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - if (modeCopy == RefreshPreKeysMode_SignedAndOneTime) { - OWSProdError([OWSAnalyticsEvents errorPrekeysUpdateFailedSignedAndOnetime]); - } else { - OWSProdError([OWSAnalyticsEvents errorPrekeysUpdateFailedJustSigned]); - } - } - - // Mark the prekeys as _NOT_ checked on failure. - [self markPreKeysAsNotChecked]; ++ (void)rotateSignedPreKeyWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler +{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + SSKRotateSignedPreKeyOperation *operation = [SSKRotateSignedPreKeyOperation new]; + [self.operationQueue addOperations:@[ operation ] waitUntilFinished:YES]; + NSError *_Nullable error = operation.failingError; + if (error) { + dispatch_async(dispatch_get_main_queue(), ^{ failureHandler(error); - - NSInteger statusCode = 0; - if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; - statusCode = httpResponse.statusCode; - } - if (statusCode >= 400 && statusCode <= 599) { - // Only treat 4xx and 5xx errors from the service as failures. - // Ignore network failures, for example. - [TSPreKeyManager incrementPreKeyUpdateFailureCount]; - } - }]; + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + successHandler(); + }); + } }); } @@ -210,207 +188,85 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; return; } - // Optimistically mark the prekeys as checked. This - // de-bounces prekey checks. - // - // If the check or key registration fails, the prekeys - // will be marked as _NOT_ checked. - dispatch_async(TSPreKeyManager.prekeyQueue, ^{ - lastPreKeyCheckTimestamp = [NSDate date]; - }); - - // We want to update prekeys if either the one-time or signed prekeys need an update, so - // we check the status of both. - // - // Most users will refresh their signed prekeys much more often than their - // one-time PreKeys, so we use a "signed only" mode to avoid updating the - // one-time keys in this case. - // - // We do not need a "one-time only" mode. - TSRequest *preKeyCountRequest = [OWSRequestFactory availablePreKeysCountRequest]; - [[TSNetworkManager sharedManager] makeRequest:preKeyCountRequest - success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) { - NSString *preKeyCountKey = @"count"; - NSNumber *count = [responseObject objectForKey:preKeyCountKey]; - - BOOL didUpdatePreKeys = NO; - void (^updatePreKeys)(RefreshPreKeysMode) = ^(RefreshPreKeysMode mode) { - [self registerPreKeysWithMode:mode - success:^{ - OWSLogInfo(@"New prekeys registered with server."); - - [self clearSignedPreKeyRecords]; - } - failure:^(NSError *error) { - OWSLogWarn(@"Failed to update prekeys with the server: %@", error); - }]; - }; - - BOOL shouldUpdateOneTimePreKeys = count.integerValue <= kEphemeralPreKeysMinimumCount; - - if (shouldUpdateOneTimePreKeys) { - OWSLogInfo(@"Updating one-time and signed prekeys due to shortage of one-time prekeys."); - updatePreKeys(RefreshPreKeysMode_SignedAndOneTime); - didUpdatePreKeys = YES; - } else { - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId]; - BOOL shouldUpdateSignedPrekey = NO; - if (!currentSignedPrekeyId) { - OWSLogError(@"Couldn't find current signed prekey id"); - shouldUpdateSignedPrekey = YES; - } else { - SignedPreKeyRecord *currentRecord = - [primaryStorage loadSignedPrekeyOrNil:currentSignedPrekeyId.intValue]; - if (!currentRecord) { - OWSFailDebug(@"Couldn't find signed prekey for id: %@", currentSignedPrekeyId); - shouldUpdateSignedPrekey = YES; - } else { - shouldUpdateSignedPrekey - = fabs([currentRecord.generatedAt timeIntervalSinceNow]) >= kSignedPreKeyRotationTime; - } - } - - if (shouldUpdateSignedPrekey) { - OWSLogInfo(@"Updating signed prekey due to rotation period."); - updatePreKeys(RefreshPreKeysMode_SignedOnly); - didUpdatePreKeys = YES; - } else { - OWSLogDebug(@"Not updating prekeys."); - } - } - - if (!didUpdatePreKeys) { - // If we didn't update the prekeys, our local "current signed key" state should - // agree with the service's "current signed key" state. Let's verify that, - // since it's closely related to the issues we saw with the 2.7.0.10 release. - TSRequest *currentSignedPreKey = [OWSRequestFactory currentSignedPreKeyRequest]; - [[TSNetworkManager sharedManager] makeRequest:currentSignedPreKey - success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) { - NSString *keyIdDictKey = @"keyId"; - NSNumber *keyId = [responseObject objectForKey:keyIdDictKey]; - OWSAssertDebug(keyId); - - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId]; - - if (!keyId || !currentSignedPrekeyId || ![currentSignedPrekeyId isEqualToNumber:keyId]) { - OWSLogError(@"Local and service 'current signed prekey ids' did not match. %@ == %@ == %d.", - keyId, - currentSignedPrekeyId, - [currentSignedPrekeyId isEqualToNumber:keyId]); - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorPrekeysCurrentSignedPrekeyRequestFailed]); - } - OWSLogWarn(@"Could not retrieve current signed key from the service."); - - // Mark the prekeys as _NOT_ checked on failure. - [self markPreKeysAsNotChecked]; - }]; - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorPrekeysAvailablePrekeysRequestFailed]); - } - OWSLogError(@"Failed to retrieve the number of available prekeys."); - - // Mark the prekeys as _NOT_ checked on failure. - [self markPreKeysAsNotChecked]; - }]; -} - -+ (void)markPreKeysAsNotChecked -{ - dispatch_async(TSPreKeyManager.prekeyQueue, ^{ - lastPreKeyCheckTimestamp = nil; - }); + SSKRefreshPreKeysOperation *operation = [SSKRefreshPreKeysOperation new]; + [self.operationQueue addOperation:operation]; } + (void)clearSignedPreKeyRecords { OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - NSNumber *currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId]; - [self clearSignedPreKeyRecordsWithKeyId:currentSignedPrekeyId success:nil]; + NSNumber *_Nullable currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId]; + [self clearSignedPreKeyRecordsWithKeyId:currentSignedPrekeyId]; } -+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *)keyId success:(void (^_Nullable)(void))successHandler ++ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *_Nullable)keyId { if (!keyId) { + // currentSignedPreKeyId should only be nil before we've completed registration. + // We have this guard here for robustness, but we should never get here. OWSFailDebug(@"Ignoring request to clear signed preKeys since no keyId was specified"); return; } - // We use prekeyQueue to serialize this logic and ensure that only - // one thread is "registering" or "clearing" prekeys at a time. - dispatch_async(TSPreKeyManager.prekeyQueue, ^{ - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - SignedPreKeyRecord *currentRecord = [primaryStorage loadSignedPrekeyOrNil:keyId.intValue]; - if (!currentRecord) { - OWSFailDebug(@"Couldn't find signed prekey for id: %@", keyId); + OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; + SignedPreKeyRecord *currentRecord = [primaryStorage loadSignedPrekeyOrNil:keyId.intValue]; + if (!currentRecord) { + OWSFailDebug(@"Couldn't find signed prekey for id: %@", keyId); + } + NSArray *allSignedPrekeys = [primaryStorage loadSignedPreKeys]; + NSArray *oldSignedPrekeys + = (currentRecord != nil ? [self removeCurrentRecord:currentRecord fromRecords:allSignedPrekeys] + : allSignedPrekeys); + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateStyle = NSDateFormatterMediumStyle; + dateFormatter.timeStyle = NSDateFormatterMediumStyle; + dateFormatter.locale = [NSLocale systemLocale]; + + // Sort the signed prekeys in ascending order of generation time. + oldSignedPrekeys = [oldSignedPrekeys sortedArrayUsingComparator:^NSComparisonResult( + SignedPreKeyRecord *_Nonnull left, SignedPreKeyRecord *_Nonnull right) { + return [left.generatedAt compare:right.generatedAt]; + }]; + + NSUInteger oldSignedPreKeyCount = oldSignedPrekeys.count; + + int oldAcceptedSignedPreKeyCount = 0; + for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { + if (signedPrekey.wasAcceptedByService) { + oldAcceptedSignedPreKeyCount++; } - NSArray *allSignedPrekeys = [primaryStorage loadSignedPreKeys]; - NSArray *oldSignedPrekeys - = (currentRecord != nil ? [self removeCurrentRecord:currentRecord fromRecords:allSignedPrekeys] - : allSignedPrekeys); - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.dateStyle = NSDateFormatterMediumStyle; - dateFormatter.timeStyle = NSDateFormatterMediumStyle; - dateFormatter.locale = [NSLocale systemLocale]; - - // Sort the signed prekeys in ascending order of generation time. - oldSignedPrekeys = [oldSignedPrekeys sortedArrayUsingComparator:^NSComparisonResult( - SignedPreKeyRecord *_Nonnull left, SignedPreKeyRecord *_Nonnull right) { - return [left.generatedAt compare:right.generatedAt]; - }]; - - NSUInteger oldSignedPreKeyCount = oldSignedPrekeys.count; - - int oldAcceptedSignedPreKeyCount = 0; - for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { - if (signedPrekey.wasAcceptedByService) { - oldAcceptedSignedPreKeyCount++; - } + } + + // Iterate the signed prekeys in ascending order so that we try to delete older keys first. + for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { + // Always keep at least 3 keys, accepted or otherwise. + if (oldSignedPreKeyCount <= 3) { + continue; } - // Iterate the signed prekeys in ascending order so that we try to delete older keys first. - for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { - // Always keep at least 3 keys, accepted or otherwise. - if (oldSignedPreKeyCount <= 3) { - continue; - } + // Never delete signed prekeys until they are N days old. + if (fabs([signedPrekey.generatedAt timeIntervalSinceNow]) < kSignedPreKeysDeletionTime) { + continue; + } - // Never delete signed prekeys until they are N days old. - if (fabs([signedPrekey.generatedAt timeIntervalSinceNow]) < kSignedPreKeysDeletionTime) { + // We try to keep a minimum of 3 "old, accepted" signed prekeys. + if (signedPrekey.wasAcceptedByService) { + if (oldAcceptedSignedPreKeyCount <= 3) { continue; - } - - // We try to keep a minimum of 3 "old, accepted" signed prekeys. - if (signedPrekey.wasAcceptedByService) { - if (oldAcceptedSignedPreKeyCount <= 3) { - continue; - } else { - oldAcceptedSignedPreKeyCount--; - } - } - - if (signedPrekey.wasAcceptedByService) { - OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldAcceptedSignedPrekey]); } else { - OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldUnacceptedSignedPrekey]); + oldAcceptedSignedPreKeyCount--; } - - oldSignedPreKeyCount--; - [primaryStorage removeSignedPreKey:signedPrekey.Id]; } - if (successHandler) { - successHandler(); + if (signedPrekey.wasAcceptedByService) { + OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldAcceptedSignedPrekey]); + } else { + OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldUnacceptedSignedPrekey]); } - }); + + oldSignedPreKeyCount--; + [primaryStorage removeSignedPreKey:signedPrekey.Id]; + } } + (NSArray *)removeCurrentRecord:(SignedPreKeyRecord *)currentRecord fromRecords:(NSArray *)allRecords { diff --git a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift b/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift index 301965b6c..6e2c309c0 100644 --- a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift +++ b/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift @@ -390,12 +390,10 @@ class CDSBatchOperation: OWSOperation { func parseAndDecrypt(response: Any?, remoteAttestation: RemoteAttestation) throws -> Data { - guard let responseDict = response as? [String: AnyObject] else { + guard let params = ParamParser(responseObject: response) else { throw ContactDiscoveryError.parseError(description: "missing response dict") } - let params = ParamParser(dictionary: responseDict) - let cipherText = try params.requiredBase64EncodedData(key: "data") let initializationVector = try params.requiredBase64EncodedData(key: "iv") let authTag = try params.requiredBase64EncodedData(key: "mac") diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 8267d5ce0..e11b8584f 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -732,17 +732,17 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // // Only try to update the signed prekey; updating it is sufficient to // re-enable message sending. - [TSPreKeyManager registerPreKeysWithMode:RefreshPreKeysMode_SignedOnly - success:^{ + [TSPreKeyManager + rotateSignedPreKeyWithSuccess:^{ OWSLogInfo(@"New prekeys registered with server."); + NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(); + [error setIsRetryable:YES]; + return failureHandler(error); } failure:^(NSError *error) { OWSLogWarn(@"Failed to update prekeys with the server: %@", error); + return failureHandler(error); }]; - - NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(); - [error setIsRetryable:YES]; - return failureHandler(error); } if (remainingAttemptsParam <= 0) { diff --git a/SignalServiceKit/src/Network/API/NetworkManager.swift b/SignalServiceKit/src/Network/API/NetworkManager.swift new file mode 100644 index 000000000..17a8acb1c --- /dev/null +++ b/SignalServiceKit/src/Network/API/NetworkManager.swift @@ -0,0 +1,46 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +enum NetworkManagerError: Error { + /// Wraps TSNetworkManager failure callback params in a single throwable error + case taskError(task: URLSessionDataTask, underlyingError: Error) +} + +extension NetworkManagerError { + var isNetworkError: Bool { + switch self { + case .taskError(_, let underlyingError): + return IsNSErrorNetworkFailure(underlyingError) + } + } + + var statusCode: Int { + switch self { + case .taskError(let task, _): + return task.statusCode() + } + } +} + +extension TSNetworkManager { + func makePromise(request: TSRequest) -> Promise<(task: URLSessionDataTask, responseObject: Any?)> { + let (promise, fulfill, reject) = Promise<(task: URLSessionDataTask, responseObject: Any?)>.pending() + + self.makeRequest(request, + success: { task, responseObject in + fulfill((task: task, responseObject: responseObject)) + }, + failure: { task, error in + let nmError = NetworkManagerError.taskError(task: task, underlyingError: error) + let nsError: NSError = nmError as NSError + nsError.isRetryable = (error as NSError).isRetryable + reject(nsError) + }) + + return promise + } +} diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h index 29a8a489a..b1dd679a5 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h @@ -67,8 +67,7 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo + (TSRequest *)registerPrekeysRequestWithPrekeyArray:(NSArray *)prekeys identityKey:(NSData *)identityKeyPublic - signedPreKey:(SignedPreKeyRecord *)signedPreKey - preKeyLastResort:(PreKeyRecord *)preKeyLastResort; + signedPreKey:(SignedPreKeyRecord *)signedPreKey; + (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair enclaveId:(NSString *)enclaveId diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index 67de23c76..3e18e5024 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -235,12 +235,10 @@ NS_ASSUME_NONNULL_BEGIN + (TSRequest *)registerPrekeysRequestWithPrekeyArray:(NSArray *)prekeys identityKey:(NSData *)identityKeyPublic signedPreKey:(SignedPreKeyRecord *)signedPreKey - preKeyLastResort:(PreKeyRecord *)preKeyLastResort { OWSAssertDebug(prekeys.count > 0); OWSAssertDebug(identityKeyPublic.length > 0); OWSAssertDebug(signedPreKey); - OWSAssertDebug(preKeyLastResort); NSString *path = textSecureKeysAPI; NSString *publicIdentityKey = [[identityKeyPublic prependKeyType] base64EncodedStringWithOptions:0]; @@ -252,7 +250,6 @@ NS_ASSUME_NONNULL_BEGIN method:@"PUT" parameters:@{ @"preKeys" : serializedPrekeyList, - @"lastResortKey" : [self dictionaryFromPreKey:preKeyLastResort], @"signedPreKey" : [self dictionaryFromSignedPreKey:signedPreKey], @"identityKey" : publicIdentityKey }]; diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.h b/SignalServiceKit/src/Network/API/TSNetworkManager.h index 406588e91..be2adfdba 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.h +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.h @@ -28,7 +28,7 @@ typedef void (^TSNetworkManagerFailure)(NSURLSessionDataTask *task, NSError *err - (void)makeRequest:(TSRequest *)request completionQueue:(dispatch_queue_t)completionQueue success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure NS_SWIFT_NAME(makeRequest(_:shouldCompleteOnMainQueue:success:failure:)); + failure:(TSNetworkManagerFailure)failure NS_SWIFT_NAME(makeRequest(_:completionQueue:success:failure:)); @end diff --git a/SignalServiceKit/src/Network/SignalServiceClient.swift b/SignalServiceKit/src/Network/SignalServiceClient.swift new file mode 100644 index 000000000..978e72435 --- /dev/null +++ b/SignalServiceKit/src/Network/SignalServiceClient.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit + +protocol SignalServiceClient { + func getAvailablePreKeys() -> Promise + func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise + func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise +} + +/// Based on libsignal-service-java's PushServiceSocket class +class SignalServiceRestClient: SignalServiceClient { + + var networkManager: TSNetworkManager { + return TSNetworkManager.shared() + } + + func unexpectedServerResponseError() -> Error { + return OWSErrorMakeUnableToProcessServerResponseError() + } + + func getAvailablePreKeys() -> Promise { + Logger.debug("") + + let request = OWSRequestFactory.availablePreKeysCountRequest() + return networkManager.makePromise(request: request).then { (_, responseObject) -> Int in + Logger.debug("got response") + guard let params = ParamParser(responseObject: responseObject) else { + throw self.unexpectedServerResponseError() + } + + let count: Int = try! params.required(key: "count") + + return count + } + } + + func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { + Logger.debug("") + + let request = OWSRequestFactory.registerPrekeysRequest(withPrekeyArray: preKeyRecords, identityKey: identityKey, signedPreKey: signedPreKeyRecord) + return networkManager.makePromise(request: request).asVoid() + } + + public func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise { + Logger.debug("") + + let request = OWSRequestFactory.registerSignedPrekeyRequest(with: signedPreKey) + return networkManager.makePromise(request: request).asVoid() + } +} diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h index df892a8bc..40f0f0c92 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h +++ b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h @@ -7,8 +7,7 @@ @interface OWSPrimaryStorage (PreKeyStore) -- (NSArray *)generatePreKeyRecords; -- (PreKeyRecord *)getOrGenerateLastResortKey; -- (void)storePreKeyRecords:(NSArray *)preKeyRecords; +- (NSArray *)generatePreKeyRecords; +- (void)storePreKeyRecords:(NSArray *)preKeyRecords NS_SWIFT_NAME(storePreKeyRecords(_:)); @end diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m index b8737de04..4a93685c5 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m +++ b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m @@ -16,19 +16,7 @@ @implementation OWSPrimaryStorage (PreKeyStore) -- (PreKeyRecord *)getOrGenerateLastResortKey -{ - if ([self containsPreKey:kPreKeyOfLastResortId]) { - return [self loadPreKey:kPreKeyOfLastResortId]; - } else { - PreKeyRecord *lastResort = - [[PreKeyRecord alloc] initWithId:kPreKeyOfLastResortId keyPair:[Curve25519 generateKeyPair]]; - [self storePreKey:kPreKeyOfLastResortId preKeyRecord:lastResort]; - return lastResort; - } -} - -- (NSArray *)generatePreKeyRecords +- (NSArray *)generatePreKeyRecords; { NSMutableArray *preKeyRecords = [NSMutableArray array]; @@ -52,7 +40,7 @@ return preKeyRecords; } -- (void)storePreKeyRecords:(NSArray *)preKeyRecords +- (void)storePreKeyRecords:(NSArray *)preKeyRecords { for (PreKeyRecord *record in preKeyRecords) { [self.dbReadWriteConnection setObject:record diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h index 687d6f6cf..752581f57 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h +++ b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h @@ -19,6 +19,7 @@ extern NSString *const OWSPrimaryStorageSignedPreKeyStoreCollection; // Returns nil if no current signed prekey id is found. - (nullable NSNumber *)currentSignedPrekeyId; - (void)setCurrentSignedPrekeyId:(int)value; +- (nullable SignedPreKeyRecord *)currentSignedPreKey; #pragma mark - Prekey update failures diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m index 84cfbbfc0..48eef0a75 100644 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m +++ b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m @@ -105,12 +105,31 @@ NSString *const OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId = @"currentSigne inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; } +- (nullable SignedPreKeyRecord *)currentSignedPreKey +{ + __block SignedPreKeyRecord *_Nullable currentRecord; + + [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + NSNumber *_Nullable preKeyId = [transaction objectForKey:OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId + inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; + + if (preKeyId == nil) { + return; + } + + currentRecord = + [transaction objectForKey:preKeyId.stringValue inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; + }]; + + return currentRecord; +} + #pragma mark - Prekey update failures - (int)prekeyUpdateFailureCount { - NSNumber *value = [self.dbReadConnection objectForKey:OWSPrimaryStorageKeyPrekeyUpdateFailureCount - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; + NSNumber *_Nullable value = [self.dbReadConnection objectForKey:OWSPrimaryStorageKeyPrekeyUpdateFailureCount + inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; // Will default to zero. return [value intValue]; } diff --git a/SignalServiceKit/src/Util/OWSOperation.m b/SignalServiceKit/src/Util/OWSOperation.m index 4ebd055a4..e416b7f26 100644 --- a/SignalServiceKit/src/Util/OWSOperation.m +++ b/SignalServiceKit/src/Util/OWSOperation.m @@ -48,12 +48,15 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; // Called one time only - (nullable NSError *)checkForPreconditionError { + // OWSOperation have a notion of failure, which is inferred by the presence of a `failingError`. + // + // By default, any failing dependency cascades that failure to it's dependent. + // If you'd like different behavior, override this method (`checkForPreconditionError`) without calling `super`. for (NSOperation *dependency in self.dependencies) { if (![dependency isKindOfClass:[OWSOperation class]]) { - NSString *errorDescription = - [NSString stringWithFormat:@"%@ unknown dependency: %@", self.logTag, dependency.class]; - - return OWSErrorMakeAssertionError(errorDescription); + // Native operations, like NSOperation and NSBlockOperation have no notion of "failure". + // So there's no `failingError` to cascade. + continue; } OWSOperation *dependentOperation = (OWSOperation *)dependency; @@ -107,6 +110,11 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; return; } + if (self.isCancelled) { + [self reportCancelled]; + return; + } + [self run]; } diff --git a/SignalServiceKit/src/Util/ParamParser.swift b/SignalServiceKit/src/Util/ParamParser.swift index 9fea969f7..9a470ebb6 100644 --- a/SignalServiceKit/src/Util/ParamParser.swift +++ b/SignalServiceKit/src/Util/ParamParser.swift @@ -34,6 +34,14 @@ public class ParamParser { self.dictionary = dictionary } + public convenience init?(responseObject: Any?) { + guard let responseDict = responseObject as? [String: AnyObject] else { + return nil + } + + self.init(dictionary: responseDict) + } + // MARK: Errors public enum ParseError: Error {