]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSZoneChangeFetcher.m
Security-58286.41.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSZoneChangeFetcher.m
1 /*
2 * Copyright (c) 2017 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 <Foundation/Foundation.h>
25
26 #if OCTAGON
27
28 #import <dispatch/dispatch.h>
29
30 #import "keychain/ckks/CKKSZoneChangeFetcher.h"
31 #import "keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h"
32 #import "keychain/ckks/CKKSKeychainView.h"
33 #import "keychain/ckks/CKKSNearFutureScheduler.h"
34 #import "keychain/ckks/CloudKitCategories.h"
35
36 CKKSFetchBecause* const CKKSFetchBecauseAPNS = (CKKSFetchBecause*) @"apns";
37 CKKSFetchBecause* const CKKSFetchBecauseAPIFetchRequest = (CKKSFetchBecause*) @"api";
38 CKKSFetchBecause* const CKKSFetchBecauseCurrentItemFetchRequest = (CKKSFetchBecause*) @"currentitemcheck";
39 CKKSFetchBecause* const CKKSFetchBecauseInitialStart = (CKKSFetchBecause*) @"initialfetch";
40 CKKSFetchBecause* const CKKSFetchBecauseSecuritydRestart = (CKKSFetchBecause*) @"restart";
41 CKKSFetchBecause* const CKKSFetchBecausePreviousFetchFailed = (CKKSFetchBecause*) @"fetchfailed";
42 CKKSFetchBecause* const CKKSFetchBecauseKeyHierarchy = (CKKSFetchBecause*) @"keyhierarchy";
43 CKKSFetchBecause* const CKKSFetchBecauseTesting = (CKKSFetchBecause*) @"testing";
44
45 @interface CKKSZoneChangeFetcher ()
46 @property NSString* name;
47 @property dispatch_queue_t queue;
48
49 @property CKKSFetchAllRecordZoneChangesOperation* currentFetch;
50 @property CKKSResultOperation* currentProcessResult;
51
52 @property NSMutableSet<CKKSFetchBecause*>* currentFetchReasons;
53 @property bool newRequests; // true if there's someone pending on successfulFetchDependency
54 @property bool newResyncRequests; // true if someone asked for a refetch operation
55 @property CKKSResultOperation* successfulFetchDependency;
56
57 @property CKKSNearFutureScheduler* fetchScheduler;
58
59 @property CKKSResultOperation* holdOperation;
60 @end
61
62 @implementation CKKSZoneChangeFetcher
63
64 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks {
65 if((self = [super init])) {
66 _ckks = ckks;
67 _zoneID = ckks.zoneID;
68
69 _currentFetchReasons = [[NSMutableSet alloc] init];
70
71 _name = [NSString stringWithFormat:@"zone-change-fetcher-%@", _zoneID.zoneName];
72 _queue = dispatch_queue_create([_name UTF8String], DISPATCH_QUEUE_SERIAL);
73 [self newSuccesfulFetchDependency];
74
75 _newRequests = false;
76
77 // If we're testing, for the initial delay, use 0.2 second. Otherwise, 2s.
78 dispatch_time_t initialDelay = (SecCKKSTestsEnabled() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
79
80 // If we're testing, for the initial delay, use 2 second. Otherwise, 30s.
81 dispatch_time_t continuingDelay = (SecCKKSTestsEnabled() ? 2 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
82
83 __weak __typeof(self) weakSelf = self;
84 _fetchScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat:@"zone-change-fetch-scheduler-%@", self.zoneID.zoneName]
85 initialDelay:initialDelay
86 continuingDelay:continuingDelay
87 keepProcessAlive:false
88 block:^{
89 [weakSelf maybeCreateNewFetch];
90 }];
91 }
92 return self;
93 }
94
95 - (NSString*)description {
96 NSDate* nextFetchAt = self.fetchScheduler.nextFireTime;
97 if(nextFetchAt) {
98 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
99 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
100 return [NSString stringWithFormat: @"<CKKSZoneChangeFetcher(%@): next fetch at %@", self.name, [dateFormatter stringFromDate: nextFetchAt]];
101 } else {
102 return [NSString stringWithFormat: @"<CKKSZoneChangeFetcher(%@): no pending fetches", self.name];
103 }
104 }
105
106 - (CKKSResultOperation*)requestSuccessfulFetch:(CKKSFetchBecause*)why {
107 return [self requestSuccessfulFetch:why resync:false];
108 }
109
110 - (CKKSResultOperation*)requestSuccessfulResyncFetch:(CKKSFetchBecause*)why {
111 return [self requestSuccessfulFetch:why resync:true];
112 }
113
114 - (CKKSResultOperation*)requestSuccessfulFetch:(CKKSFetchBecause*)why resync:(bool)resync {
115 __block CKKSResultOperation* dependency = nil;
116 dispatch_sync(self.queue, ^{
117 dependency = self.successfulFetchDependency;
118 self.newRequests = true;
119 self.newResyncRequests |= resync;
120 [self.currentFetchReasons addObject: why];
121
122 [self.fetchScheduler trigger];
123 });
124
125 return dependency;
126 }
127
128 -(void)maybeCreateNewFetch {
129 dispatch_sync(self.queue, ^{
130 if(self.newRequests &&
131 (self.currentFetch == nil || [self.currentFetch isFinished]) &&
132 (self.currentProcessResult == nil || [self.currentProcessResult isFinished])) {
133 [self _onqueueCreateNewFetch];
134 }
135 });
136 }
137
138 -(void)_onqueueCreateNewFetch {
139 dispatch_assert_queue(self.queue);
140
141 __weak __typeof(self) weakSelf = self;
142
143 CKKSResultOperation* dependency = self.successfulFetchDependency;
144
145 CKKSKeychainView* ckks = self.ckks; // take a strong reference
146 if(!ckks) {
147 secerror("ckksfetcher: received a null CKKSKeychainView pointer; strange.");
148 return;
149 }
150
151 ckksnotice("ckksfetcher", self.zoneID, "Starting a new fetch for %@", self.zoneID.zoneName);
152
153 NSSet* lastFetchReasons = self.currentFetchReasons;
154 self.currentFetchReasons = [[NSMutableSet alloc] init];
155 if(self.newResyncRequests) {
156 [lastFetchReasons setByAddingObject:@"resync"];
157 }
158
159 CKOperationGroup* operationGroup = [CKOperationGroup CKKSGroupWithName: [[lastFetchReasons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES]]] componentsJoinedByString:@","]];
160
161 CKKSFetchAllRecordZoneChangesOperation* fetchAllChanges = [[CKKSFetchAllRecordZoneChangesOperation alloc] initWithCKKSKeychainView: ckks ckoperationGroup:operationGroup];
162 [fetchAllChanges addNullableDependency: self.holdOperation];
163 fetchAllChanges.resync = self.newResyncRequests;
164 self.newResyncRequests = false;
165
166 // Can't fetch until the zone is setup.
167 [fetchAllChanges addNullableDependency:ckks.zoneSetupOperation];
168
169 self.currentProcessResult = [CKKSResultOperation operationWithBlock: ^{
170 __strong __typeof(self) strongSelf = weakSelf;
171 if(!strongSelf) {
172 secerror("ckksfetcher: Received a null self pointer; strange.");
173 return;
174 }
175
176 CKKSKeychainView* blockckks = strongSelf.ckks; // take a strong reference
177 if(!blockckks) {
178 secerror("ckksfetcher: Received a null CKKSKeychainView pointer; strange.");
179 return;
180 }
181
182 dispatch_sync(strongSelf.queue, ^{
183 if(!fetchAllChanges.error) {
184 // success! notify the listeners.
185 [blockckks scheduleOperation: dependency];
186
187 // Did new people show up and want another fetch?
188 if(strongSelf.newRequests) {
189 [strongSelf.fetchScheduler trigger];
190 }
191 } else {
192 if([blockckks isFatalCKFetchError: fetchAllChanges.error]) {
193 ckkserror("ckksfetcher", strongSelf.zoneID, "Notified that %@ is a fatal error. Not restarting fetch.", fetchAllChanges.error);
194 return;
195 }
196
197 // The operation errored. Chain the dependency on the current one...
198 [dependency addSuccessDependency: strongSelf.successfulFetchDependency];
199 [blockckks scheduleOperation: dependency];
200
201 // And in a bit, try the fetch again.
202 NSNumber* delaySeconds = fetchAllChanges.error.userInfo[CKErrorRetryAfterKey];
203 if([fetchAllChanges.error.domain isEqual: CKErrorDomain] && delaySeconds) {
204 ckksnotice("ckksfetcher", strongSelf.zoneID, "Fetch failed with rate-limiting error, restarting in %@ seconds: %@", delaySeconds, fetchAllChanges.error);
205 [strongSelf.fetchScheduler waitUntil: NSEC_PER_SEC * [delaySeconds unsignedLongValue]];
206 } else {
207 ckksnotice("ckksfetcher", strongSelf.zoneID, "Fetch failed with error, restarting soon: %@", fetchAllChanges.error);
208 }
209
210 // Add the failed fetch reasons to the new fetch reasons
211 [strongSelf.currentFetchReasons unionSet:lastFetchReasons];
212 [strongSelf.currentFetchReasons addObject:CKKSFetchBecausePreviousFetchFailed];
213 strongSelf.newRequests = true;
214 strongSelf.newResyncRequests |= fetchAllChanges.resync;
215 [strongSelf.fetchScheduler trigger];
216 }
217 });
218 }];
219 self.currentProcessResult.name = @"zone-change-fetcher-worker";
220 [self.currentProcessResult addDependency: fetchAllChanges];
221
222 [ckks scheduleOperation: self.currentProcessResult];
223
224 self.currentFetch = fetchAllChanges;
225 [ckks scheduleOperation: self.currentFetch];
226
227 // creata a new fetch dependency, for all those who come in while this operation is executing
228 self.newRequests = false;
229 [self newSuccesfulFetchDependency];
230 }
231
232 -(void)newSuccesfulFetchDependency {
233 CKKSResultOperation* dep = [[CKKSResultOperation alloc] init];
234 __weak __typeof(dep) weakDep = dep;
235
236 // Since these dependencies might chain, when one runs, break the chain.
237 [dep addExecutionBlock:^{
238 __strong __typeof(dep) strongDep = weakDep;
239
240 // Remove all dependencies
241 NSArray* deps = [strongDep.dependencies copy];
242 for(NSOperation* op in deps) {
243 [strongDep removeDependency: op];
244 }
245 }];
246 dep.name = @"successful-fetch-dependency";
247
248 self.successfulFetchDependency = dep;
249 }
250
251 - (void)holdFetchesUntil:(CKKSResultOperation*)holdOperation {
252 self.holdOperation = holdOperation;
253 }
254
255 -(void)cancel {
256 [self.fetchScheduler cancel];
257 }
258
259 @end
260
261 #endif
262
263