Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/platform/mac/objc/reachability.m
StratoSDK / crates / strato-ui-renderer / src / platform / mac / objc / reachability.m
1/*
2 Copyright (c) 2011, Tony Million.
3 All rights reserved.
4 
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7 
8 1. Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
10 
11 2. Redistributions in binary form must reproduce the above copyright notice,
12 this list of conditions and the following disclaimer in the documentation
13 and/or other materials provided with the distribution.
14 
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28#import "reachability.h"
29 
30#import <arpa/inet.h>
31#import <ifaddrs.h>
32#import <netdb.h>
33#import <netinet/in.h>
34#import <netinet6/in6.h>
35#import <sys/socket.h>
36 
37NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";
38 
39@interface Reachability ()
40 
41@property(nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
42@property(nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
43@property(nonatomic, strong) id reachabilityObject;
44 
45- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
46- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
47 
48@end
49 
50static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) {
51 return [NSString
52 stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
53#if TARGET_OS_IPHONE
54 (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
55#else
56 'X',
57#endif
58 (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
59 (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
60 (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
61 (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
62 (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
63 (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
64 (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
65 (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
66}
67 
68// Start listening for reachability notifications on the current run loop
69static void TMReachabilityCallback(SCNetworkReachabilityRef target,
70 SCNetworkReachabilityFlags flags, void *info) {
71#pragma unused(target)
72 
73 Reachability *reachability = ((__bridge Reachability *)info);
74 
75 // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own
76 // autorelease pool, but what the heck eh?
77 @autoreleasepool {
78 [reachability reachabilityChanged:flags];
79 }
80}
81 
82@implementation Reachability
83 
84#pragma mark - Class Constructor Methods
85 
86+ (instancetype)reachabilityWithHostName:(NSString *)hostname {
87 return [Reachability reachabilityWithHostname:hostname];
88}
89 
90+ (instancetype)reachabilityWithHostname:(NSString *)hostname {
91 SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
92 if (ref) {
93 id reachability = [[[self alloc] initWithReachabilityRef:ref] autorelease];
94 
95 return reachability;
96 }
97 
98 return nil;
99}
100 
101+ (instancetype)reachabilityWithAddress:(void *)hostAddress {
102 SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(
103 kCFAllocatorDefault, (const struct sockaddr *)hostAddress);
104 if (ref) {
105 id reachability = [[[self alloc] initWithReachabilityRef:ref] autorelease];
106 
107 return reachability;
108 }
109 
110 return nil;
111}
112 
113+ (instancetype)reachabilityForInternetConnection {
114 struct sockaddr_in zeroAddress;
115 bzero(&zeroAddress, sizeof(zeroAddress));
116 zeroAddress.sin_len = sizeof(zeroAddress);
117 zeroAddress.sin_family = AF_INET;
118 
119 return [self reachabilityWithAddress:&zeroAddress];
120}
121 
122+ (instancetype)reachabilityForLocalWiFi {
123 struct sockaddr_in localWifiAddress;
124 bzero(&localWifiAddress, sizeof(localWifiAddress));
125 localWifiAddress.sin_len = sizeof(localWifiAddress);
126 localWifiAddress.sin_family = AF_INET;
127 // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
128 localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
129 
130 return [self reachabilityWithAddress:&localWifiAddress];
131}
132 
133+ (instancetype)reachabilityWithURL:(NSURL *)url {
134 id reachability;
135 
136 NSString *host = url.host;
137 BOOL isIpAddress = [self isIpAddress:host];
138 
139 if (isIpAddress) {
140 NSNumber *port = url.port ?: [url.scheme isEqualToString:@"https"] ? @(443) : @(80);
141 
142 struct sockaddr_in address;
143 address.sin_len = sizeof(address);
144 address.sin_family = AF_INET;
145 address.sin_port = htons([port intValue]);
146 address.sin_addr.s_addr = inet_addr([host UTF8String]);
147 
148 reachability = [self reachabilityWithAddress:&address];
149 } else {
150 reachability = [self reachabilityWithHostname:host];
151 }
152 
153 return reachability;
154}
155 
156+ (BOOL)isIpAddress:(NSString *)host {
157 struct in_addr pin;
158 return 1 == inet_aton([host UTF8String], &pin);
159}
160 
161// Initialization methods
162 
163- (instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref {
164 self = [super init];
165 if (self != nil) {
166 self.reachableOnWWAN = YES;
167 self.reachabilityRef = ref;
168 
169 // We need to create a serial queue.
170 // We allocate this once for the lifetime of the notifier.
171 
172 self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
173 }
174 
175 return self;
176}
177 
178- (void)dealloc {
179 [self stopNotifier];
180 
181 if (self.reachabilityRef) {
182 CFRelease(self.reachabilityRef);
183 self.reachabilityRef = nil;
184 }
185 
186 self.reachableBlock = nil;
187 self.unreachableBlock = nil;
188 self.reachabilityBlock = nil;
189 self.reachabilitySerialQueue = nil;
190 
191 [super dealloc];
192}
193 
194#pragma mark - Notifier Methods
195 
196// Notifier
197// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
198// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
199// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you
200// want)
201 
202- (BOOL)startNotifier {
203 // allow start notifier to be called multiple times
204 if (self.reachabilityObject && (self.reachabilityObject == self)) {
205 return YES;
206 }
207 
208 SCNetworkReachabilityContext context = {0, NULL, NULL, NULL, NULL};
209 context.info = (__bridge void *)self;
210 
211 if (SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) {
212 // Set it as our reachability queue, which will retain the queue
213 if (SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef,
214 self.reachabilitySerialQueue)) {
215 // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't
216 // disappear out from under ourselves woah
217 self.reachabilityObject = self;
218 return YES;
219 } else {
220#ifdef DEBUG
221 NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
222#endif
223 
224 // UH OH - FAILURE - stop any callbacks!
225 SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
226 }
227 } else {
228#ifdef DEBUG
229 NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
230#endif
231 }
232 
233 // if we get here we fail at the internet
234 self.reachabilityObject = nil;
235 return NO;
236}
237 
238- (void)stopNotifier {
239 // First stop, any callbacks!
240 SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
241 
242 // Unregister target from the GCD serial dispatch queue.
243 SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
244 
245 self.reachabilityObject = nil;
246}
247 
248#pragma mark - reachability tests
249 
250// This is for the case where you flick the airplane mode;
251// you end up getting something like this:
252// Reachability: WR ct-----
253// Reachability: -- -------
254// Reachability: WR ct-----
255// Reachability: -- -------
256// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
257 
258#define testcase \
259 (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
260 
261- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags {
262 BOOL connectionUP = YES;
263 
264 if (!(flags & kSCNetworkReachabilityFlagsReachable)) connectionUP = NO;
265 
266 if ((flags & testcase) == testcase) connectionUP = NO;
267 
268#if TARGET_OS_IPHONE
269 if (flags & kSCNetworkReachabilityFlagsIsWWAN) {
270 // We're on 3G.
271 if (!self.reachableOnWWAN) {
272 // We don't want to connect when on 3G.
273 connectionUP = NO;
274 }
275 }
276#endif
277 
278 return connectionUP;
279}
280 
281- (BOOL)isReachable {
282 SCNetworkReachabilityFlags flags;
283 
284 if (!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) return NO;
285 
286 return [self isReachableWithFlags:flags];
287}
288 
289- (BOOL)isReachableViaWWAN {
290#if TARGET_OS_IPHONE
291 
292 SCNetworkReachabilityFlags flags = 0;
293 
294 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
295 // Check we're REACHABLE
296 if (flags & kSCNetworkReachabilityFlagsReachable) {
297 // Now, check we're on WWAN
298 if (flags & kSCNetworkReachabilityFlagsIsWWAN) {
299 return YES;
300 }
301 }
302 }
303#endif
304 
305 return NO;
306}
307 
308- (BOOL)isReachableViaWiFi {
309 SCNetworkReachabilityFlags flags = 0;
310 
311 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
312 // Check we're reachable
313 if ((flags & kSCNetworkReachabilityFlagsReachable)) {
314#if TARGET_OS_IPHONE
315 // Check we're NOT on WWAN
316 if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) {
317 return NO;
318 }
319#endif
320 return YES;
321 }
322 }
323 
324 return NO;
325}
326 
327// WWAN may be available, but not active until a connection has been established.
328// WiFi may require a connection for VPN on Demand.
329- (BOOL)isConnectionRequired {
330 return [self connectionRequired];
331}
332 
333- (BOOL)connectionRequired {
334 SCNetworkReachabilityFlags flags;
335 
336 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
337 return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
338 }
339 
340 return NO;
341}
342 
343// Dynamic, on demand connection?
344- (BOOL)isConnectionOnDemand {
345 SCNetworkReachabilityFlags flags;
346 
347 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
348 return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
349 (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic |
350 kSCNetworkReachabilityFlagsConnectionOnDemand)));
351 }
352 
353 return NO;
354}
355 
356// Is user intervention required?
357- (BOOL)isInterventionRequired {
358 SCNetworkReachabilityFlags flags;
359 
360 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
361 return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
362 (flags & kSCNetworkReachabilityFlagsInterventionRequired));
363 }
364 
365 return NO;
366}
367 
368#pragma mark - reachability status stuff
369 
370- (NetworkStatus)currentReachabilityStatus {
371 if ([self isReachable]) {
372 if ([self isReachableViaWiFi]) return ReachableViaWiFi;
373 
374#if TARGET_OS_IPHONE
375 return ReachableViaWWAN;
376#endif
377 }
378 
379 return NotReachable;
380}
381 
382- (SCNetworkReachabilityFlags)reachabilityFlags {
383 SCNetworkReachabilityFlags flags = 0;
384 
385 if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) {
386 return flags;
387 }
388 
389 return 0;
390}
391 
392- (NSString *)currentReachabilityString {
393 NetworkStatus temp = [self currentReachabilityStatus];
394 
395 if (temp == ReachableViaWWAN) {
396 // Updated for the fact that we have CDMA phones now!
397 return NSLocalizedString(@"Cellular", @"");
398 }
399 if (temp == ReachableViaWiFi) {
400 return NSLocalizedString(@"WiFi", @"");
401 }
402 
403 return NSLocalizedString(@"No Connection", @"");
404}
405 
406- (NSString *)currentReachabilityFlags {
407 return reachabilityFlags([self reachabilityFlags]);
408}
409 
410#pragma mark - Callback function calls this method
411 
412- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags {
413 if ([self isReachableWithFlags:flags]) {
414 if (self.reachableBlock) {
415 self.reachableBlock(self);
416 }
417 } else {
418 if (self.unreachableBlock) {
419 self.unreachableBlock(self);
420 }
421 }
422 
423 if (self.reachabilityBlock) {
424 self.reachabilityBlock(self, flags);
425 }
426 
427 // this makes sure the change notification happens on the MAIN THREAD
428 dispatch_async(dispatch_get_main_queue(), ^{
429 [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
430 object:self];
431 });
432}
433 
434#pragma mark - Debug Description
435 
436- (NSString *)description {
437 NSString *description =
438 [NSString stringWithFormat:@"<%@: %p (%@)>", NSStringFromClass([self class]), self,
439 [self currentReachabilityFlags]];
440 return description;
441}
442 
443@end
444