/* * * Copyright 2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #import "GRPCConnectivityMonitor.h" #pragma mark Flags @implementation GRPCReachabilityFlags { SCNetworkReachabilityFlags _flags; } + (instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags { return [[self alloc] initWithFlags:flags]; } - (instancetype)initWithFlags:(SCNetworkReachabilityFlags)flags { if ((self = [super init])) { _flags = flags; } return self; } /* * One accessor method implementation per flag. Example: - (BOOL)isCell { \ return !!(_flags & kSCNetworkReachabilityFlagsIsWWAN); \ } */ #define GRPC_XMACRO_ITEM(methodName, FlagName) \ - (BOOL)methodName { \ return !!(_flags & kSCNetworkReachabilityFlags ## FlagName); \ } #include "GRPCReachabilityFlagNames.xmacro.h" #undef GRPC_XMACRO_ITEM - (BOOL)isHostReachable { // Note: connectionOnDemand means it'll be reachable only if using the CFSocketStream API or APIs // on top of it. // connectionRequired means we can't tell until a connection is attempted (e.g. for VPN on // demand). return self.reachable && !self.interventionRequired && !self.connectionOnDemand; } - (NSString *)description { NSMutableArray *activeOptions = [NSMutableArray arrayWithCapacity:9]; /* * For each flag, add its name to the array if it's ON. Example: if (self.isCell) { [activeOptions addObject:@"isCell"]; } */ #define GRPC_XMACRO_ITEM(methodName, FlagName) \ if (self.methodName) { \ [activeOptions addObject:@ #methodName]; \ } #include "GRPCReachabilityFlagNames.xmacro.h" #undef GRPC_XMACRO_ITEM return activeOptions.count == 0 ? @"(none)" : [activeOptions componentsJoinedByString:@", "]; } - (BOOL)isEqual:(id)object { return [object isKindOfClass:[GRPCReachabilityFlags class]] && _flags == ((GRPCReachabilityFlags *)object)->_flags; } - (NSUInteger)hash { return _flags; } @end #pragma mark Connectivity Monitor // Assumes the third argument is a block that accepts a GRPCReachabilityFlags object, and passes the // received ones to it. static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) { #pragma unused (target) // This can be called many times with the same info. The info is retained by SCNetworkReachability // while this function is being executed. void (^handler)(GRPCReachabilityFlags *) = (__bridge void (^)(GRPCReachabilityFlags *))info; handler([[GRPCReachabilityFlags alloc] initWithFlags:flags]); } @implementation GRPCConnectivityMonitor { SCNetworkReachabilityRef _reachabilityRef; GRPCReachabilityFlags *_previousReachabilityFlags; } - (nullable instancetype)initWithReachability:(nullable SCNetworkReachabilityRef)reachability { if (!reachability) { return nil; } if ((self = [super init])) { _reachabilityRef = CFRetain(reachability); _queue = dispatch_get_main_queue(); _previousReachabilityFlags = nil; } return self; } + (nullable instancetype)monitorWithHost:(nonnull NSString *)host { const char *hostName = host.UTF8String; if (!hostName) { [NSException raise:NSInvalidArgumentException format:@"host.UTF8String returns NULL for %@", host]; } SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, hostName); GRPCConnectivityMonitor *returnValue = [[self alloc] initWithReachability:reachability]; if (reachability) { CFRelease(reachability); } return returnValue; } - (void)handleLossWithHandler:(nullable void (^)())lossHandler wifiStatusChangeHandler:(nullable void (^)())wifiStatusChangeHandler { __weak typeof(self) weakSelf = self; [self startListeningWithHandler:^(GRPCReachabilityFlags *flags) { typeof(self) strongSelf = weakSelf; if (strongSelf) { if (lossHandler && !flags.reachable) { lossHandler(); } else if (wifiStatusChangeHandler && strongSelf->_previousReachabilityFlags && (flags.isWWAN ^ strongSelf->_previousReachabilityFlags.isWWAN)) { wifiStatusChangeHandler(); } strongSelf->_previousReachabilityFlags = flags; } }]; } - (void)startListeningWithHandler:(void (^)(GRPCReachabilityFlags *))handler { // Copy to ensure the handler block is in the heap (and so can't be deallocated when this method // returns). void (^copiedHandler)(GRPCReachabilityFlags *) = [handler copy]; SCNetworkReachabilityContext context = { .version = 0, .info = (__bridge void *)copiedHandler, .retain = CFRetain, .release = CFRelease, }; // The following will retain context.info, and release it when the callback is set to NULL. SCNetworkReachabilitySetCallback(_reachabilityRef, PassFlagsToContextInfoBlock, &context); SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _queue); } - (void)stopListening { // This releases the block on context.info. SCNetworkReachabilitySetCallback(_reachabilityRef, NULL, NULL); SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, NULL); } - (void)setQueue:(dispatch_queue_t)queue { _queue = queue ?: dispatch_get_main_queue(); } - (void)dealloc { if (_reachabilityRef) { [self stopListening]; CFRelease(_reachabilityRef); } } @end