]>
Commit | Line | Data |
---|---|---|
866f8763 A |
1 | /* |
2 | * Copyright (c) 2016 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | #import "CKKSKeychainView.h" | |
25 | #import "CKKSGroupOperation.h" | |
26 | #import "CKKSSynchronizeOperation.h" | |
27 | #import "CKKSFetchAllRecordZoneChangesOperation.h" | |
28 | #import "CKKSScanLocalItemsOperation.h" | |
29 | #import "keychain/ckks/CloudKitCategories.h" | |
30 | ||
31 | #if OCTAGON | |
32 | ||
33 | @interface CKKSSynchronizeOperation () | |
34 | @property int32_t restartCount; | |
35 | @end | |
36 | ||
37 | @implementation CKKSSynchronizeOperation | |
38 | ||
39 | - (instancetype)init { | |
40 | return nil; | |
41 | } | |
42 | - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks { | |
43 | if(self = [super init]) { | |
44 | _ckks = ckks; | |
45 | _restartCount = 0; | |
46 | } | |
47 | return self; | |
48 | } | |
49 | ||
50 | - (void)groupStart { | |
51 | __weak __typeof(self) weakSelf = self; | |
52 | ||
53 | /* | |
54 | * Synchronizations (or resynchronizations) are complicated beasts. We will: | |
55 | * | |
56 | * 1. Finish processing the outgoing queue. You can't be in-sync with cloudkit if you have an update that hasn't propagated. | |
57 | * 2. Kick off a normal CloudKit fetch. | |
58 | * 3. Process the incoming queue as normal. | |
59 | * (Note that this requires the keybag to be unlocked.) | |
60 | * | |
61 | * So far, this is normal operation. Now: | |
62 | * | |
63 | * 4. Start another CloudKit fetch, giving it the nil change tag. This fetches all objects in CloudKit. | |
64 | * 4a. Compare those objects against our local mirror. If any discrepancies, this is a bug. | |
65 | * 4b. All conflicts: CloudKit data wins. | |
66 | * 4c. All items we have that CloudKit doesn't: delete locally. | |
67 | * | |
68 | * 5. Process the incoming queue again. This should be empty. | |
69 | * 6. Scan the local keychain for items which exist locally but are not in CloudKit. Upload them. | |
70 | * 7. If there are any such items in 6, restart the sync. | |
71 | */ | |
72 | ||
73 | // Promote to strong reference | |
74 | CKKSKeychainView* ckks = self.ckks; | |
75 | ||
76 | // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety. | |
77 | [ckks dispatchSync: ^bool{ | |
78 | if(self.cancelled) { | |
79 | ckksnotice("ckksresync", ckks, "CKKSSynchronizeOperation cancelled, quitting"); | |
80 | return false; | |
81 | } | |
82 | ||
83 | ckks.lastSynchronizeOperation = self; | |
84 | ||
85 | uint32_t steps = 7; | |
86 | ||
87 | ckksinfo("ckksresync", ckks, "Beginning resynchronize (attempt %u)", self.restartCount); | |
88 | ||
89 | CKOperationGroup* operationGroup = [CKOperationGroup CKKSGroupWithName:@"ckks-resync"]; | |
90 | ||
91 | // Step 1 | |
92 | CKKSOutgoingQueueOperation* outgoingOp = [ckks processOutgoingQueue: operationGroup]; | |
93 | outgoingOp.name = [NSString stringWithFormat: @"resync-step%u-outgoing", self.restartCount * steps + 1]; | |
94 | [self dependOnBeforeGroupFinished:outgoingOp]; | |
95 | ||
96 | // Step 2 | |
97 | CKKSFetchAllRecordZoneChangesOperation* fetchOp = [[CKKSFetchAllRecordZoneChangesOperation alloc] initWithCKKSKeychainView:ckks ckoperationGroup:operationGroup]; | |
98 | fetchOp.name = [NSString stringWithFormat: @"resync-step%u-fetch", self.restartCount * steps + 2]; | |
99 | [fetchOp addSuccessDependency: outgoingOp]; | |
100 | [self runBeforeGroupFinished: fetchOp]; | |
101 | ||
102 | // Step 3 | |
103 | CKKSIncomingQueueOperation* incomingOp = [[CKKSIncomingQueueOperation alloc] initWithCKKSKeychainView:ckks errorOnClassAFailure:true]; | |
104 | incomingOp.name = [NSString stringWithFormat: @"resync-step%u-incoming", self.restartCount * steps + 3]; | |
105 | [incomingOp addSuccessDependency:fetchOp]; | |
106 | [self runBeforeGroupFinished:incomingOp]; | |
107 | ||
108 | // Now, get serious: | |
109 | ||
110 | // Step 4 | |
111 | CKKSFetchAllRecordZoneChangesOperation* fetchAllOp = [[CKKSFetchAllRecordZoneChangesOperation alloc] initWithCKKSKeychainView:ckks ckoperationGroup:operationGroup]; | |
112 | fetchAllOp.resync = true; | |
113 | fetchAllOp.name = [NSString stringWithFormat: @"resync-step%u-fetchAll", self.restartCount * steps + 4]; | |
114 | [fetchAllOp addSuccessDependency: incomingOp]; | |
115 | [self runBeforeGroupFinished: fetchAllOp]; | |
116 | ||
117 | // Step 5 | |
118 | CKKSIncomingQueueOperation* incomingResyncOp = [[CKKSIncomingQueueOperation alloc] initWithCKKSKeychainView:ckks errorOnClassAFailure:true]; | |
119 | incomingResyncOp.name = [NSString stringWithFormat: @"resync-step%u-incoming", self.restartCount * steps + 5]; | |
120 | [incomingResyncOp addSuccessDependency: fetchAllOp]; | |
121 | [self runBeforeGroupFinished:incomingResyncOp]; | |
122 | ||
123 | // Step 6 | |
124 | CKKSScanLocalItemsOperation* scan = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:ckks ckoperationGroup:operationGroup]; | |
125 | scan.name = [NSString stringWithFormat: @"resync-step%u-scan", self.restartCount * steps + 6]; | |
126 | [scan addSuccessDependency: incomingResyncOp]; | |
127 | [self runBeforeGroupFinished: scan]; | |
128 | ||
129 | // Step 7: | |
130 | CKKSResultOperation* restart = [[CKKSResultOperation alloc] init]; | |
131 | restart.name = [NSString stringWithFormat: @"resync-step%u-consider-restart", self.restartCount * steps + 7]; | |
132 | [restart addExecutionBlock:^{ | |
133 | __strong __typeof(weakSelf) strongSelf = weakSelf; | |
134 | if(!strongSelf) { | |
135 | secerror("ckksresync: received callback for released object"); | |
136 | return; | |
137 | } | |
138 | ||
139 | if(scan.recordsFound > 0) { | |
140 | if(strongSelf.restartCount >= 3) { | |
141 | // we've restarted too many times. Fail and stop. | |
142 | ckkserror("ckksresync", ckks, "restarted synchronization too often; Failing"); | |
143 | strongSelf.error = [NSError errorWithDomain:@"securityd" | |
144 | code:2 | |
145 | userInfo:@{NSLocalizedDescriptionKey: @"resynchronization restarted too many times; churn in database?"}]; | |
146 | } else { | |
147 | // restart the sync operation. | |
148 | strongSelf.restartCount += 1; | |
149 | ckkserror("ckksresync", ckks, "restarting synchronization operation due to new local items"); | |
150 | [strongSelf groupStart]; | |
151 | } | |
152 | } | |
153 | }]; | |
154 | ||
155 | [restart addSuccessDependency: scan]; | |
156 | [self runBeforeGroupFinished: restart]; | |
157 | ||
158 | return true; | |
159 | }]; | |
160 | } | |
161 | ||
162 | @end; | |
163 | ||
164 | #endif |