1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "RNCNetInfo.h"
9#import "RNCConnectionStateWatcher.h"
10
11#include <ifaddrs.h>
12#include <arpa/inet.h>
13
14#if !TARGET_OS_TV && !TARGET_OS_MACCATALYST
15#import <CoreTelephony/CTCarrier.h>
16#import <CoreTelephony/CTTelephonyNetworkInfo.h>
17#endif
18@import SystemConfiguration.CaptiveNetwork;
19
20#import <React/RCTAssert.h>
21#import <React/RCTBridge.h>
22#import <React/RCTEventDispatcher.h>
23
24@interface RNCNetInfo () <RNCConnectionStateWatcherDelegate>
25
26@property (nonatomic, strong) RNCConnectionStateWatcher *connectionStateWatcher;
27@property (nonatomic) BOOL isObserving;
28@property (nonatomic) NSDictionary *config;
29
30@end
31
32@implementation RNCNetInfo
33
34#pragma mark - Module setup
35
36RCT_EXPORT_MODULE()
37
38// We need RNCReachabilityCallback's and module methods to be called on the same thread so that we can have
39// guarantees about when we mess with the reachability callbacks.
40- (dispatch_queue_t)methodQueue
41{
42  return dispatch_get_main_queue();
43}
44
45+ (BOOL)requiresMainQueueSetup
46{
47  return YES;
48}
49
50#pragma mark - Lifecycle
51
52- (NSArray *)supportedEvents
53{
54  return @[@"netInfo.networkStatusDidChange"];
55}
56
57- (void)startObserving
58{
59  self.isObserving = YES;
60}
61
62- (void)stopObserving
63{
64  self.isObserving = NO;
65}
66
67- (instancetype)init
68{
69  self = [super init];
70  if (self) {
71    _connectionStateWatcher = [[RNCConnectionStateWatcher alloc] initWithDelegate:self];
72  }
73  return self;
74}
75
76- (void)dealloc
77{
78  self.connectionStateWatcher = nil;
79}
80
81#pragma mark - RNCConnectionStateWatcherDelegate
82
83- (void)connectionStateWatcher:(RNCConnectionStateWatcher *)connectionStateWatcher didUpdateState:(RNCConnectionState *)state
84{
85  if (self.isObserving) {
86    NSDictionary *dictionary = [self currentDictionaryFromUpdateState:state withInterface:NULL];
87    [self sendEventWithName:@"netInfo.networkStatusDidChange" body:dictionary];
88  }
89}
90
91#pragma mark - Public API
92
93RCT_EXPORT_METHOD(getCurrentState:(nullable NSString *)requestedInterface resolve:(RCTPromiseResolveBlock)resolve
94                  reject:(__unused RCTPromiseRejectBlock)reject)
95{
96  RNCConnectionState *state = [self.connectionStateWatcher currentState];
97  resolve([self currentDictionaryFromUpdateState:state withInterface:requestedInterface]);
98}
99
100RCT_EXPORT_METHOD(configure:(NSDictionary *)config)
101{
102    self.config = config;
103}
104
105#pragma mark - Utilities
106
107// Converts the state into a dictionary to send over the bridge
108- (NSDictionary *)currentDictionaryFromUpdateState:(RNCConnectionState *)state withInterface:(nullable NSString *)requestedInterface
109{
110  NSString *selectedInterface = requestedInterface ?: state.type;
111  NSMutableDictionary *details = [self detailsFromInterface:selectedInterface withState:state];
112  bool connected = [state.type isEqualToString:selectedInterface] && state.connected;
113  if (connected) {
114    details[@"isConnectionExpensive"] = @(state.expensive);
115  }
116
117  return @{
118    @"type": selectedInterface,
119    @"isConnected": @(connected),
120    @"details": details ?: NSNull.null
121  };
122}
123
124- (NSMutableDictionary *)detailsFromInterface:(nonnull NSString *)requestedInterface withState:(RNCConnectionState *)state
125{
126  NSMutableDictionary *details = [NSMutableDictionary new];
127  if ([requestedInterface isEqualToString: RNCConnectionTypeCellular]) {
128    details[@"cellularGeneration"] = state.cellularGeneration ?: NSNull.null;
129    details[@"carrier"] = [self carrier] ?: NSNull.null;
130  } else if ([requestedInterface isEqualToString: RNCConnectionTypeWifi] || [requestedInterface isEqualToString: RNCConnectionTypeEthernet]) {
131    details[@"ipAddress"] = [self ipAddress] ?: NSNull.null;
132    details[@"subnet"] = [self subnet] ?: NSNull.null;
133    #if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_MACCATALYST
134      /*
135        Without one of the conditions needed to use CNCopyCurrentNetworkInfo, it will leak memory.
136        Clients should only set the shouldFetchWiFiSSID to true after ensuring requirements are met to get (B)SSID.
137      */
138      if (self.config && self.config[@"shouldFetchWiFiSSID"]) {
139        details[@"ssid"] = [self ssid] ?: NSNull.null;
140        details[@"bssid"] = [self bssid] ?: NSNull.null;
141      }
142    #endif
143  }
144  return details;
145}
146
147- (NSString *)carrier
148{
149#if (TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST)
150  return nil;
151#else
152  CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
153  CTCarrier *carrier = [netinfo subscriberCellularProvider];
154  return carrier.carrierName;
155#endif
156}
157
158- (NSString *)ipAddress
159{
160  NSString *address = @"0.0.0.0";
161  struct ifaddrs *interfaces = NULL;
162  struct ifaddrs *temp_addr = NULL;
163  int success = 0;
164  // retrieve the current interfaces - returns 0 on success
165  success = getifaddrs(&interfaces);
166  if (success == 0) {
167    // Loop through linked list of interfaces
168    temp_addr = interfaces;
169    while (temp_addr != NULL) {
170      if (temp_addr->ifa_addr->sa_family == AF_INET) {
171        NSString* ifname = [NSString stringWithUTF8String:temp_addr->ifa_name];
172        if (
173          // Check if interface is en0 which is the wifi connection on the iPhone
174          // and the ethernet connection on the Apple TV
175          [ifname isEqualToString:@"en0"] ||
176          // Check if interface is en1 which is the wifi connection on the Apple TV
177          [ifname isEqualToString:@"en1"]
178        ) {
179          // Get NSString from C String
180          char str[INET_ADDRSTRLEN];
181          inet_ntop(AF_INET, &((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr, str, INET_ADDRSTRLEN);
182          address = [NSString stringWithUTF8String:str];
183        }
184      }
185
186      temp_addr = temp_addr->ifa_next;
187    }
188  }
189  // Free memory
190  freeifaddrs(interfaces);
191  return address;
192}
193
194- (NSString *)subnet
195{
196  NSString *subnet = @"0.0.0.0";
197  struct ifaddrs *interfaces = NULL;
198  struct ifaddrs *temp_addr = NULL;
199  int success = 0;
200  // retrieve the current interfaces - returns 0 on success
201  success = getifaddrs(&interfaces);
202  if (success == 0) {
203    // Loop through linked list of interfaces
204    temp_addr = interfaces;
205    while (temp_addr != NULL) {
206      if (temp_addr->ifa_addr->sa_family == AF_INET) {
207        NSString* ifname = [NSString stringWithUTF8String:temp_addr->ifa_name];
208        if (
209          // Check if interface is en0 which is the wifi connection on the iPhone
210          // and the ethernet connection on the Apple TV
211          [ifname isEqualToString:@"en0"] ||
212          // Check if interface is en1 which is the wifi connection on the Apple TV
213          [ifname isEqualToString:@"en1"]
214        ) {
215          // Get NSString from C String
216          char str[INET_ADDRSTRLEN];
217          inet_ntop(AF_INET, &((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr, str, INET_ADDRSTRLEN);
218          subnet = [NSString stringWithUTF8String:str];
219        }
220      }
221
222      temp_addr = temp_addr->ifa_next;
223    }
224  }
225  // Free memory
226  freeifaddrs(interfaces);
227  return subnet;
228}
229
230#if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_MACCATALYST
231- (NSString *)ssid
232{
233  NSArray *interfaceNames = CFBridgingRelease(CNCopySupportedInterfaces());
234  NSDictionary *SSIDInfo;
235  NSString *SSID = NULL;
236  for (NSString *interfaceName in interfaceNames) {
237    // CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler
238    SSIDInfo = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName));
239    if (SSIDInfo.count > 0) {
240        SSID = SSIDInfo[@"SSID"];
241        if ([SSID isEqualToString:@"Wi-Fi"] || [SSID isEqualToString:@"WLAN"]){
242          SSID = NULL;
243        }
244        break;
245    }
246  }
247  return SSID;
248}
249
250- (NSString *)bssid
251{
252  NSArray *interfaceNames = CFBridgingRelease(CNCopySupportedInterfaces());
253  NSDictionary *networkDetails;
254  NSString *BSSID = NULL;
255  for (NSString *interfaceName in interfaceNames) {
256        // CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler
257      networkDetails = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName));
258      if (networkDetails.count > 0)
259      {
260          BSSID = networkDetails[(NSString *) kCNNetworkInfoKeyBSSID];
261          break;
262      }
263  }
264  return BSSID;
265}
266#endif
267
268@end
269