You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
10 KiB

6 years ago
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <Foundation/Foundation.h>
  15. #import "Private/FIRReachabilityChecker+Internal.h"
  16. #import "Private/FIRReachabilityChecker.h"
  17. #import "Private/FIRLogger.h"
  18. #import "Private/FIRNetwork.h"
  19. #import "Private/FIRNetworkMessageCode.h"
  20. static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
  21. SCNetworkReachabilityFlags flags,
  22. void *info);
  23. static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = {
  24. SCNetworkReachabilityCreateWithName,
  25. SCNetworkReachabilitySetCallback,
  26. SCNetworkReachabilityScheduleWithRunLoop,
  27. SCNetworkReachabilityUnscheduleFromRunLoop,
  28. CFRelease,
  29. };
  30. static NSString *const kFIRReachabilityUnknownStatus = @"Unknown";
  31. static NSString *const kFIRReachabilityConnectedStatus = @"Connected";
  32. static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
  33. @interface FIRReachabilityChecker ()
  34. @property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi;
  35. @property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus;
  36. @property(nonatomic, copy) NSString *host;
  37. @property(nonatomic, assign) SCNetworkReachabilityRef reachability;
  38. @end
  39. @implementation FIRReachabilityChecker
  40. @synthesize reachabilityApi = reachabilityApi_;
  41. @synthesize reachability = reachability_;
  42. - (const struct FIRReachabilityApi *)reachabilityApi {
  43. return reachabilityApi_;
  44. }
  45. - (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi {
  46. if (reachability_) {
  47. NSString *message =
  48. @"Cannot change reachability API while reachability is running. "
  49. @"Call stop first.";
  50. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  51. messageCode:kFIRNetworkMessageCodeReachabilityChecker000
  52. message:message];
  53. return;
  54. }
  55. reachabilityApi_ = reachabilityApi;
  56. }
  57. @synthesize reachabilityStatus = reachabilityStatus_;
  58. @synthesize host = host_;
  59. @synthesize reachabilityDelegate = reachabilityDelegate_;
  60. @synthesize loggerDelegate = loggerDelegate_;
  61. - (BOOL)isActive {
  62. return reachability_ != nil;
  63. }
  64. - (void)setReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate {
  65. if (reachabilityDelegate &&
  66. (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) {
  67. FIRLogError(kFIRLoggerCore,
  68. [NSString stringWithFormat:@"I-NET%06ld",
  69. (long)kFIRNetworkMessageCodeReachabilityChecker005],
  70. @"Reachability delegate doesn't conform to Reachability protocol.");
  71. return;
  72. }
  73. reachabilityDelegate_ = reachabilityDelegate;
  74. }
  75. - (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
  76. if (loggerDelegate &&
  77. (![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) {
  78. FIRLogError(kFIRLoggerCore,
  79. [NSString stringWithFormat:@"I-NET%06ld",
  80. (long)kFIRNetworkMessageCodeReachabilityChecker006],
  81. @"Reachability delegate doesn't conform to Logger protocol.");
  82. return;
  83. }
  84. loggerDelegate_ = loggerDelegate;
  85. }
  86. - (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
  87. loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
  88. withHost:(NSString *)host {
  89. self = [super init];
  90. [self setLoggerDelegate:loggerDelegate];
  91. if (!host || !host.length) {
  92. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  93. messageCode:kFIRNetworkMessageCodeReachabilityChecker001
  94. message:@"Invalid host specified"];
  95. return nil;
  96. }
  97. if (self) {
  98. [self setReachabilityDelegate:reachabilityDelegate];
  99. reachabilityApi_ = &kFIRDefaultReachabilityApi;
  100. reachabilityStatus_ = kFIRReachabilityUnknown;
  101. host_ = [host copy];
  102. reachability_ = nil;
  103. }
  104. return self;
  105. }
  106. - (void)dealloc {
  107. reachabilityDelegate_ = nil;
  108. loggerDelegate_ = nil;
  109. [self stop];
  110. }
  111. - (BOOL)start {
  112. if (!reachability_) {
  113. reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
  114. if (!reachability_) {
  115. return NO;
  116. }
  117. SCNetworkReachabilityContext context = {
  118. 0, /* version */
  119. (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
  120. NULL, /* retain */
  121. NULL, /* release */
  122. NULL /* copyDescription */
  123. };
  124. if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
  125. !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
  126. kCFRunLoopCommonModes)) {
  127. reachabilityApi_->releaseFn(reachability_);
  128. reachability_ = nil;
  129. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  130. messageCode:kFIRNetworkMessageCodeReachabilityChecker002
  131. message:@"Failed to start reachability handle"];
  132. return NO;
  133. }
  134. }
  135. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  136. messageCode:kFIRNetworkMessageCodeReachabilityChecker003
  137. message:@"Monitoring the network status"];
  138. return YES;
  139. }
  140. - (void)stop {
  141. if (reachability_) {
  142. reachabilityStatus_ = kFIRReachabilityUnknown;
  143. reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
  144. kCFRunLoopCommonModes);
  145. reachabilityApi_->releaseFn(reachability_);
  146. reachability_ = nil;
  147. }
  148. }
  149. - (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
  150. FIRReachabilityStatus status = kFIRReachabilityNotReachable;
  151. // If the Reachable flag is not set, we definitely don't have connectivity.
  152. if (flags & kSCNetworkReachabilityFlagsReachable) {
  153. // Reachable flag is set. Check further flags.
  154. if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
  155. // Connection required flag is not set, so we have connectivity.
  156. #if TARGET_OS_IOS || TARGET_OS_TV
  157. status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
  158. : kFIRReachabilityViaWifi;
  159. #elif TARGET_OS_OSX
  160. status = kFIRReachabilityViaWifi;
  161. #endif
  162. } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
  163. kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
  164. !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
  165. // If the connection on demand or connection on traffic flag is set, and user intervention
  166. // is not required, we have connectivity.
  167. #if TARGET_OS_IOS || TARGET_OS_TV
  168. status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
  169. : kFIRReachabilityViaWifi;
  170. #elif TARGET_OS_OSX
  171. status = kFIRReachabilityViaWifi;
  172. #endif
  173. }
  174. }
  175. return status;
  176. }
  177. - (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
  178. FIRReachabilityStatus status = [self statusForFlags:flags];
  179. if (reachabilityStatus_ != status) {
  180. NSString *reachabilityStatusString;
  181. if (status == kFIRReachabilityUnknown) {
  182. reachabilityStatusString = kFIRReachabilityUnknownStatus;
  183. } else {
  184. reachabilityStatusString = (status == kFIRReachabilityNotReachable)
  185. ? kFIRReachabilityDisconnectedStatus
  186. : kFIRReachabilityConnectedStatus;
  187. }
  188. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  189. messageCode:kFIRNetworkMessageCodeReachabilityChecker004
  190. message:@"Network status has changed. Code, status"
  191. contexts:@[ @(status), reachabilityStatusString ]];
  192. reachabilityStatus_ = status;
  193. [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
  194. }
  195. }
  196. @end
  197. static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
  198. SCNetworkReachabilityFlags flags,
  199. void *info) {
  200. FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info;
  201. [checker reachabilityFlagsChanged:flags];
  202. }
  203. // This function used to be at the top of the file, but it was moved here
  204. // as a workaround for a suspected compiler bug. When compiled in Release mode
  205. // and run on an iOS device with WiFi disabled, the reachability code crashed
  206. // when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
  207. // After unsuccessfully trying to diagnose the cause of the crash, it was
  208. // discovered that moving this function to the end of the file magically fixed
  209. // the crash. If you are going to edit this file, exercise caution and make sure
  210. // to test thoroughly with an iOS device under various network conditions.
  211. const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) {
  212. switch (status) {
  213. case kFIRReachabilityUnknown:
  214. return @"Reachability Unknown";
  215. case kFIRReachabilityNotReachable:
  216. return @"Not reachable";
  217. case kFIRReachabilityViaWifi:
  218. return @"Reachable via Wifi";
  219. case kFIRReachabilityViaCellular:
  220. return @"Reachable via Cellular Data";
  221. default:
  222. return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
  223. }
  224. }