]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OctagonStateMachineObservers.m
Security-59306.11.20.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 @property bool timeoutCanOccur;
111 @property dispatch_queue_t queue;
112 @end
113
114 @implementation OctagonStateTransitionWatcher
115
116 - (instancetype)initNamed:(NSString*)name
117 serialQueue:(dispatch_queue_t)queue
118 path:(OctagonStateTransitionPath*)pathBeginning
119 {
120 if((self = [super init])) {
121 _name = name;
122 _intendedPath = pathBeginning;
123 _remainingPath = [pathBeginning asPathStep];
124
125 _result = [CKKSResultOperation named:[NSString stringWithFormat:@"watcher-%@", name] withBlock:^{}];
126 _operationQueue = [[NSOperationQueue alloc] init];
127
128 _queue = queue;
129
130 _timeoutCanOccur = true;
131
132 _active = NO;
133 _completed = NO;
134 }
135 return self;
136 }
137
138 - (NSString*)description {
139 return [NSString stringWithFormat:@"<OctagonStateTransitionWatcher(%@): remaining: %@, result: %@>",
140 self.name,
141 self.remainingPath,
142 self.result];
143 }
144
145 - (void)onqueueHandleTransition:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt
146 {
147 dispatch_assert_queue(self.queue);
148
149 // Early-exit to make error handling better
150 if(self.remainingPath == nil || self.completed) {
151 return;
152 }
153
154 if(self.active) {
155 [self onqueueProcessTransition:attempt];
156
157 } else {
158 if([attempt.nextState isEqualToString:self.intendedPath.initialState]) {
159 self.active = YES;
160 [self onqueueProcessTransition:attempt];
161 }
162 }
163 }
164
165 - (instancetype)timeout:(dispatch_time_t)timeout
166 {
167 WEAKIFY(self);
168 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.queue, ^{
169 STRONGIFY(self);
170 if(self.timeoutCanOccur) {
171 self.timeoutCanOccur = false;
172
173 NSString* description = [NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]",
174 self.name,
175 self.remainingPath];
176
177 self.result.error = [NSError errorWithDomain:CKKSResultErrorDomain
178 code:CKKSResultTimedOut
179 description:description
180 underlying:nil];
181 [self onqueueStartFinishOperation];
182
183 }
184 });
185
186 return self;
187 }
188
189 - (void)onqueueProcessTransition:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt
190 {
191 dispatch_assert_queue(self.queue);
192
193 if(self.remainingPath == nil || self.completed) {
194 return;
195 }
196
197
198 OctagonStateTransitionPathStep* nextPath = [self.remainingPath nextStep:attempt.nextState];
199
200 if(nextPath) {
201 self.remainingPath = nextPath;
202 if(self.remainingPath.successState) {
203 // We're done!
204 [self onqueueStartFinishOperation];
205 }
206
207 } else {
208 // We're off the path. Error and finish.
209 if(attempt.error) {
210 self.result.error = attempt.error;
211 } else {
212 self.result.error = [NSError errorWithDomain:OctagonErrorDomain
213 code:OTErrorUnexpectedStateTransition
214 description:[NSString stringWithFormat:@"state became %@, was expecting %@", attempt.nextState, self.remainingPath]];
215 }
216 [[CKKSAnalytics logger] logUnrecoverableError:self.result.error
217 forEvent:OctagonEventStateTransition
218 withAttributes:@{
219 @"name" : self.name,
220 @"intended": [self.remainingPath.followStates allKeys],
221 @"became" : attempt.nextState,
222 }];
223
224 [self onqueueStartFinishOperation];
225 }
226 }
227
228 - (void)onqueueStartFinishOperation {
229 dispatch_assert_queue(self.queue);
230
231 self.timeoutCanOccur = false;
232 [self.operationQueue addOperation:self.result];
233 self.active = false;
234 self.completed = TRUE;
235 }
236
237 @end
238
239 #endif