StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 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 | |
| 37 | NSString *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 | |
| 50 | static 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 |
| 69 | static 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 |