aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Utilities/Reachability/GULReachabilityChecker.m
blob: 1ddacdfc222834a722e30a99be6a3224246a077b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import <Foundation/Foundation.h>

#import "GULReachabilityChecker+Internal.h"
#import "Private/GULReachabilityChecker.h"
#import "Private/GULReachabilityMessageCode.h"

#import <GoogleUtilities/GULLogger.h>
#import <GoogleUtilities/GULReachabilityChecker.h>

static GULLoggerService kGULLoggerReachability = @"[GULReachability]";

static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
                                 SCNetworkReachabilityFlags flags,
                                 void *info);

static const struct GULReachabilityApi kGULDefaultReachabilityApi = {
    SCNetworkReachabilityCreateWithName,
    SCNetworkReachabilitySetCallback,
    SCNetworkReachabilityScheduleWithRunLoop,
    SCNetworkReachabilityUnscheduleFromRunLoop,
    CFRelease,
};

static NSString *const kGULReachabilityUnknownStatus = @"Unknown";
static NSString *const kGULReachabilityConnectedStatus = @"Connected";
static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";

@interface GULReachabilityChecker ()

@property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi;
@property(nonatomic, assign) GULReachabilityStatus reachabilityStatus;
@property(nonatomic, copy) NSString *host;
@property(nonatomic, assign) SCNetworkReachabilityRef reachability;

@end

@implementation GULReachabilityChecker

@synthesize reachabilityApi = reachabilityApi_;
@synthesize reachability = reachability_;

- (const struct GULReachabilityApi *)reachabilityApi {
  return reachabilityApi_;
}

- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi {
  if (reachability_) {
    GULLogError(kGULLoggerReachability, NO,
                [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
                @"Cannot change reachability API while reachability is running. "
                @"Call stop first.");
    return;
  }
  reachabilityApi_ = reachabilityApi;
}

@synthesize reachabilityStatus = reachabilityStatus_;
@synthesize host = host_;
@synthesize reachabilityDelegate = reachabilityDelegate_;

- (BOOL)isActive {
  return reachability_ != nil;
}

- (void)setReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate {
  if (reachabilityDelegate &&
      (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) {
    GULLogError(kGULLoggerReachability, NO,
                [NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005],
                @"Reachability delegate doesn't conform to Reachability protocol.");
    return;
  }
  reachabilityDelegate_ = reachabilityDelegate;
}

- (instancetype)initWithReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate
                                    withHost:(NSString *)host {
  self = [super init];

  if (!host || !host.length) {
    GULLogError(kGULLoggerReachability, NO,
                [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
                @"Invalid host specified");
    return nil;
  }
  if (self) {
    [self setReachabilityDelegate:reachabilityDelegate];
    reachabilityApi_ = &kGULDefaultReachabilityApi;
    reachabilityStatus_ = kGULReachabilityUnknown;
    host_ = [host copy];
    reachability_ = nil;
  }
  return self;
}

- (void)dealloc {
  reachabilityDelegate_ = nil;
  [self stop];
}

- (BOOL)start {
  if (!reachability_) {
    reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
    if (!reachability_) {
      return NO;
    }
    SCNetworkReachabilityContext context = {
        0,                       /* version */
        (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
        NULL,                    /* retain */
        NULL,                    /* release */
        NULL                     /* copyDescription */
    };
    if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
        !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
                                                 kCFRunLoopCommonModes)) {
      reachabilityApi_->releaseFn(reachability_);
      reachability_ = nil;

      GULLogError(kGULLoggerReachability, NO,
                  [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
                  @"Failed to start reachability handle");
      return NO;
    }
  }
  GULLogDebug(kGULLoggerReachability, NO,
              [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
              @"Monitoring the network status");
  return YES;
}

- (void)stop {
  if (reachability_) {
    reachabilityStatus_ = kGULReachabilityUnknown;
    reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
                                              kCFRunLoopCommonModes);
    reachabilityApi_->releaseFn(reachability_);
    reachability_ = nil;
  }
}

- (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
  GULReachabilityStatus status = kGULReachabilityNotReachable;
  // If the Reachable flag is not set, we definitely don't have connectivity.
  if (flags & kSCNetworkReachabilityFlagsReachable) {
    // Reachable flag is set. Check further flags.
    if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
// Connection required flag is not set, so we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV
      status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
                                                           : kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
      status = kGULReachabilityViaWifi;
#endif
    } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
                         kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
               !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
// If the connection on demand or connection on traffic flag is set, and user intervention
// is not required, we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV
      status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
                                                           : kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
      status = kGULReachabilityViaWifi;
#endif
    }
  }
  return status;
}

- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
  GULReachabilityStatus status = [self statusForFlags:flags];
  if (reachabilityStatus_ != status) {
    NSString *reachabilityStatusString;
    if (status == kGULReachabilityUnknown) {
      reachabilityStatusString = kGULReachabilityUnknownStatus;
    } else {
      reachabilityStatusString = (status == kGULReachabilityNotReachable)
                                     ? kGULReachabilityDisconnectedStatus
                                     : kGULReachabilityConnectedStatus;
    }

    GULLogDebug(kGULLoggerReachability, NO,
                [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
                @"Network status has changed. Code:%@, status:%@", @(status),
                reachabilityStatusString);
    reachabilityStatus_ = status;
    [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
  }
}

@end

static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
                                 SCNetworkReachabilityFlags flags,
                                 void *info) {
  GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info;
  [checker reachabilityFlagsChanged:flags];
}

// This function used to be at the top of the file, but it was moved here
// as a workaround for a suspected compiler bug. When compiled in Release mode
// and run on an iOS device with WiFi disabled, the reachability code crashed
// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
// After unsuccessfully trying to diagnose the cause of the crash, it was
// discovered that moving this function to the end of the file magically fixed
// the crash. If you are going to edit this file, exercise caution and make sure
// to test thoroughly with an iOS device under various network conditions.
const NSString *GULReachabilityStatusString(GULReachabilityStatus status) {
  switch (status) {
    case kGULReachabilityUnknown:
      return @"Reachability Unknown";

    case kGULReachabilityNotReachable:
      return @"Not reachable";

    case kGULReachabilityViaWifi:
      return @"Reachable via Wifi";

    case kGULReachabilityViaCellular:
      return @"Reachable via Cellular Data";

    default:
      return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
  }
}