ios/Pods/gRPC/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
/*
 *
 * Copyright 2016 gRPC authors.
 *
 * 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 "GRPCConnectivityMonitor.h"

#include <netinet/in.h>

NSString *kGRPCConnectivityNotification = @"kGRPCConnectivityNotification";

static SCNetworkReachabilityRef reachability;
static GRPCConnectivityStatus currentStatus;

// Aggregate information in flags into network status.
GRPCConnectivityStatus CalculateConnectivityStatus(SCNetworkReachabilityFlags flags) {
  GRPCConnectivityStatus result = GRPCConnectivityUnknown;
  if (((flags & kSCNetworkReachabilityFlagsReachable) == 0) ||
      ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0)) {
    return GRPCConnectivityNoNetwork;
  }
  result = GRPCConnectivityWiFi;
#if TARGET_OS_IPHONE
  if (flags & kSCNetworkReachabilityFlagsIsWWAN) {
    return result = GRPCConnectivityCellular;
  }
#endif
  return result;
}

static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
                                 void *info) {
  GRPCConnectivityStatus newStatus = CalculateConnectivityStatus(flags);

  if (newStatus != currentStatus) {
    [[NSNotificationCenter defaultCenter] postNotificationName:kGRPCConnectivityNotification
                                                        object:nil];
    currentStatus = newStatus;
  }
}

@implementation GRPCConnectivityMonitor

+ (void)initialize {
  if (self == [GRPCConnectivityMonitor self]) {
    struct sockaddr_in addr = {0};
    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    reachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&addr);
    currentStatus = GRPCConnectivityUnknown;

    SCNetworkConnectionFlags flags;
    if (SCNetworkReachabilityGetFlags(reachability, &flags)) {
      currentStatus = CalculateConnectivityStatus(flags);
    }

    SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    if (!SCNetworkReachabilitySetCallback(reachability, ReachabilityCallback, &context) ||
        !SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(),
                                                  kCFRunLoopCommonModes)) {
      NSLog(@"gRPC connectivity monitor fail to set");
    }
  }
}

+ (void)registerObserver:(_Nonnull id)observer selector:(SEL)selector {
  [[NSNotificationCenter defaultCenter] addObserver:observer
                                           selector:selector
                                               name:kGRPCConnectivityNotification
                                             object:nil];
}

+ (void)unregisterObserver:(_Nonnull id)observer {
  [[NSNotificationCenter defaultCenter] removeObserver:observer];
}

@end
