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