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