]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSResultOperation.m
Security-58286.200.222.tar.gz
[apple/security.git] / keychain / ckks / CKKSResultOperation.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 #if OCTAGON
25
26 #import "keychain/ckks/CKKSResultOperation.h"
27 #import "keychain/ckks/NSOperationCategories.h"
28 #import "keychain/ckks/CKKSCondition.h"
29 #import "keychain/categories/NSError+UsefulConstructors.h"
30 #include <utilities/debugging.h>
31
32 @interface CKKSResultOperation()
33 @property NSMutableArray<CKKSResultOperation*>* successDependencies;
34 @property bool timeoutCanOccur;
35 @property dispatch_queue_t timeoutQueue;
36 @property void (^finishingBlock)(void);
37 @end
38
39 @implementation CKKSResultOperation
40 - (instancetype)init {
41 if(self = [super init]) {
42 _error = nil;
43 _successDependencies = [[NSMutableArray alloc] init];
44 _timeoutCanOccur = true;
45 _timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL);
46 _completionHandlerDidRunCondition = [[CKKSCondition alloc] init];
47
48 __weak __typeof(self) weakSelf = self;
49 _finishingBlock = ^(void) {
50 weakSelf.finishDate = [NSDate dateWithTimeIntervalSinceNow:0];
51 };
52 self.completionBlock = ^{}; // our _finishing block gets added in the method override
53 }
54 return self;
55 }
56
57 - (NSString*)operationStateString {
58 return ([self isFinished] ? [NSString stringWithFormat:@"finished %@", self.finishDate] :
59 [self isCancelled] ? @"cancelled" :
60 [self isExecuting] ? @"executing" :
61 [self isReady] ? @"ready" :
62 @"pending");
63 }
64
65 - (NSString*)description {
66 NSString* state = [self operationStateString];
67
68 if(self.error) {
69 return [NSString stringWithFormat: @"<%@: %@ error:%@>", [self selfname], state, self.error];
70 } else {
71 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString:@" dep:"]];
72 }
73 }
74
75 - (NSString*)debugDescription {
76 return [self description];
77 }
78
79 - (void)setCompletionBlock:(void (^)(void))completionBlock
80 {
81 __weak __typeof(self) weakSelf = self;
82 [super setCompletionBlock:^(void) {
83 __strong __typeof(self) strongSelf = weakSelf;
84 if (!strongSelf) {
85 secerror("ckksresultoperation: completion handler called on deallocated operation instance");
86 completionBlock(); // go ahead and still behave as things would if this method override were not here
87 return;
88 }
89
90 strongSelf.finishingBlock();
91 completionBlock();
92 [strongSelf.completionHandlerDidRunCondition fulfill];
93
94 for (NSOperation *op in strongSelf.dependencies) {
95 [strongSelf removeDependency:op];
96 }
97 }];
98 }
99
100 - (void)start {
101 if(![self allDependentsSuccessful]) {
102 secdebug("ckksresultoperation", "Not running due to some failed dependent: %@", self.error);
103 [self cancel];
104 } else {
105 [self invalidateTimeout];
106
107 }
108
109 [super start];
110 }
111
112 - (void)invalidateTimeout {
113 dispatch_sync(self.timeoutQueue, ^{
114 if(![self isCancelled]) {
115 self.timeoutCanOccur = false;
116 };
117 });
118 }
119
120 - (NSError* _Nullable)dependenciesDescriptionError {
121 NSError* underlyingReason = nil;
122 NSArray* dependencies = [self.dependencies copy];
123 dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj,
124 NSUInteger idx,
125 BOOL* stop) {
126 return [obj isFinished] ? NO : YES;
127 }]];
128
129 for(NSOperation* dependency in dependencies) {
130 if([dependency isKindOfClass:[CKKSResultOperation class]]) {
131 CKKSResultOperation* ro = (CKKSResultOperation*)dependency;
132 underlyingReason = [ro descriptionError] ?: underlyingReason;
133 }
134 }
135
136 return underlyingReason;
137 }
138
139 // Returns, for this CKKSResultOperation, an error describing this operation or its dependents.
140 // Used mainly by other CKKSResultOperations who time out waiting for this operation to start/complete.
141 - (NSError* _Nullable)descriptionError {
142 if(self.descriptionErrorCode != 0) {
143 return [NSError errorWithDomain:CKKSResultDescriptionErrorDomain
144 code:self.descriptionErrorCode
145 userInfo:nil];
146 } else {
147 return [self dependenciesDescriptionError];
148 }
149 }
150
151 - (NSError*)_onqueueTimeoutError {
152 // Find if any of our dependencies are CKKSResultOperations with a custom reason for existing
153
154 NSError* underlyingReason = [self descriptionError];
155
156 NSError* error = [NSError errorWithDomain:CKKSResultErrorDomain
157 code:CKKSResultTimedOut
158 description:[NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]",
159 [self selfname],
160 [self pendingDependenciesString:@""]]
161 underlying:underlyingReason];
162 return error;
163 }
164
165 - (instancetype)timeout:(dispatch_time_t)timeout {
166 __weak __typeof(self) weakSelf = self;
167 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
168 __strong __typeof(self) strongSelf = weakSelf;
169 if(strongSelf.timeoutCanOccur) {
170 strongSelf.error = [self _onqueueTimeoutError];
171 strongSelf.timeoutCanOccur = false;
172 [strongSelf cancel];
173 }
174 });
175
176 return self;
177 }
178
179 - (void)addSuccessDependency:(CKKSResultOperation *)operation {
180 [self addNullableSuccessDependency:operation];
181 }
182
183 - (void)addNullableSuccessDependency:(CKKSResultOperation *)operation {
184 if(!operation) {
185 return;
186 }
187 @synchronized(self) {
188 [self.successDependencies addObject: operation];
189 [self addDependency: operation];
190 }
191 }
192
193 - (bool)allDependentsSuccessful {
194 return [self allSuccessful: self.successDependencies];
195 }
196
197 - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations {
198 @synchronized(self) {
199 bool result = false;
200
201 bool finished = true; // all dependents must be finished
202 bool cancelled = false; // no dependents can be cancelled
203 bool failed = false; // no dependents can have failed
204 NSMutableArray<NSOperation*>* cancelledSuboperations = [NSMutableArray array];
205
206 for(CKKSResultOperation* op in operations) {
207 finished &= !!([op isFinished]);
208 cancelled |= !!([op isCancelled]);
209 failed |= (op.error != nil);
210
211 if([op isCancelled]) {
212 [cancelledSuboperations addObject:op];
213 }
214
215 // TODO: combine suberrors
216 if(op.error != nil) {
217 if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
218 // Already a subresult, just copy it on in
219 self.error = op.error;
220 } else {
221 self.error = [NSError errorWithDomain:CKKSResultErrorDomain
222 code:CKKSResultSubresultError
223 description:@"Success-dependent operation failed"
224 underlying:op.error];
225 }
226 }
227 }
228
229 result = finished && !( cancelled || failed );
230
231 if(!result && self.error == nil) {
232 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation (%@) cancelled", cancelledSuboperations]}];
233 }
234 return result;
235 }
236 }
237
238 + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block {
239 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
240 [op addExecutionBlock: block];
241 return op;
242 }
243
244 +(instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
245 CKKSResultOperation* blockOp = [CKKSResultOperation operationWithBlock: block];
246 blockOp.name = name;
247 return blockOp;
248 }
249
250 + (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block
251 {
252 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
253 __weak __typeof(op) weakOp = op;
254 [op addExecutionBlock:^{
255 __strong __typeof(op) strongOp = weakOp;
256 block(strongOp);
257 }];
258 op.name = name;
259 return op;
260 }
261 @end
262
263 #endif // OCTAGON