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 "RNCConnectionStateWatcher.h"
9#import <SystemConfiguration/SystemConfiguration.h>
10#import <netinet/in.h>
11
12@interface RNCConnectionStateWatcher () <NSURLSessionDataDelegate>
13
14@property (nonatomic) SCNetworkReachabilityRef reachabilityRef;
15@property (nullable, weak, nonatomic) id<RNCConnectionStateWatcherDelegate> delegate;
16@property (nonatomic) SCNetworkReachabilityFlags lastFlags;
17@property (nonnull, strong, nonatomic) RNCConnectionState *state;
18
19@end
20
21@implementation RNCConnectionStateWatcher
22
23#pragma mark - Lifecycle
24
25- (instancetype)initWithDelegate:(id<RNCConnectionStateWatcherDelegate>)delegate
26{
27    self = [self init];
28    if (self) {
29        _delegate = delegate;
30        _state = [[RNCConnectionState alloc] init];
31        _reachabilityRef = [self createReachabilityRef];
32    }
33    return self;
34}
35
36- (void)dealloc
37{
38    self.delegate = nil;
39
40    if (self.reachabilityRef != nil) {
41        SCNetworkReachabilityUnscheduleFromRunLoop(self.reachabilityRef, CFRunLoopGetMain(), kCFRunLoopCommonModes);
42        CFRelease(self.reachabilityRef);
43        self.reachabilityRef = nil;
44    }
45}
46
47#pragma mark - Public methods
48
49- (RNCConnectionState *)currentState
50{
51    return self.state;
52}
53
54#pragma mark - Callback
55
56typedef void (^RNCConnectionStateUpdater)(SCNetworkReachabilityFlags);
57
58static void RNCReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
59{
60    RNCConnectionStateUpdater block = (__bridge id)info;
61    if (block != nil) {
62        block(flags);
63    }
64}
65
66static void RNCReachabilityContextRelease(const void *info)
67{
68    Block_release(info);
69}
70
71static const void *RNCReachabilityContextRetain(const void *info)
72{
73    return Block_copy(info);
74}
75
76- (void)update:(SCNetworkReachabilityFlags)flags
77{
78    self.lastFlags = flags;
79    self.state = [[RNCConnectionState alloc] initWithReachabilityFlags:flags];
80}
81
82#pragma mark - Setters
83
84- (void)setState:(RNCConnectionState *)state
85{
86    if (![state isEqualToConnectionState:_state]) {
87        _state = state;
88
89        [self updateDelegate];
90    }
91}
92
93#pragma mark - Utilities
94
95- (void)updateDelegate
96{
97    [self.delegate connectionStateWatcher:self didUpdateState:self.state];
98}
99
100- (SCNetworkReachabilityRef)createReachabilityRef
101{
102    struct sockaddr_in zeroAddress;
103    bzero(&zeroAddress, sizeof(zeroAddress));
104    zeroAddress.sin_len = sizeof(zeroAddress);
105    zeroAddress.sin_family = AF_INET;
106
107    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *) &zeroAddress);
108
109    __weak typeof(self) weakSelf = self;
110    RNCConnectionStateUpdater callback = ^(SCNetworkReachabilityFlags flags) {
111        __strong __typeof(weakSelf) strongSelf = weakSelf;
112        if (strongSelf != nil) {
113            [strongSelf update:flags];
114        }
115    };
116
117    SCNetworkReachabilityContext context = {
118        0,
119        (__bridge void *)callback,
120        RNCReachabilityContextRetain,
121        RNCReachabilityContextRelease,
122        NULL
123    };
124    SCNetworkReachabilitySetCallback(reachability, RNCReachabilityCallback, &context);
125    SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
126
127    // Set the state the first time
128    SCNetworkReachabilityFlags flags;
129    SCNetworkReachabilityGetFlags(reachability, &flags);
130    [self update:flags];
131
132    return reachability;
133}
134
135@end
136