]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSGroupOperation.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / CKKSGroupOperation.m
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 "CKKSGroupOperation.h"
25 #import "CKKSCondition.h"
26 #include <utilities/debugging.h>
27
28 @implementation NSOperation (CKKSUsefulPrintingOperation)
29 - (NSString*)selfname {
30 if(self.name) {
31 return [NSString stringWithFormat: @"%@(%@)", NSStringFromClass([self class]), self.name];
32 } else {
33 return NSStringFromClass([self class]);
34 }
35 }
36
37 -(void)linearDependencies: (NSHashTable*) collection {
38 @synchronized(collection) {
39 for(NSOperation* existingop in collection) {
40 if(existingop == self) {
41 // don't depend on yourself
42 continue;
43 }
44 [self addDependency: existingop];
45 }
46 [collection addObject:self];
47 }
48 }
49
50 -(NSString*)pendingDependenciesString:(NSString*)prefix {
51 NSArray* dependencies = [self.dependencies copy];
52 dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj,
53 NSUInteger idx,
54 BOOL* stop) {
55 return [obj isPending] ? YES : NO;
56 }]];
57
58 if(dependencies.count == 0u) {
59 return @"";
60 }
61
62 return [NSString stringWithFormat: @"%@%@", prefix, [dependencies componentsJoinedByString: @", "]];
63 }
64
65 - (NSString*)description {
66 NSString* state = ([self isFinished] ? @"finished" :
67 [self isCancelled] ? @"cancelled" :
68 [self isExecuting] ? @"executing" :
69 [self isReady] ? @"ready" :
70 @"pending");
71
72 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString: @" dep:"]];
73 }
74 - (NSString*)debugDescription {
75 NSString* state = ([self isFinished] ? @"finished" :
76 [self isCancelled] ? @"cancelled" :
77 [self isExecuting] ? @"executing" :
78 [self isReady] ? @"ready" :
79 @"pending");
80
81 return [NSString stringWithFormat: @"<%@ (%p): %@%@>", [self selfname], self, state, [self pendingDependenciesString: @" dep:"]];
82 }
83
84 - (BOOL)isPending {
85 return (!([self isExecuting] || [self isFinished])) ? YES : NO;
86 }
87
88 - (void)addNullableDependency: (NSOperation*) op {
89 if(op) {
90 [self addDependency:op];
91 }
92 }
93 @end
94
95 @implementation NSBlockOperation (CKKSUsefulConstructorOperation)
96 +(instancetype)named: (NSString*)name withBlock: (void(^)(void)) block {
97 // How many blocks could a block block if a block could block blocks?
98 NSBlockOperation* blockOp = [NSBlockOperation blockOperationWithBlock: block];
99 blockOp.name = name;
100 return blockOp;
101 }
102 @end
103
104
105 @interface CKKSResultOperation()
106 @property NSMutableArray<CKKSResultOperation*>* successDependencies;
107 @property bool timeoutCanOccur;
108 @property dispatch_queue_t timeoutQueue;
109 @property void (^finishingBlock)(void);
110 @end
111
112 @implementation CKKSResultOperation
113 - (instancetype)init {
114 if(self = [super init]) {
115 _error = nil;
116 _successDependencies = [[NSMutableArray alloc] init];
117 _timeoutCanOccur = true;
118 _timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL);
119 _completionHandlerDidRunCondition = [[CKKSCondition alloc] init];
120
121 __weak __typeof(self) weakSelf = self;
122 _finishingBlock = ^(void) {
123 weakSelf.finishDate = [NSDate dateWithTimeIntervalSinceNow:0];
124 };
125 self.completionBlock = ^{}; // our _finishing block gets added in the method override
126 }
127 return self;
128 }
129
130 - (NSString*)description {
131 NSString* state = ([self isFinished] ? [NSString stringWithFormat:@"finished %@", self.finishDate] :
132 [self isCancelled] ? @"cancelled" :
133 [self isExecuting] ? @"executing" :
134 [self isReady] ? @"ready" :
135 @"pending");
136
137 if(self.error) {
138 return [NSString stringWithFormat: @"<%@: %@ error:%@>", [self selfname], state, self.error];
139 } else {
140 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString:@" dep:"]];
141 }
142 }
143
144 - (NSString*)debugDescription {
145 return [self description];
146 }
147
148 - (void)setCompletionBlock:(void (^)(void))completionBlock
149 {
150 __weak __typeof(self) weakSelf = self;
151 [super setCompletionBlock:^(void) {
152 __strong __typeof(self) strongSelf = weakSelf;
153 if (!strongSelf) {
154 secerror("ckksresultoperation: completion handler called on deallocated operation instance");
155 completionBlock(); // go ahead and still behave as things would if this method override were not here
156 return;
157 }
158
159 strongSelf.finishingBlock();
160 completionBlock();
161 [strongSelf.completionHandlerDidRunCondition fulfill];
162 }];
163 }
164
165 - (void)start {
166 if(![self allDependentsSuccessful]) {
167 secdebug("ckksresultoperation", "Not running due to some failed dependent: %@", self.error);
168 [self cancel];
169 } else {
170 dispatch_sync(self.timeoutQueue, ^{
171 if(![self isCancelled]) {
172 self.timeoutCanOccur = false;
173 };
174 });
175 }
176
177 [super start];
178 }
179
180 - (instancetype)timeout:(dispatch_time_t)timeout {
181 __weak __typeof(self) weakSelf = self;
182 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
183 __strong __typeof(self) strongSelf = weakSelf;
184 if(strongSelf.timeoutCanOccur) {
185 strongSelf.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultTimedOut userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation timed out waiting to start for [%@]", [self pendingDependenciesString:@""]]}];
186 strongSelf.timeoutCanOccur = false;
187 [strongSelf cancel];
188 }
189 });
190
191 return self;
192 }
193
194 - (void)addSuccessDependency: (CKKSResultOperation*) operation {
195 if(!operation) {
196 return;
197 }
198 @synchronized(self) {
199 [self.successDependencies addObject: operation];
200 [self addDependency: operation];
201 }
202 }
203
204 - (bool)allDependentsSuccessful {
205 return [self allSuccessful: self.successDependencies];
206 }
207
208 - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations {
209 @synchronized(self) {
210 bool result = false;
211
212 bool finished = true; // all dependents must be finished
213 bool cancelled = false; // no dependents can be cancelled
214 bool failed = false; // no dependents can have failed
215
216 for(CKKSResultOperation* op in operations) {
217 finished &= !!([op isFinished]);
218 cancelled |= !!([op isCancelled]);
219 failed |= (op.error != nil);
220
221 // TODO: combine suberrors
222 if(op.error != nil) {
223 if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
224 // Already a subresult, just copy it on in
225 self.error = op.error;
226 } else {
227 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultError userInfo:@{ NSUnderlyingErrorKey: op.error}];
228 }
229 }
230 }
231
232 result = finished && !( cancelled || failed );
233
234 if(!result && self.error == nil) {
235 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:nil];
236 }
237 return result;
238 }
239 }
240
241 + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block {
242 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
243 [op addExecutionBlock: block];
244 return op;
245 }
246
247 +(instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
248 CKKSResultOperation* blockOp = [CKKSResultOperation operationWithBlock: block];
249 blockOp.name = name;
250 return blockOp;
251 }
252 @end
253
254
255 @interface CKKSGroupOperation()
256 @property NSBlockOperation* startOperation;
257 @property NSBlockOperation* finishOperation;
258
259 @property NSMutableArray<CKKSResultOperation*>* internalSuccesses;
260 @end
261
262
263 @implementation CKKSGroupOperation
264
265 - (instancetype)init {
266 if(self = [super init]) {
267 __weak __typeof(self) weakSelf = self;
268
269 _operationQueue = [[NSOperationQueue alloc] init];
270 _internalSuccesses = [[NSMutableArray alloc] init];
271
272 // At start, we'll call this method (for subclasses)
273 _startOperation = [NSBlockOperation blockOperationWithBlock:^{
274 __strong __typeof(weakSelf) strongSelf = weakSelf;
275 if(!strongSelf) {
276 secerror("ckks: received callback for released object");
277 return;
278 }
279
280 if(![strongSelf allDependentsSuccessful]) {
281 secdebug("ckksgroup", "Not running due to some failed dependent: %@", strongSelf.error);
282 [strongSelf cancel];
283 return;
284 }
285
286 [strongSelf groupStart];
287 }];
288
289 // The finish operation will 'finish' us
290 _finishOperation = [NSBlockOperation blockOperationWithBlock:^{
291 __strong __typeof(weakSelf) strongSelf = weakSelf;
292 if(!strongSelf) {
293 secerror("ckks: received callback for released object");
294 return;
295 }
296
297 [strongSelf completeOperation];
298 }];
299
300 [self.finishOperation addDependency: self.startOperation];
301 [self.operationQueue addOperation: self.finishOperation];
302
303 self.startOperation.name = @"group-start";
304 self.finishOperation.name = @"group-finish";
305
306 executing = NO;
307 finished = NO;
308 }
309 return self;
310 }
311
312 - (void)dealloc {
313 // If the GroupOperation is dealloced before starting, all of its downstream operations form a retain loop.
314
315 if([self isPending]) {
316 [self.operationQueue cancelAllOperations];
317 [self.startOperation cancel];
318 [super cancel];
319 }
320 }
321
322 - (BOOL)isPending {
323 return [self.startOperation isPending];
324 }
325
326 - (void)setName:(NSString*) name {
327 self.operationQueue.name = [NSString stringWithFormat: @"group-queue:%@", name];
328 self.startOperation.name = [NSString stringWithFormat: @"group-start:%@", name];
329 self.finishOperation.name = [NSString stringWithFormat: @"group-finish:%@", name];
330 [super setName: name];
331 }
332
333 - (NSString*)description {
334 if(self.isFinished) {
335 if(self.error) {
336 return [NSString stringWithFormat: @"<%@: finished %@ - %@>", [self selfname], self.finishDate, self.error];
337 } else {
338 return [NSString stringWithFormat: @"<%@: finished %@>", [self selfname], self.finishDate];
339 }
340 }
341
342 NSMutableArray* ops = [self.operationQueue.operations mutableCopy];
343
344 [ops removeObject: self.finishOperation];
345
346 // Any extra dependencies from the finish operation should be considered part of this group
347 for(NSOperation* finishDep in self.finishOperation.dependencies) {
348 if(finishDep != self.startOperation && (NSNotFound == [ops indexOfObject: finishDep])) {
349 [ops addObject: finishDep];
350 }
351 }
352
353 NSString* opsString = [ops componentsJoinedByString:@", "];
354
355 if(self.error) {
356 return [NSString stringWithFormat: @"<%@: [%@] error:%@>", [self selfname], opsString, self.error];
357 } else {
358 return [NSString stringWithFormat: @"<%@: [%@]%@>", [self selfname], opsString, [self pendingDependenciesString:@" dep:"]];
359 }
360 }
361
362 - (NSString*)debugDescription {
363 return [self description];
364 }
365
366 - (BOOL)isConcurrent {
367 return YES;
368 }
369
370 - (BOOL)isExecuting {
371 return self->executing;
372 }
373
374 - (BOOL)isFinished {
375 return self->finished;
376 }
377
378 - (void)start {
379 if([self isCancelled]) {
380 [self willChangeValueForKey:@"isFinished"];
381 finished = YES;
382 [self didChangeValueForKey:@"isFinished"];
383 return;
384 }
385
386 [self.operationQueue addOperation: self.startOperation];
387
388 [self willChangeValueForKey:@"isExecuting"];
389 executing = YES;
390 [self didChangeValueForKey:@"isExecuting"];
391 }
392
393 - (void)cancel {
394 [self.operationQueue cancelAllOperations];
395 [self.startOperation cancel];
396
397 [super cancel];
398
399 // Our finishoperation might not fire (as we cancelled it above), so let's help it out
400 [self completeOperation];
401 }
402
403 - (void)completeOperation {
404 [self willChangeValueForKey:@"isFinished"];
405 [self willChangeValueForKey:@"isExecuting"];
406
407 // Run through all the failable operations in this group, and determine if we should be considered successful ourselves
408 [self allSuccessful: self.internalSuccesses];
409
410 executing = NO;
411 finished = YES;
412
413 [self didChangeValueForKey:@"isExecuting"];
414 [self didChangeValueForKey:@"isFinished"];
415 }
416
417 - (void)addDependency:(NSOperation *)op {
418 [super addDependency:op];
419 [self.startOperation addDependency: op];
420 }
421
422 - (void)groupStart {
423 // Do nothing. Subclasses can do things here.
424 }
425
426 - (void)runBeforeGroupFinished: (NSOperation*) suboperation {
427 if([self isCancelled]) {
428 // Cancelled operations can't add anything.
429 secnotice("ckksgroup", "Not adding operation to cancelled group");
430 return;
431 }
432
433 // op must wait for this operation to start
434 [suboperation addDependency: self.startOperation];
435
436 [self dependOnBeforeGroupFinished: suboperation];
437 [self.operationQueue addOperation: suboperation];
438 }
439
440 - (void)dependOnBeforeGroupFinished: (NSOperation*) suboperation {
441 if(suboperation == nil) {
442 return;
443 }
444
445 if([self isCancelled]) {
446 // Cancelled operations can't add anything.
447 secnotice("ckksgroup", "Can't add operation dependency to cancelled group");
448 return;
449 }
450
451 if([self.finishOperation isExecuting] || [self.finishOperation isFinished]) {
452 @throw @"Attempt to add operation to completed group";
453 }
454
455 // If this is a CKKSResultOperation, then its result impacts our result.
456 if([suboperation isKindOfClass: [CKKSResultOperation class]]) {
457 // don't use addSuccessDependency, because it's not a dependency for The Group Operation, but rather a suboperation
458 @synchronized(self) {
459 [self.internalSuccesses addObject: (CKKSResultOperation*) suboperation];
460 }
461 }
462
463 // Make sure it waits for us...
464 [suboperation addDependency: self.startOperation];
465 // and we wait for it.
466 [self.finishOperation addDependency: suboperation];
467 }
468
469 @end