]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OctagonStateMachineObservers.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ot / OctagonStateMachineObservers.m
1 #if OCTAGON
2
3 #import "keychain/categories/NSError+UsefulConstructors.h"
4 #import "keychain/ot/ObjCImprovements.h"
5 #import "keychain/ot/OctagonStateMachineObservers.h"
6 #import "keychain/ot/OTDefines.h"
7 #import "keychain/ot/OTConstants.h"
8
9 @implementation OctagonStateTransitionPathStep
10
11 - (instancetype)initAsSuccess
12 {
13 if((self = [super init])) {
14 _successState = YES;
15 _followStates = @{};
16 }
17 return self;
18 }
19 - (instancetype)initWithPath:(NSDictionary<OctagonState*, OctagonStateTransitionPathStep*>*)followStates
20 {
21 if((self = [super init])) {
22 _successState = NO;
23 _followStates = followStates;
24 }
25 return self;
26 }
27
28 - (OctagonStateTransitionPathStep*)nextStep:(OctagonState*)stateStep
29 {
30 // If stateStep matches a followState, return it. Otherwise, return nil.
31 return self.followStates[stateStep];
32 }
33
34 - (NSString*)description
35 {
36 return [NSString stringWithFormat:@"<OSTPath(%@)>", self.followStates.allKeys];
37 }
38
39 + (OctagonStateTransitionPathStep*)success
40 {
41 return [[OctagonStateTransitionPathStep alloc] initAsSuccess];
42 }
43
44 + (OctagonStateTransitionPathStep*)pathFromDictionary:(NSDictionary<OctagonState*, id>*)pathDict
45 {
46 NSMutableDictionary<OctagonState*, OctagonStateTransitionPathStep*>* converted = [NSMutableDictionary dictionary];
47 for(id key in pathDict.allKeys) {
48 id obj = pathDict[key];
49
50 if([obj isKindOfClass:[OctagonStateTransitionPathStep class]]) {
51 converted[key] = obj;
52 } else if([obj isKindOfClass:[NSDictionary class]]) {
53 converted[key] = [OctagonStateTransitionPathStep pathFromDictionary:(NSDictionary*)obj];
54 }
55 }
56
57 if([converted count] == 0) {
58 return [[OctagonStateTransitionPathStep alloc] initAsSuccess];
59 }
60
61 return [[OctagonStateTransitionPathStep alloc] initWithPath:converted];
62 }
63 @end
64
65 #pragma mark - OctagonStateTransitionPath
66
67 @implementation OctagonStateTransitionPath
68 - (instancetype)initWithState:(OctagonState*)initialState
69 pathStep:(OctagonStateTransitionPathStep*)pathStep
70 {
71 if((self = [super init])) {
72 _initialState = initialState;
73 _pathStep = pathStep;
74 }
75 return self;
76 }
77
78 - (OctagonStateTransitionPathStep*)asPathStep
79 {
80 return [[OctagonStateTransitionPathStep alloc] initWithPath:@{
81 self.initialState: self.pathStep,
82 }];
83 }
84
85 + (OctagonStateTransitionPath* _Nullable)pathFromDictionary:(NSDictionary<OctagonState*, id>*)pathDict
86 {
87 for(id key in pathDict.allKeys) {
88 id obj = pathDict[key];
89
90 if([obj isKindOfClass:[OctagonStateTransitionPathStep class]]) {
91 return [[OctagonStateTransitionPath alloc] initWithState:key
92 pathStep:obj];
93 } else if([obj isKindOfClass:[NSDictionary class]]) {
94 return [[OctagonStateTransitionPath alloc] initWithState:key
95 pathStep:[OctagonStateTransitionPathStep pathFromDictionary:obj]];
96 }
97 }
98 return nil;
99 }
100 @end
101
102
103 #pragma mark - OctagonStateTransitionWatcher
104
105 @interface OctagonStateTransitionWatcher ()
106 @property BOOL active;
107 @property BOOL completed;
108 @property (nullable) OctagonStateTransitionPathStep* remainingPath;
109 @property NSOperationQueue* operationQueue;
110
111 @property (nullable) OctagonStateTransitionRequest* initialRequest;
112 @property (nullable) CKKSResultOperation* initialTimeoutListenerOp;
113
114 @property bool timeoutCanOccur;
115 @property dispatch_queue_t queue;
116 @end
117
118 @implementation OctagonStateTransitionWatcher
119
120 - (instancetype)initNamed:(NSString*)name
121 serialQueue:(dispatch_queue_t)queue
122 path:(OctagonStateTransitionPath*)pathBeginning
123 initialRequest:(OctagonStateTransitionRequest* _Nullable)initialRequest
124 {
125 if((self = [super init])) {
126 _name = name;
127 _intendedPath = pathBeginning;
128 _remainingPath = [pathBeginning asPathStep];
129
130 _result = [CKKSResultOperation named:[NSString stringWithFormat:@"watcher-%@", name] withBlock:^{}];
131 _operationQueue = [[NSOperationQueue alloc] init];
132
133 _queue = queue;
134
135 _timeoutCanOccur = true;
136 _initialRequest = initialRequest;
137 if(initialRequest) {
138 WEAKIFY(self);
139 _initialTimeoutListenerOp = [CKKSResultOperation named:[NSString stringWithFormat:@"watcher-timeout-%@", name] withBlock:^{
140 STRONGIFY(self);
141 if(!self) {
142 return;
143 }
144
145 NSError* opError = initialRequest.transitionOperation.error;
146
147 if(opError &&
148 [opError.domain isEqualToString:CKKSResultErrorDomain] &&
149 opError.code == CKKSResultTimedOut) {
150 dispatch_sync(self.queue, ^{
151 [self _onqueuePerformTimeoutWithUnderlyingError:opError];
152 });
153 }
154 }];
155 [_initialTimeoutListenerOp addDependency:initialRequest.transitionOperation];
156 [_operationQueue addOperation:_initialTimeoutListenerOp];
157 }
158
159 _active = NO;
160 _completed = NO;
161 }
162 return self;
163 }
164
165 - (NSString*)description {
166 return [NSString stringWithFormat:@"<OctagonStateTransitionWatcher(%@): remaining: %@, result: %@>",
167 self.name,
168 self.remainingPath,
169 self.result];
170 }
171
172 - (void)onqueueHandleTransition:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt
173 {
174 dispatch_assert_queue(self.queue);
175
176 // Early-exit to make error handling better
177 if(self.remainingPath == nil || self.completed) {
178 return;
179 }
180
181 if(self.active) {
182 [self onqueueProcessTransition:attempt];
183
184 } else {
185 if([attempt.nextState isEqualToString:self.intendedPath.initialState]) {
186 self.active = YES;
187 [self onqueueProcessTransition:attempt];
188 }
189 }
190 }
191
192 - (void)_onqueuePerformTimeoutWithUnderlyingError:(NSError* _Nullable)underlyingError
193 {
194 dispatch_assert_queue(self.queue);
195
196 if(self.timeoutCanOccur) {
197 self.timeoutCanOccur = false;
198
199 NSString* description = [NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]",
200 self.name,
201 self.remainingPath];
202
203 self.result.error = [NSError errorWithDomain:CKKSResultErrorDomain
204 code:CKKSResultTimedOut
205 description:description
206 underlying:underlyingError];
207 [self onqueueStartFinishOperation];
208 }
209 }
210
211 - (instancetype)timeout:(dispatch_time_t)timeout
212 {
213 WEAKIFY(self);
214 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.queue, ^{
215 STRONGIFY(self);
216 [self _onqueuePerformTimeoutWithUnderlyingError:nil];
217 });
218
219 return self;
220 }
221
222 - (void)onqueueProcessTransition:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt
223 {
224 dispatch_assert_queue(self.queue);
225
226 if(self.remainingPath == nil || self.completed) {
227 return;
228 }
229
230 OctagonStateTransitionPathStep* nextPath = [self.remainingPath nextStep:attempt.nextState];
231
232 if(nextPath) {
233 self.remainingPath = nextPath;
234 if(self.remainingPath.successState) {
235 // We're done!
236 [self onqueueStartFinishOperation];
237 }
238
239 } else {
240 // We're off the path. Error and finish.
241 if(attempt.error) {
242 self.result.error = attempt.error;
243 } else {
244 self.result.error = [NSError errorWithDomain:OctagonErrorDomain
245 code:OTErrorUnexpectedStateTransition
246 description:[NSString stringWithFormat:@"state became %@, was expecting %@", attempt.nextState, self.remainingPath]];
247 }
248 [[CKKSAnalytics logger] logUnrecoverableError:self.result.error
249 forEvent:OctagonEventStateTransition
250 withAttributes:@{
251 @"name" : self.name,
252 @"intended": [self.remainingPath.followStates allKeys],
253 @"became" : attempt.nextState,
254 }];
255
256 [self onqueueStartFinishOperation];
257 }
258 }
259
260 - (void)onqueueStartFinishOperation {
261 dispatch_assert_queue(self.queue);
262
263 self.timeoutCanOccur = false;
264 [self.operationQueue addOperation:self.result];
265 self.active = false;
266 self.completed = TRUE;
267 }
268
269 @end
270
271 #pragma mark - OctagonStateMultiStateArrivalWatcher
272
273 @interface OctagonStateMultiStateArrivalWatcher ()
274 @property BOOL completed;
275 @property NSOperationQueue* operationQueue;
276
277 @property (nullable) CKKSResultOperation* initialTimeoutListenerOp;
278
279 @property bool timeoutCanOccur;
280 @property dispatch_queue_t queue;
281 @end
282
283
284 @implementation OctagonStateMultiStateArrivalWatcher
285 - (instancetype)initNamed:(NSString*)name
286 serialQueue:(dispatch_queue_t)queue
287 states:(NSSet<OctagonState*>*)states
288 {
289 if((self = [super init])) {
290 _name = name;
291 _states = states;
292
293 _result = [CKKSResultOperation named:[NSString stringWithFormat:@"watcher-%@", name] withBlock:^{}];
294 _operationQueue = [[NSOperationQueue alloc] init];
295
296 _queue = queue;
297 _timeoutCanOccur = true;
298
299 _completed = NO;
300 }
301 return self;
302 }
303
304 - (void)onqueueHandleTransition:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt
305 {
306 dispatch_assert_queue(self.queue);
307 [self onqueueEnterState:attempt.nextState];
308 }
309
310 - (void)onqueueEnterState:(OctagonState*)state
311 {
312 if(!self.completed) {
313 if([self.states containsObject:state]) {
314 [self onqueueStartFinishOperation];
315 }
316 }
317 }
318
319 - (void)_onqueuePerformTimeoutWithUnderlyingError
320 {
321 dispatch_assert_queue(self.queue);
322
323 if(self.timeoutCanOccur) {
324 self.timeoutCanOccur = false;
325
326 NSString* description = [NSString stringWithFormat:@"Operation(%@) timed out waiting to start for any state in [%@]",
327 self.name,
328 self.states];
329
330 self.result.error = [NSError errorWithDomain:CKKSResultErrorDomain
331 code:CKKSResultTimedOut
332 description:description];
333 [self onqueueStartFinishOperation];
334 }
335 }
336
337 - (instancetype)timeout:(dispatch_time_t)timeout
338 {
339 WEAKIFY(self);
340 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.queue, ^{
341 STRONGIFY(self);
342 [self _onqueuePerformTimeoutWithUnderlyingError];
343 });
344
345 return self;
346 }
347
348 - (void)onqueueStartFinishOperation {
349 dispatch_assert_queue(self.queue);
350
351 self.timeoutCanOccur = false;
352 [self.operationQueue addOperation:self.result];
353 self.completed = TRUE;
354 }
355 @end
356
357
358 #endif