Merge branch 'mkirk/debounce-prekey-checks2'

pull/1/head
Michael Kirk 7 years ago
commit e9f0c31d49

@ -1 +1 @@
Subproject commit e9fb539a0061d7e9f87ba48a724f99092232b41b
Subproject commit c20b80e3952ccdb215831c4063c79d479d1cd702

@ -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())

@ -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}]);

@ -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 <XCTest/XCTest.h>
@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) {

@ -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))

@ -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)

@ -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<Int> {
return serviceClient.getAvailablePreKeys()
}
public func setPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void> {
return serviceClient.registerPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}
public func setSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise<Void> {
return serviceClient.setCurrentSignedPreKey(signedPreKey)
}
}

@ -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()
}
}

@ -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<Void> 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)")
}
}
}

@ -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)")
}
}
}

@ -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: {

@ -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;

@ -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 <SignalServiceKit/SignalServiceKit-Swift.h>
// 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];
RefreshPreKeysMode modeCopy = mode;
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
ECKeyPair *identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
SSKRotateSignedPreKeyOperation *rotationOperation = [SSKRotateSignedPreKeyOperation new];
if (!identityKeyPair) {
[[OWSIdentityManager sharedManager] generateNewIdentityKey];
identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
__weak SSKRotateSignedPreKeyOperation *weakRotationOperation = rotationOperation;
NSBlockOperation *checkIfRotationNecessaryOperation = [NSBlockOperation blockOperationWithBlock:^{
OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager];
SignedPreKeyRecord *_Nullable signedPreKey = [primaryStorage currentSignedPreKey];
// Switch modes if necessary.
modeCopy = RefreshPreKeysMode_SignedAndOneTime;
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<NSOperation *> *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 {

@ -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")

@ -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) {

@ -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
}
}

@ -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

@ -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
}];

@ -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

@ -0,0 +1,54 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
protocol SignalServiceClient {
func getAvailablePreKeys() -> Promise<Int>
func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void>
func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise<Void>
}
/// 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<Int> {
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<Void> {
Logger.debug("")
let request = OWSRequestFactory.registerPrekeysRequest(withPrekeyArray: preKeyRecords, identityKey: identityKey, signedPreKey: signedPreKeyRecord)
return networkManager.makePromise(request: request).asVoid()
}
public func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise<Void> {
Logger.debug("")
let request = OWSRequestFactory.registerSignedPrekeyRequest(with: signedPreKey)
return networkManager.makePromise(request: request).asVoid()
}
}

@ -7,8 +7,7 @@
@interface OWSPrimaryStorage (PreKeyStore) <PreKeyStore>
- (NSArray *)generatePreKeyRecords;
- (PreKeyRecord *)getOrGenerateLastResortKey;
- (void)storePreKeyRecords:(NSArray *)preKeyRecords;
- (NSArray<PreKeyRecord *> *)generatePreKeyRecords;
- (void)storePreKeyRecords:(NSArray<PreKeyRecord *> *)preKeyRecords NS_SWIFT_NAME(storePreKeyRecords(_:));
@end

@ -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<PreKeyRecord *> *)generatePreKeyRecords;
{
NSMutableArray *preKeyRecords = [NSMutableArray array];
@ -52,7 +40,7 @@
return preKeyRecords;
}
- (void)storePreKeyRecords:(NSArray *)preKeyRecords
- (void)storePreKeyRecords:(NSArray<PreKeyRecord *> *)preKeyRecords
{
for (PreKeyRecord *record in preKeyRecords) {
[self.dbReadWriteConnection setObject:record

@ -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

@ -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];
}

@ -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];
}

@ -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 {

Loading…
Cancel
Save