2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "keychain/ckks/CKKSResultOperation.h"
25 #import "keychain/ckks/CKKSCondition.h"
26 #include <utilities/debugging.h>
28 @interface CKKSResultOperation()
29 @property NSMutableArray<CKKSResultOperation*>* successDependencies;
30 @property bool timeoutCanOccur;
31 @property dispatch_queue_t timeoutQueue;
32 @property void (^finishingBlock)(void);
35 @implementation CKKSResultOperation
36 - (instancetype)init {
37 if(self = [super init]) {
39 _successDependencies = [[NSMutableArray alloc] init];
40 _timeoutCanOccur = true;
41 _timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL);
42 _completionHandlerDidRunCondition = [[CKKSCondition alloc] init];
44 __weak __typeof(self) weakSelf = self;
45 _finishingBlock = ^(void) {
46 weakSelf.finishDate = [NSDate dateWithTimeIntervalSinceNow:0];
48 self.completionBlock = ^{}; // our _finishing block gets added in the method override
53 - (NSString*)description {
54 NSString* state = ([self isFinished] ? [NSString stringWithFormat:@"finished %@", self.finishDate] :
55 [self isCancelled] ? @"cancelled" :
56 [self isExecuting] ? @"executing" :
57 [self isReady] ? @"ready" :
61 return [NSString stringWithFormat: @"<%@: %@ error:%@>", [self selfname], state, self.error];
63 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString:@" dep:"]];
67 - (NSString*)debugDescription {
68 return [self description];
71 - (void)setCompletionBlock:(void (^)(void))completionBlock
73 __weak __typeof(self) weakSelf = self;
74 [super setCompletionBlock:^(void) {
75 __strong __typeof(self) strongSelf = weakSelf;
77 secerror("ckksresultoperation: completion handler called on deallocated operation instance");
78 completionBlock(); // go ahead and still behave as things would if this method override were not here
82 strongSelf.finishingBlock();
84 [strongSelf.completionHandlerDidRunCondition fulfill];
89 if(![self allDependentsSuccessful]) {
90 secdebug("ckksresultoperation", "Not running due to some failed dependent: %@", self.error);
93 [self invalidateTimeout];
100 - (void)invalidateTimeout {
101 dispatch_sync(self.timeoutQueue, ^{
102 if(![self isCancelled]) {
103 self.timeoutCanOccur = false;
108 - (instancetype)timeout:(dispatch_time_t)timeout {
109 __weak __typeof(self) weakSelf = self;
110 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
111 __strong __typeof(self) strongSelf = weakSelf;
112 if(strongSelf.timeoutCanOccur) {
113 strongSelf.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultTimedOut userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation timed out waiting to start for [%@]", [self pendingDependenciesString:@""]]}];
114 strongSelf.timeoutCanOccur = false;
122 - (void)addSuccessDependency:(CKKSResultOperation *)operation {
123 [self addNullableSuccessDependency:operation];
126 - (void)addNullableSuccessDependency:(CKKSResultOperation *)operation {
130 @synchronized(self) {
131 [self.successDependencies addObject: operation];
132 [self addDependency: operation];
136 - (bool)allDependentsSuccessful {
137 return [self allSuccessful: self.successDependencies];
140 - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations {
141 @synchronized(self) {
144 bool finished = true; // all dependents must be finished
145 bool cancelled = false; // no dependents can be cancelled
146 bool failed = false; // no dependents can have failed
148 for(CKKSResultOperation* op in operations) {
149 finished &= !!([op isFinished]);
150 cancelled |= !!([op isCancelled]);
151 failed |= (op.error != nil);
153 // TODO: combine suberrors
154 if(op.error != nil) {
155 if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
156 // Already a subresult, just copy it on in
157 self.error = op.error;
159 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultError userInfo:@{ NSUnderlyingErrorKey: op.error}];
164 result = finished && !( cancelled || failed );
166 if(!result && self.error == nil) {
167 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:nil];
173 + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block {
174 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
175 [op addExecutionBlock: block];
179 +(instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
180 CKKSResultOperation* blockOp = [CKKSResultOperation operationWithBlock: block];