#if OCTAGON
+#import <CloudKit/CloudKit.h>
+#import <CloudKit/CloudKit_Private.h>
+
#import "keychain/ckks/CKKSKeychainView.h"
#import "keychain/ckks/CKKSCurrentKeyPointer.h"
#import "keychain/ckks/CKKSKey.h"
#import "keychain/ckks/CKKSHealTLKSharesOperation.h"
#import "keychain/ckks/CKKSGroupOperation.h"
-#import "keychain/ckks/CKKSTLKShare.h"
+#import "keychain/ckks/CKKSTLKShareRecord.h"
+#import "keychain/ot/ObjCImprovements.h"
+
+#import "CKKSPowerCollection.h"
@interface CKKSHealTLKSharesOperation ()
@property NSBlockOperation* cloudkitModifyOperationFinished;
* Attempt to figure out what it is, and what we can do about it.
*/
- __weak __typeof(self) weakSelf = self;
+ WEAKIFY(self);
CKKSKeychainView* ckks = self.ckks;
if(!ckks) {
NSError* error = nil;
- CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:ckks.zoneID];
+ CKKSCurrentKeySet* keyset = [CKKSCurrentKeySet loadForZone:ckks.zoneID];
if(keyset.error) {
self.error = keyset.error;
ckksnotice("ckksshare", ckks, "Key set is %@", keyset);
}
+ [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:ckks.zoneName];
+
// Okay! Perform the checks.
if(![keyset.tlk loadKeyMaterialFromKeychain:&error] || error) {
// Well, that's no good. We can't share a TLK we don't have.
if([ckks.lockStateTracker isLockedError: error]) {
ckkserror("ckksshare", ckks, "Keychain is locked: can't fix shares yet: %@", error);
- [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
+ [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReadyPendingUnlock withError:nil];
} else {
// TODO go to waitfortlk
ckkserror("ckksshare", ckks, "couldn't load current tlk from keychain: %@", error);
return true;
}
- NSSet<CKKSTLKShare*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
+ NSSet<CKKSTLKShareRecord*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
error:&error];
if(error) {
ckkserror("ckksshare", ckks, "Unable to create shares: %@", error);
+ [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
return false;
}
return true;
}
+ // Let's double-check: if we upload these TLKShares, will the world be right?
+ BOOL newSharesSufficient = [ckks _onqueueAreNewSharesSufficient:newShares
+ currentTLK:keyset.tlk
+ error:&error];
+ if(!newSharesSufficient || error) {
+ ckksnotice("ckksshare", ckks, "New shares won't resolve the share issue; erroring to avoid infinite loops");
+ [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:error];
+ return true;
+ }
+
// Fire up our CloudKit operation!
NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
- for(CKKSTLKShare* share in newShares) {
+ for(CKKSTLKShareRecord* share in newShares) {
CKRecord* record = [share CKRecordWithZoneID:ckks.zoneID];
[recordsToSave addObject: record];
attemptedRecords[record.recordID] = record;
recordIDsToDelete:recordIDsToDelete];
modifyRecordsOp.atomic = YES;
modifyRecordsOp.longLived = NO;
- modifyRecordsOp.timeoutIntervalForRequest = 10;
- modifyRecordsOp.qualityOfService = NSQualityOfServiceUtility; // relatively important. Use Utility.
+
+ // very important: get the TLKShares off-device ASAP
+ modifyRecordsOp.configuration.automaticallyRetryNetworkFailures = NO;
+ modifyRecordsOp.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
+ modifyRecordsOp.configuration.isCloudKitSupportOperation = YES;
+
modifyRecordsOp.group = self.ckoperationGroup;
ckksnotice("ckksshare", ckks, "Operation group is %@", self.ckoperationGroup);
modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
- __strong __typeof(weakSelf) strongSelf = weakSelf;
- __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
+ STRONGIFY(self);
+ CKKSKeychainView* blockCKKS = self.ckks;
// These should all fail or succeed as one. Do the hard work in the records completion block.
if(!error) {
- ckksinfo("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
+ ckksnotice("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
} else {
ckkserror("ckksshare", blockCKKS, "error on row: %@ %@", record.recordID, error);
}
};
modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
- __strong __typeof(weakSelf) strongSelf = weakSelf;
- __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
- if(!strongSelf) {
+ STRONGIFY(self);
+ CKKSKeychainView* strongCKKS = self.ckks;
+ if(!self) {
secerror("ckks: received callback for released object");
return;
}
// Save the new CKRecords to the database
for(CKRecord* record in savedRecords) {
- CKKSTLKShare* savedShare = [[CKKSTLKShare alloc] initWithCKRecord:record];
- [savedShare saveToDatabase:&localerror];
+ CKKSTLKShareRecord* savedShare = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
+ bool saved = [savedShare saveToDatabase:&localerror];
- if(localerror) {
+ if(!saved || localerror != nil) {
+ // erroring means we were unable to save the new TLKShare records to the database. This will cause us to try to reupload them. Fail.
// No recovery from this, really...
ckkserror("ckksshare", strongCKKS, "Couldn't save new TLKShare record to database: %@", localerror);
- localerror = nil;
+ [strongCKKS _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError: localerror];
+ return true;
+
} else {
ckksnotice("ckksshare", strongCKKS, "Successfully completed upload for %@", savedShare);
}
}];
// Notify that we're done
- [strongSelf.operationQueue addOperation: strongSelf.cloudkitModifyOperationFinished];
+ [self.operationQueue addOperation: self.cloudkitModifyOperationFinished];
};
[ckks.database addOperation: modifyRecordsOp];