]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSNearFutureScheduler.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / ckks / CKKSNearFutureScheduler.m
1 /*
2 * Copyright (c) 2017 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 "CKKSNearFutureScheduler.h"
27 #import "CKKSCondition.h"
28 #import "keychain/ckks/NSOperationCategories.h"
29 #import "keychain/ckks/CKKSResultOperation.h"
30 #import "keychain/ot/ObjCImprovements.h"
31 #include <os/transaction_private.h>
32
33 @interface CKKSNearFutureScheduler ()
34 @property NSString* name;
35 @property dispatch_time_t initialDelay;
36 @property dispatch_time_t continuingDelay;
37
38 @property NSInteger operationDependencyDescriptionCode;
39 @property CKKSResultOperation* operationDependency;
40 @property (nonnull) NSOperationQueue* operationQueue;
41
42 @property NSDate* predictedNextFireTime;
43 @property bool liveRequest;
44 @property CKKSCondition* liveRequestReceived; // Triggered when liveRequest goes to true.
45
46 @property dispatch_source_t timer;
47 @property dispatch_queue_t queue;
48
49 @property bool keepProcessAlive;
50 @property os_transaction_t transaction;
51 @end
52
53 @implementation CKKSNearFutureScheduler
54
55 -(instancetype)initWithName:(NSString*)name
56 delay:(dispatch_time_t)ns
57 keepProcessAlive:(bool)keepProcessAlive
58 dependencyDescriptionCode:(NSInteger)code
59 block:(void (^)(void))futureBlock
60 {
61 return [self initWithName:name
62 initialDelay:ns
63 continuingDelay:ns
64 keepProcessAlive:keepProcessAlive
65 dependencyDescriptionCode:code
66 block:futureBlock];
67 }
68
69 -(instancetype)initWithName:(NSString*)name
70 initialDelay:(dispatch_time_t)initialDelay
71 continuingDelay:(dispatch_time_t)continuingDelay
72 keepProcessAlive:(bool)keepProcessAlive
73 dependencyDescriptionCode:(NSInteger)code
74 block:(void (^)(void))futureBlock
75 {
76 if((self = [super init])) {
77 _name = name;
78
79 _queue = dispatch_queue_create([[NSString stringWithFormat:@"near-future-scheduler-%@",name] UTF8String], DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
80 _initialDelay = initialDelay;
81 _continuingDelay = continuingDelay;
82 _futureBlock = futureBlock;
83
84 _liveRequest = false;
85 _liveRequestReceived = [[CKKSCondition alloc] init];
86 _predictedNextFireTime = nil;
87
88 _keepProcessAlive = keepProcessAlive;
89
90 _operationQueue = [[NSOperationQueue alloc] init];
91 _operationDependencyDescriptionCode = code;
92 _operationDependency = [self makeOperationDependency];
93 }
94 return self;
95 }
96
97 - (void)changeDelays:(dispatch_time_t)initialDelay continuingDelay:(dispatch_time_t)continuingDelay
98 {
99 dispatch_sync(self.queue, ^{
100 self.initialDelay = initialDelay;
101 self.continuingDelay = continuingDelay;
102 });
103 }
104
105 - (CKKSResultOperation*)makeOperationDependency {
106 CKKSResultOperation* op = [CKKSResultOperation named:[NSString stringWithFormat:@"nfs-%@", self.name] withBlock:^{}];
107 op.descriptionErrorCode = self.operationDependencyDescriptionCode;
108 return op;
109 }
110
111 -(NSString*)description {
112 NSDate* nextAt = self.nextFireTime;
113 if(nextAt) {
114 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
115 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
116 return [NSString stringWithFormat: @"<CKKSNearFutureScheduler(%@): next at %@", self.name, [dateFormatter stringFromDate: nextAt]];
117 } else {
118 return [NSString stringWithFormat: @"<CKKSNearFutureScheduler(%@): no pending attempts", self.name];
119 }
120 }
121
122 - (NSDate*)nextFireTime {
123 // If we have a live request, send the next fire time back. Otherwise, wait a tiny tiny bit to see if we receive a request.
124 if(self.liveRequest) {
125 return self.predictedNextFireTime;
126 } else if([self.liveRequestReceived wait:50*NSEC_PER_USEC] == 0) {
127 return self.predictedNextFireTime;
128 }
129
130 return nil;
131 }
132
133 -(void)waitUntil:(uint64_t)delay {
134 dispatch_sync(self.queue, ^{
135 [self _onqueueTrigger:delay maximumDelay:DISPATCH_TIME_FOREVER];
136 });
137 }
138
139 - (void)triggerAt:(uint64_t)delay {
140 WEAKIFY(self);
141 dispatch_async(self.queue, ^{
142 STRONGIFY(self);
143 self.liveRequest = true;
144 [self.liveRequestReceived fulfill];
145 [self _onqueueTrigger:(delay == DISPATCH_TIME_FOREVER ? DISPATCH_TIME_NOW : delay) maximumDelay:delay];
146 });
147 }
148
149 -(void)_onqueueTimerTick {
150 dispatch_assert_queue(self.queue);
151
152 if(self.liveRequest) {
153 // Put a new dependency in place, and save the old one for execution
154 NSOperation* dependency = self.operationDependency;
155 self.operationDependency = [self makeOperationDependency];
156
157 self.futureBlock();
158 self.liveRequest = false;
159 self.liveRequestReceived = [[CKKSCondition alloc] init];
160 self.transaction = nil;
161
162 [self.operationQueue addOperation: dependency];
163
164 self.predictedNextFireTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) self.continuingDelay) / (double) NSEC_PER_SEC];
165 } else {
166 // The timer has fired with no requests to call the block. Cancel it.
167 dispatch_source_cancel(self.timer);
168 self.predictedNextFireTime = nil;
169 }
170 }
171
172 -(void)trigger {
173 WEAKIFY(self);
174 dispatch_async(self.queue, ^{
175 STRONGIFY(self);
176 // The timer tick should call the block!
177 self.liveRequest = true;
178 [self.liveRequestReceived fulfill];
179
180 [self _onqueueTrigger:DISPATCH_TIME_NOW maximumDelay:DISPATCH_TIME_FOREVER];
181 });
182 }
183
184 -(void)_onqueueTrigger:(dispatch_time_t)requestedDelay maximumDelay:(dispatch_time_t)maximumDelay {
185 dispatch_assert_queue(self.queue);
186 WEAKIFY(self);
187
188 // If we don't have one already, set up an os_transaction
189 if(self.keepProcessAlive && self.transaction == nil) {
190 self.transaction = os_transaction_create([[NSString stringWithFormat:@"com.apple.securityd.%@",self.name] UTF8String]);
191 }
192
193 if(requestedDelay != DISPATCH_TIME_NOW && self.predictedNextFireTime != nil) {
194 NSDate* delayTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) requestedDelay) / (double) NSEC_PER_SEC];
195 if([delayTime compare:self.predictedNextFireTime] != NSOrderedDescending) {
196 // The next fire time is after this delay. Do nothing with the request.
197 } else {
198 // Need to cancel the timer and reset it below.
199 dispatch_source_cancel(self.timer);
200 self.predictedNextFireTime = nil;
201 }
202 }
203
204 if(maximumDelay != DISPATCH_TIME_FOREVER && self.predictedNextFireTime != nil) {
205 NSDate* delayTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) requestedDelay) / (double) NSEC_PER_SEC];
206 if([delayTime compare:self.predictedNextFireTime] != NSOrderedDescending) {
207 // Need to cancel the timer and reset it below.
208 dispatch_source_cancel(self.timer);
209 self.predictedNextFireTime = nil;
210 } else {
211 // The next fire time is before the maximum delay. Do nothing with the request.
212 }
213 }
214
215 // Check if the timer is alive
216 if(self.timer != nil && 0 == dispatch_source_testcancel(self.timer)) {
217 // timer is alive, do nothing
218 } else {
219 // start the timer
220 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
221 0,
222 (dispatch_source_timer_flags_t)0,
223 self.queue);
224 dispatch_source_set_event_handler(self.timer, ^{
225 STRONGIFY(self);
226 [self _onqueueTimerTick];
227 });
228
229 dispatch_time_t actualDelay = self.initialDelay;
230 if(requestedDelay != DISPATCH_TIME_NOW) {
231 actualDelay = MAX(actualDelay, requestedDelay);
232 }
233 if(maximumDelay != DISPATCH_TIME_FOREVER) {
234 actualDelay = MIN(actualDelay, maximumDelay);
235 }
236
237 dispatch_source_set_timer(self.timer,
238 dispatch_walltime(NULL, actualDelay),
239 self.continuingDelay,
240 50 * NSEC_PER_MSEC);
241 dispatch_resume(self.timer);
242
243 self.predictedNextFireTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) actualDelay) / (double) NSEC_PER_SEC];
244 };
245 }
246
247 -(void)cancel {
248 dispatch_sync(self.queue, ^{
249 if(self.timer != nil && 0 == dispatch_source_testcancel(self.timer)) {
250 dispatch_source_cancel(self.timer);
251 }
252 });
253 }
254
255 @end
256
257 #endif // OCTAGON