]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSReachabilityTracker.m
Security-58286.51.6.tar.gz
[apple/security.git] / keychain / ckks / CKKSReachabilityTracker.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 <Foundation/Foundation.h>
27 #import <CloudKit/CloudKit.h>
28
29 #import <dispatch/dispatch.h>
30 #import <sys/types.h>
31 #import <sys/socket.h>
32 #import <netinet/in.h>
33
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ckks/CKKSGroupOperation.h"
36 #import "keychain/ckks/CKKSReachabilityTracker.h"
37 #import "keychain/ckks/CKKSAnalytics.h"
38
39 // force reachability timeout every now and then
40 #define REACHABILITY_TIMEOUT (12 * 3600 * NSEC_PER_SEC)
41
42 @interface CKKSReachabilityTracker ()
43 @property bool haveNetwork;
44 @property dispatch_queue_t queue;
45 @property NSOperationQueue* operationQueue;
46 @property (assign) SCNetworkReachabilityRef reachability;
47 @property dispatch_source_t timer;
48 @end
49
50 @implementation CKKSReachabilityTracker
51
52 static void
53 callout(SCNetworkReachabilityRef reachability,
54 SCNetworkReachabilityFlags flags,
55 void *context)
56 {
57 CKKSReachabilityTracker *tracker = (__bridge id)context;
58 [tracker _onqueueRecheck:flags];
59 }
60
61 - (instancetype)init {
62 if((self = [super init])) {
63 _queue = dispatch_queue_create("reachabiltity-tracker", DISPATCH_QUEUE_SERIAL);
64 _operationQueue = [[NSOperationQueue alloc] init];
65
66 dispatch_sync(_queue, ^{
67 [self _onQueueResetReachabilityDependency];
68 });
69
70 __weak __typeof(self) weakSelf = self;
71
72 if(!SecCKKSTestsEnabled()) {
73 struct sockaddr_in zeroAddress;
74 bzero(&zeroAddress, sizeof(zeroAddress));
75 zeroAddress.sin_len = sizeof(zeroAddress);
76 zeroAddress.sin_family = AF_INET;
77
78 _reachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
79
80 SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
81 SCNetworkReachabilitySetDispatchQueue(_reachability, _queue);
82 SCNetworkReachabilitySetCallback(_reachability, callout, &context);
83 }
84
85 [weakSelf recheck];
86 }
87 return self;
88 }
89
90 -(NSString*)description {
91 return [NSString stringWithFormat: @"<CKKSReachabilityTracker: %@>", self.haveNetwork ? @"online" : @"offline"];
92 }
93
94 -(bool)currentReachability {
95 __block bool currentReachability = false;
96 dispatch_sync(self.queue, ^{
97 currentReachability = self.haveNetwork;
98 });
99 return currentReachability;
100 }
101
102 -(void)_onQueueRunReachablityDependency
103 {
104 dispatch_assert_queue(self.queue);
105 // We're have network now, or timer expired, either way, execute dependency
106 if (self.reachablityDependency) {
107 [self.operationQueue addOperation: self.reachablityDependency];
108 self.reachablityDependency = nil;
109 }
110 if (self.timer) {
111 dispatch_source_cancel(self.timer);
112 self.timer = nil;
113 }
114 }
115
116 -(void)_onQueueResetReachabilityDependency {
117 dispatch_assert_queue(self.queue);
118
119 if(self.reachablityDependency == nil || ![self.reachablityDependency isPending]) {
120 __weak __typeof(self) weakSelf = self;
121
122 self.reachablityDependency = [NSBlockOperation blockOperationWithBlock: ^{
123 __typeof(self) strongSelf = weakSelf;
124 if (strongSelf == nil) {
125 return;
126 }
127 if (strongSelf.haveNetwork) {
128 secinfo("ckks", "Network available");
129 } else {
130 secinfo("ckks", "Network still not available, retrying after waiting %2.1f hours",
131 ((float)(REACHABILITY_TIMEOUT/NSEC_PER_SEC)) / 3600);
132 }
133 }];
134 self.reachablityDependency.name = @"network-available-dependency";
135
136 /*
137 * Make sure we are not stuck forever and retry every REACHABILITY_TIMEOUT
138 */
139 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
140 0,
141 (dispatch_source_timer_flags_t)0,
142 self.queue);
143 dispatch_source_set_event_handler(self.timer, ^{
144 __typeof(self) strongSelf = weakSelf;
145 if (strongSelf == nil) {
146 return;
147 }
148 if (strongSelf.timer) {
149 [[CKKSAnalytics logger] noteEvent:CKKSEventReachabilityTimerExpired];
150 [strongSelf _onQueueRunReachablityDependency];
151 }
152 });
153
154 dispatch_source_set_timer(self.timer,
155 dispatch_time(DISPATCH_TIME_NOW, REACHABILITY_TIMEOUT),
156 DISPATCH_TIME_FOREVER, //one-shot
157 30 * NSEC_PER_SEC);
158 dispatch_resume(self.timer);
159 }
160 }
161
162 -(void)_onqueueRecheck:(SCNetworkReachabilityFlags)flags {
163 dispatch_assert_queue(self.queue);
164
165 const SCNetworkReachabilityFlags reachabilityFlags =
166 kSCNetworkReachabilityFlagsReachable
167 | kSCNetworkReachabilityFlagsConnectionAutomatic
168 #if TARGET_OS_IPHONE
169 | kSCNetworkReachabilityFlagsIsWWAN
170 #endif
171 ;
172
173 bool hadNetwork = self.haveNetwork;
174 self.haveNetwork = !!(flags & reachabilityFlags);
175
176 if(hadNetwork != self.haveNetwork) {
177 if(self.haveNetwork) {
178 // We're have network now
179 [self _onQueueRunReachablityDependency];
180 } else {
181 [self _onQueueResetReachabilityDependency];
182 }
183 }
184 }
185
186 + (SCNetworkReachabilityFlags)getReachabilityFlags:(SCNetworkReachabilityRef)target
187 {
188 SCNetworkReachabilityFlags flags;
189 if (SCNetworkReachabilityGetFlags(target, &flags))
190 return flags;
191 return 0;
192 }
193
194 -(void)recheck {
195 dispatch_sync(self.queue, ^{
196 SCNetworkReachabilityFlags flags = [CKKSReachabilityTracker getReachabilityFlags:self.reachability];
197 [self _onqueueRecheck:flags];
198 });
199 }
200
201 -(bool)isNetworkError:(NSError *)error {
202 if (error == nil)
203 return false;
204 return ([error.domain isEqualToString:CKErrorDomain] &&
205 (error.code == CKErrorNetworkUnavailable
206 || error.code == CKErrorNetworkFailure));
207 }
208
209 @end
210
211 #endif // OCTAGON
212