/* Copyright 2012 Mozilla Foundation and Mozilla contributors
 *
 * 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.
 */

#include "NetworkUtils.h"

#include "mozilla/Snprintf.h"
#include "SystemProperty.h"

#include <android/log.h>
#include <limits>
#include "mozilla/dom/network/NetUtils.h"
#include "mozilla/fallible.h"
#include "base/task.h"

#include <errno.h>
#include <string.h>
#include <sys/types.h>  // struct addrinfo
#include <sys/socket.h> // getaddrinfo(), freeaddrinfo()
#include <netdb.h>
#include <arpa/inet.h>  // inet_ntop()

#define _DEBUG 0

#define WARN(args...)   __android_log_print(ANDROID_LOG_WARN,  "NetworkUtils", ## args)
#define ERROR(args...)  __android_log_print(ANDROID_LOG_ERROR,  "NetworkUtils", ## args)

#if _DEBUG
#define NU_DBG(args...)  __android_log_print(ANDROID_LOG_DEBUG, "NetworkUtils" , ## args)
#else
#define NU_DBG(args...)
#endif

using namespace mozilla::dom;
using namespace mozilla::ipc;
using mozilla::system::Property;

static const char* PERSIST_SYS_USB_CONFIG_PROPERTY = "persist.sys.usb.config";
static const char* SYS_USB_CONFIG_PROPERTY         = "sys.usb.config";
static const char* SYS_USB_STATE_PROPERTY          = "sys.usb.state";

static const char* USB_FUNCTION_RNDIS  = "rndis";
static const char* USB_FUNCTION_ADB    = "adb";

// Use this command to continue the function chain.
static const char* DUMMY_COMMAND = "tether status";

// IPV6 Tethering is not supported in AOSP, use the property to
// identify vendor specific support in IPV6. We can remove this flag
// once upstream Android support IPV6 in tethering.
static const char* IPV6_TETHERING = "ro.tethering.ipv6";

// Retry 20 times (2 seconds) for usb state transition.
static const uint32_t USB_FUNCTION_RETRY_TIMES = 20;
// Check "sys.usb.state" every 100ms.
static const uint32_t USB_FUNCTION_RETRY_INTERVAL = 100;

// 1xx - Requested action is proceeding
static const uint32_t NETD_COMMAND_PROCEEDING   = 100;
// 2xx - Requested action has been successfully completed
static const uint32_t NETD_COMMAND_OKAY         = 200;
// 4xx - The command is accepted but the requested action didn't
// take place.
static const uint32_t NETD_COMMAND_FAIL         = 400;
// 5xx - The command syntax or parameters error
static const uint32_t NETD_COMMAND_ERROR        = 500;
// 6xx - Unsolicited broadcasts
static const uint32_t NETD_COMMAND_UNSOLICITED  = 600;

// Broadcast messages
static const uint32_t NETD_COMMAND_INTERFACE_CHANGE     = 600;
static const uint32_t NETD_COMMAND_BANDWIDTH_CONTROLLER = 601;

static const char* INTERFACE_DELIMIT = ",";
static const char* USB_CONFIG_DELIMIT = ",";
static const char* NETD_MESSAGE_DELIMIT = " ";

static const uint32_t BUF_SIZE = 1024;

static const int32_t SUCCESS = 0;

static uint32_t SDK_VERSION;
static uint32_t SUPPORT_IPV6_TETHERING;

struct IFProperties {
  char gateway[Property::VALUE_MAX_LENGTH];
  char dns1[Property::VALUE_MAX_LENGTH];
  char dns2[Property::VALUE_MAX_LENGTH];
};

struct CurrentCommand {
  CommandChain* chain;
  CommandCallback callback;
  char command[MAX_COMMAND_SIZE];
};

typedef Tuple3<NetdCommand*, CommandChain*, CommandCallback> QueueData;

#define GET_CURRENT_NETD_COMMAND   (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a)
#define GET_CURRENT_CHAIN          (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].b)
#define GET_CURRENT_CALLBACK       (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].c)
#define GET_CURRENT_COMMAND        (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a->mData)

// A macro for native function call return value check.
// For native function call, non-zero return value means failure.
#define RETURN_IF_FAILED(rv) do { \
  if (SUCCESS != rv) { \
    return rv; \
  } \
} while (0);

#define WARN_IF_FAILED(rv) do { \
  if (SUCCESS != rv) { \
    WARN("Error (%d) occurred in %s (%s:%d)", rv, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
  } \
} while (0);

static NetworkUtils* gNetworkUtils;
static nsTArray<QueueData> gCommandQueue;
static CurrentCommand gCurrentCommand;
static bool gPending = false;
static nsTArray<nsCString> gReason;
static NetworkParams *gWifiTetheringParms = 0;

static nsTArray<CommandChain*> gCommandChainQueue;

const CommandFunc NetworkUtils::sWifiEnableChain[] = {
  NetworkUtils::clearWifiTetherParms,
  NetworkUtils::wifiFirmwareReload,
  NetworkUtils::startAccessPointDriver,
  NetworkUtils::setAccessPoint,
  NetworkUtils::startSoftAP,
  NetworkUtils::setConfig,
  NetworkUtils::tetherInterface,
  NetworkUtils::addInterfaceToLocalNetwork,
  NetworkUtils::addRouteToLocalNetwork,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::tetheringStatus,
  NetworkUtils::startTethering,
  NetworkUtils::setDnsForwarders,
  NetworkUtils::enableNat,
  NetworkUtils::wifiTetheringSuccess
};

const CommandFunc NetworkUtils::sWifiDisableChain[] = {
  NetworkUtils::clearWifiTetherParms,
  NetworkUtils::stopSoftAP,
  NetworkUtils::stopAccessPointDriver,
  NetworkUtils::wifiFirmwareReload,
  NetworkUtils::untetherInterface,
  NetworkUtils::removeInterfaceFromLocalNetwork,
  NetworkUtils::preTetherInterfaceList,
  NetworkUtils::postTetherInterfaceList,
  NetworkUtils::disableNat,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::stopTethering,
  NetworkUtils::wifiTetheringSuccess
};

const CommandFunc NetworkUtils::sWifiFailChain[] = {
  NetworkUtils::clearWifiTetherParms,
  NetworkUtils::stopSoftAP,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::stopTethering
};

const CommandFunc NetworkUtils::sWifiRetryChain[] = {
  NetworkUtils::clearWifiTetherParms,
  NetworkUtils::stopSoftAP,
  NetworkUtils::stopTethering,

  // sWifiEnableChain:
  NetworkUtils::wifiFirmwareReload,
  NetworkUtils::startAccessPointDriver,
  NetworkUtils::setAccessPoint,
  NetworkUtils::startSoftAP,
  NetworkUtils::setConfig,
  NetworkUtils::tetherInterface,
  NetworkUtils::addInterfaceToLocalNetwork,
  NetworkUtils::addRouteToLocalNetwork,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::tetheringStatus,
  NetworkUtils::startTethering,
  NetworkUtils::setDnsForwarders,
  NetworkUtils::enableNat,
  NetworkUtils::wifiTetheringSuccess
};

const CommandFunc NetworkUtils::sWifiOperationModeChain[] = {
  NetworkUtils::wifiFirmwareReload,
  NetworkUtils::wifiOperationModeSuccess
};

const CommandFunc NetworkUtils::sUSBEnableChain[] = {
  NetworkUtils::setConfig,
  NetworkUtils::enableNat,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::tetherInterface,
  NetworkUtils::addInterfaceToLocalNetwork,
  NetworkUtils::addRouteToLocalNetwork,
  NetworkUtils::tetheringStatus,
  NetworkUtils::startTethering,
  NetworkUtils::setDnsForwarders,
  NetworkUtils::addUpstreamInterface,
  NetworkUtils::usbTetheringSuccess
};

const CommandFunc NetworkUtils::sUSBDisableChain[] = {
  NetworkUtils::untetherInterface,
  NetworkUtils::removeInterfaceFromLocalNetwork,
  NetworkUtils::preTetherInterfaceList,
  NetworkUtils::postTetherInterfaceList,
  NetworkUtils::removeUpstreamInterface,
  NetworkUtils::disableNat,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::stopTethering,
  NetworkUtils::usbTetheringSuccess
};

const CommandFunc NetworkUtils::sUSBFailChain[] = {
  NetworkUtils::stopSoftAP,
  NetworkUtils::setIpForwardingEnabled,
  NetworkUtils::stopTethering
};

const CommandFunc NetworkUtils::sUpdateUpStreamChain[] = {
  NetworkUtils::cleanUpStream,
  NetworkUtils::removeUpstreamInterface,
  NetworkUtils::createUpStream,
  NetworkUtils::addUpstreamInterface,
  NetworkUtils::updateUpStreamSuccess
};

const CommandFunc NetworkUtils::sStartDhcpServerChain[] = {
  NetworkUtils::setConfig,
  NetworkUtils::startTethering,
  NetworkUtils::setDhcpServerSuccess
};

const CommandFunc NetworkUtils::sStopDhcpServerChain[] = {
  NetworkUtils::stopTethering,
  NetworkUtils::setDhcpServerSuccess
};

const CommandFunc NetworkUtils::sNetworkInterfaceEnableAlarmChain[] = {
  NetworkUtils::enableAlarm,
  NetworkUtils::setQuota,
  NetworkUtils::setAlarm,
  NetworkUtils::networkInterfaceAlarmSuccess
};

const CommandFunc NetworkUtils::sNetworkInterfaceDisableAlarmChain[] = {
  NetworkUtils::removeQuota,
  NetworkUtils::disableAlarm,
  NetworkUtils::networkInterfaceAlarmSuccess
};

const CommandFunc NetworkUtils::sNetworkInterfaceSetAlarmChain[] = {
  NetworkUtils::setAlarm,
  NetworkUtils::networkInterfaceAlarmSuccess
};

const CommandFunc NetworkUtils::sGetInterfacesChain[] = {
  NetworkUtils::getInterfaceList,
  NetworkUtils::getInterfacesSuccess
};

const CommandFunc NetworkUtils::sGetInterfaceConfigChain[] = {
  NetworkUtils::getConfig,
  NetworkUtils::getInterfaceConfigSuccess
};

const CommandFunc NetworkUtils::sSetInterfaceConfigChain[] = {
  NetworkUtils::setConfig,
  NetworkUtils::setInterfaceConfigSuccess
};

const CommandFunc NetworkUtils::sTetheringInterfaceSetAlarmChain[] = {
  NetworkUtils::setGlobalAlarm,
  NetworkUtils::removeAlarm,
  NetworkUtils::networkInterfaceAlarmSuccess
};

const CommandFunc NetworkUtils::sTetheringInterfaceRemoveAlarmChain[] = {
  NetworkUtils::removeGlobalAlarm,
  NetworkUtils::setAlarm,
  NetworkUtils::networkInterfaceAlarmSuccess
};

const CommandFunc NetworkUtils::sTetheringGetStatusChain[] = {
  NetworkUtils::tetheringStatus,
  NetworkUtils::defaultAsyncSuccessHandler
};

/**
 * Helper function to get the mask from given prefix length.
 */
static uint32_t makeMask(const uint32_t prefixLength)
{
  uint32_t mask = 0;
  for (uint32_t i = 0; i < prefixLength; ++i) {
    mask |= (0x80000000 >> i);
  }
  return ntohl(mask);
}

/**
 * Helper function to get the network part of an ip from prefix.
 * param ip must be in network byte order.
 */
static char* getNetworkAddr(const uint32_t ip, const uint32_t prefix)
{
  uint32_t mask = 0, subnet = 0;

  mask = ~mask << (32 - prefix);
  mask = htonl(mask);
  subnet = ip & mask;

  struct in_addr addr;
  addr.s_addr = subnet;

  return inet_ntoa(addr);
}

/**
 * Helper function to split string by seperator, store split result as an nsTArray.
 */
static void split(char* str, const char* sep, nsTArray<nsCString>& result)
{
  char *s = strtok(str, sep);
  while (s != nullptr) {
    result.AppendElement(s);
    s = strtok(nullptr, sep);
  }
}

static void split(char* str, const char* sep, nsTArray<nsString>& result)
{
  char *s = strtok(str, sep);
  while (s != nullptr) {
    result.AppendElement(NS_ConvertUTF8toUTF16(s));
    s = strtok(nullptr, sep);
  }
}

/**
 * Helper function that implement join function.
 */
static void join(nsTArray<nsCString>& array,
                 const char* sep,
                 const uint32_t maxlen,
                 char* result)
{
#define CHECK_LENGTH(len, add, max)  len += add;          \
                                     if (len > max - 1)   \
                                       return;            \

  uint32_t len = 0;
  uint32_t seplen = strlen(sep);

  if (array.Length() > 0) {
    CHECK_LENGTH(len, strlen(array[0].get()), maxlen)
    strcpy(result, array[0].get());

    for (uint32_t i = 1; i < array.Length(); i++) {
      CHECK_LENGTH(len, seplen, maxlen)
      strcat(result, sep);

      CHECK_LENGTH(len, strlen(array[i].get()), maxlen)
      strcat(result, array[i].get());
    }
  }

#undef CHECK_LEN
}

static void convertUTF8toUTF16(nsTArray<nsCString>& narrow,
                               nsTArray<nsString>& wide,
                               uint32_t length)
{
  for (uint32_t i = 0; i < length; i++) {
    wide.AppendElement(NS_ConvertUTF8toUTF16(narrow[i].get()));
  }
}

/**
 * Helper function to get network interface properties from the system property table.
 */
static void getIFProperties(const char* ifname, IFProperties& prop)
{
  char key[Property::KEY_MAX_LENGTH];
  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.gw", ifname);
  Property::Get(key, prop.gateway, "");
  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns1", ifname);
  Property::Get(key, prop.dns1, "");
  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns2", ifname);
  Property::Get(key, prop.dns2, "");
}

static int getIpType(const char *aIp) {
  struct addrinfo hint, *ip_info = NULL;

  memset(&hint, 0, sizeof(hint));
  hint.ai_family = AF_UNSPEC;
  hint.ai_flags = AI_NUMERICHOST;

  if (getaddrinfo(aIp, NULL, &hint, &ip_info)) {
    return AF_UNSPEC;
  }

  int type = ip_info->ai_family;
  freeaddrinfo(ip_info);

  return type;
}

static void postMessage(NetworkResultOptions& aResult)
{
  MOZ_ASSERT(gNetworkUtils);
  MOZ_ASSERT(gNetworkUtils->getMessageCallback());

  if (*(gNetworkUtils->getMessageCallback()))
    (*(gNetworkUtils->getMessageCallback()))(aResult);
}

static void postMessage(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  MOZ_ASSERT(gNetworkUtils);
  MOZ_ASSERT(gNetworkUtils->getMessageCallback());

  aResult.mId = aOptions.mId;

  if (*(gNetworkUtils->getMessageCallback()))
    (*(gNetworkUtils->getMessageCallback()))(aResult);
}

void NetworkUtils::runNextQueuedCommandChain()
{
  if (gCommandChainQueue.IsEmpty()) {
    NU_DBG("No command chain left in the queue. Done!");
    return;
  }
  NU_DBG("Process the queued command chain.");
  CommandChain* nextChain = gCommandChainQueue[0];
  NetworkResultOptions newResult;
  next(nextChain, false, newResult);
}

void NetworkUtils::next(CommandChain* aChain, bool aError, NetworkResultOptions& aResult)
{
  if (aError) {
    ErrorCallback onError = aChain->getErrorCallback();
    if(onError) {
      aResult.mError = true;
      (*onError)(aChain->getParams(), aResult);
    }
    delete aChain;
    gCommandChainQueue.RemoveElementAt(0);
    runNextQueuedCommandChain();
    return;
  }
  CommandFunc f = aChain->getNextCommand();
  if (!f) {
    delete aChain;
    gCommandChainQueue.RemoveElementAt(0);
    runNextQueuedCommandChain();
    return;
  }

  (*f)(aChain, next, aResult);
}

CommandResult::CommandResult(int32_t aResultCode)
  : mIsPending(false)
{
  // This is usually not a netd command. We treat the return code
  // typical linux convention, which uses 0 to indicate success.
  mResult.mError = (aResultCode == SUCCESS ? false : true);
  mResult.mResultCode = aResultCode;
  if (aResultCode != SUCCESS) {
    // The returned value is sometimes negative, make sure we pass a positive
    // error number to strerror.
    enum { STRERROR_R_BUF_SIZE = 1024, };
    char strerrorBuf[STRERROR_R_BUF_SIZE];
    strerror_r(abs(aResultCode), strerrorBuf, STRERROR_R_BUF_SIZE);
    mResult.mReason = NS_ConvertUTF8toUTF16(strerrorBuf);
  }
}

CommandResult::CommandResult(const mozilla::dom::NetworkResultOptions& aResult)
  : mResult(aResult)
  , mIsPending(false)
{
}

CommandResult::CommandResult(const Pending&)
  : mIsPending(true)
{
}

bool CommandResult::isPending() const
{
  return mIsPending;
}

/**
 * Send command to netd.
 */
void NetworkUtils::nextNetdCommand()
{
  if (gCommandQueue.IsEmpty() || gPending) {
    return;
  }

  gCurrentCommand.chain = GET_CURRENT_CHAIN;
  gCurrentCommand.callback = GET_CURRENT_CALLBACK;
  snprintf(gCurrentCommand.command, MAX_COMMAND_SIZE - 1, "%s", GET_CURRENT_COMMAND);

  NU_DBG("Sending \'%s\' command to netd.", gCurrentCommand.command);
  SendNetdCommand(GET_CURRENT_NETD_COMMAND);

  gCommandQueue.RemoveElementAt(0);
  gPending = true;
}

/**
 * Composite NetdCommand sent to netd
 *
 * @param aCommand  Command sent to netd to execute.
 * @param aChain    Store command chain data, ex. command parameter.
 * @param aCallback Callback function to be executed when the result of
 *                  this command is returned from netd.
 */
void NetworkUtils::doCommand(const char* aCommand, CommandChain* aChain, CommandCallback aCallback)
{
  NU_DBG("Preparing to send \'%s\' command...", aCommand);

  NetdCommand* netdCommand = new NetdCommand();

  // Android JB version adds sequence number to netd command.
  if (SDK_VERSION >= 16) {
    snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "0 %s", aCommand);
  } else {
    snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "%s", aCommand);
  }
  netdCommand->mSize = strlen((char*)netdCommand->mData) + 1;

  gCommandQueue.AppendElement(QueueData(netdCommand, aChain, aCallback));

  nextNetdCommand();
}

/*
 * Netd command function
 */
#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aChain->getParams().prop).get()
#define GET_FIELD(prop) aChain->getParams().prop

void NetworkUtils::wifiFirmwareReload(CommandChain* aChain,
                                      CommandCallback aCallback,
                                      NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "softap fwreload %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::startAccessPointDriver(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
{
  // Skip the command for sdk version >= 16.
  if (SDK_VERSION >= 16) {
    aResult.mResultCode = 0;
    aResult.mResultReason = NS_ConvertUTF8toUTF16("");
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "softap start %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::stopAccessPointDriver(CommandChain* aChain,
                                         CommandCallback aCallback,
                                         NetworkResultOptions& aResult)
{
  // Skip the command for sdk version >= 16.
  if (SDK_VERSION >= 16) {
    aResult.mResultCode = 0;
    aResult.mResultReason = NS_ConvertUTF8toUTF16("");
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "softap stop %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

/**
 * Command format for sdk version < 16
 *   Arguments:
 *     argv[2] - wlan interface
 *     argv[3] - SSID
 *     argv[4] - Security
 *     argv[5] - Key
 *     argv[6] - Channel
 *     argv[7] - Preamble
 *     argv[8] - Max SCB
 *
 * Command format for sdk version >= 16
 *   Arguments:
 *     argv[2] - wlan interface
 *     argv[3] - SSID
 *     argv[4] - Security
 *     argv[5] - Key
 *
 * Command format for sdk version >= 18
 *   Arguments:
 *      argv[2] - wlan interface
 *      argv[3] - SSID
 *      argv[4] - Broadcast/Hidden
 *      argv[5] - Channel
 *      argv[6] - Security
 *      argv[7] - Key
 */
void NetworkUtils::setAccessPoint(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  nsCString ssid(GET_CHAR(mSsid));
  nsCString key(GET_CHAR(mKey));

  escapeQuote(ssid);
  escapeQuote(key);

  if (SDK_VERSION >= 19) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" broadcast 6 %s \"%s\"",
             GET_CHAR(mIfname),
             ssid.get(),
             GET_CHAR(mSecurity),
             key.get());
  } else if (SDK_VERSION >= 16) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" %s \"%s\"",
             GET_CHAR(mIfname),
             ssid.get(),
             GET_CHAR(mSecurity),
             key.get());
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s %s \"%s\" %s \"%s\" 6 0 8",
             GET_CHAR(mIfname),
             GET_CHAR(mWifictrlinterfacename),
             ssid.get(),
             GET_CHAR(mSecurity),
             key.get());
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::cleanUpStream(CommandChain* aChain,
                                 CommandCallback aCallback,
                                 NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0", GET_CHAR(mPreInternalIfname), GET_CHAR(mPreExternalIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::createUpStream(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0", GET_CHAR(mCurInternalIfname), GET_CHAR(mCurExternalIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::startSoftAP(CommandChain* aChain,
                               CommandCallback aCallback,
                               NetworkResultOptions& aResult)
{
  const char* command= "softap startap";
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::stopSoftAP(CommandChain* aChain,
                              CommandCallback aCallback,
                              NetworkResultOptions& aResult)
{
  const char* command= "softap stopap";
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::clearWifiTetherParms(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
{
  delete gWifiTetheringParms;
  gWifiTetheringParms = 0;
  next(aChain, false, aResult);
}

void NetworkUtils::enableAlarm(CommandChain* aChain,
                               CommandCallback aCallback,
                               NetworkResultOptions& aResult)
{
  const char* command= "bandwidth enable";
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::disableAlarm(CommandChain* aChain,
                                CommandCallback aCallback,
                                NetworkResultOptions& aResult)
{
  const char* command= "bandwidth disable";
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setQuota(CommandChain* aChain,
                            CommandCallback aCallback,
                            NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setiquota %s % " PRId64, GET_CHAR(mIfname), INT64_MAX);

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeQuota(CommandChain* aChain,
                               CommandCallback aCallback,
                               NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeiquota %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setAlarm(CommandChain* aChain,
                            CommandCallback aCallback,
                            NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setinterfacealert %s %lld",
           GET_CHAR(mIfname), GET_FIELD(mThreshold));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeAlarm(CommandChain* aChain,
                            CommandCallback aCallback,
                            NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setGlobalAlarm(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %lld", GET_FIELD(mThreshold));
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeGlobalAlarm(CommandChain* aChain,
                                     CommandCallback aCallback,
                                     NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert");
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::tetherInterface(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addInterfaceToLocalNetwork(CommandChain* aChain,
                                              CommandCallback aCallback,
                                              NetworkResultOptions& aResult)
{
  // Skip the command for sdk version < 20.
  if (SDK_VERSION < 20) {
    aResult.mResultCode = 0;
    aResult.mResultReason = NS_ConvertUTF8toUTF16("");
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add local %s",
           GET_CHAR(mInternalIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addRouteToLocalNetwork(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
{
  // Skip the command for sdk version < 20.
  if (SDK_VERSION < 20) {
    aResult.mResultCode = 0;
    aResult.mResultReason = NS_ConvertUTF8toUTF16("");
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  uint32_t prefix = atoi(GET_CHAR(mPrefix));
  uint32_t ip = inet_addr(GET_CHAR(mIp));
  char* networkAddr = getNetworkAddr(ip, prefix);

  snprintf(command, MAX_COMMAND_SIZE - 1, "network route add local %s %s/%s",
           GET_CHAR(mInternalIfname), networkAddr, GET_CHAR(mPrefix));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::preTetherInterfaceList(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  if (SDK_VERSION >= 16) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list");
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list 0");
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::postTetherInterfaceList(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
{
  // Send the dummy command to continue the function chain.
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);

  char buf[BUF_SIZE];
  NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);

  size_t length = reason.Length() + 1 < BUF_SIZE ? reason.Length() + 1 : BUF_SIZE;
  memcpy(buf, reason.get(), length);
  split(buf, INTERFACE_DELIMIT, GET_FIELD(mInterfaceList));

  doCommand(command, aChain, aCallback);
}

bool isCommandChainIPv6(CommandChain* aChain, const char *externalInterface) {
  // Check by gateway address
  if (getIpType(GET_CHAR(mGateway)) == AF_INET6) {
    return true;
  }

  uint32_t length = GET_FIELD(mGateways).Length();
  for (uint32_t i = 0; i < length; i++) {
    NS_ConvertUTF16toUTF8 autoGateway(GET_FIELD(mGateways)[i]);
    if(getIpType(autoGateway.get()) == AF_INET6) {
      return true;
    }
  }

  // Check by external inteface address
  FILE *file = fopen("/proc/net/if_inet6", "r");
  if (!file) {
    return false;
  }

  bool isIPv6 = false;
  char interface[32];
  while(fscanf(file, "%*s %*s %*s %*s %*s %32s", interface)) {
    if (strcmp(interface, externalInterface) == 0) {
      isIPv6 = true;
      break;
    }
  }

  fclose(file);
  return isIPv6;
}

void NetworkUtils::addUpstreamInterface(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
{
  nsCString interface(GET_CHAR(mExternalIfname));
  if (!interface.get()[0]) {
    interface = GET_CHAR(mCurExternalIfname);
  }

  if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s",
           interface.get());
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeUpstreamInterface(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
{
  nsCString interface(GET_CHAR(mExternalIfname));
  if (!interface.get()[0]) {
    interface = GET_CHAR(mPreExternalIfname);
  }

  if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s",
           interface.get());
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setIpForwardingEnabled(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  if (GET_FIELD(mEnable)) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd enable");
  } else {
    // Don't disable ip forwarding because others interface still need it.
    // Send the dummy command to continue the function chain.
    if (GET_FIELD(mInterfaceList).Length() > 1) {
      snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
    } else {
      snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd disable");
    }
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::tetheringStatus(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
{
  const char* command= "tether status";
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::stopTethering(CommandChain* aChain,
                                 CommandCallback aCallback,
                                 NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  // Don't stop tethering because others interface still need it.
  // Send the dummy to continue the function chain.
  if (GET_FIELD(mInterfaceList).Length() > 1) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "tether stop");
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::startTethering(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  // We don't need to start tethering again.
  // Send the dummy command to continue the function chain.
  if (aResult.mResultReason.Find("started") != kNotFound) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
  } else {
    // If usbStartIp/usbEndIp is not valid, don't append them since
    // the trailing white spaces will be parsed to extra empty args
    // See: http://androidxref.com/4.3_r2.1/xref/system/core/libsysutils/src/FrameworkListener.cpp#78
    if (!GET_FIELD(mUsbStartIp).IsEmpty() && !GET_FIELD(mUsbEndIp).IsEmpty()) {
      snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s %s %s",
               GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp),
               GET_CHAR(mUsbStartIp),  GET_CHAR(mUsbEndIp));
    } else {
      snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s",
               GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp));
    }
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::untetherInterface(CommandChain* aChain,
                                     CommandCallback aCallback,
                                     NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeInterfaceFromLocalNetwork(CommandChain* aChain,
                                                   CommandCallback aCallback,
                                                   NetworkResultOptions& aResult)
{
  // Skip the command for sdk version < 20.
  if (SDK_VERSION < 20) {
    aResult.mResultCode = 0;
    aResult.mResultReason = NS_ConvertUTF8toUTF16("");
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface remove local %s",
           GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setDnsForwarders(CommandChain* aChain,
                                    CommandCallback aCallback,
                                    NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  if (SDK_VERSION >= 20) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %d %s %s",
             GET_FIELD(mNetId), GET_CHAR(mDns1), GET_CHAR(mDns2));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %s %s",
             GET_CHAR(mDns1), GET_CHAR(mDns2));
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::enableNat(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
    uint32_t prefix = atoi(GET_CHAR(mPrefix));
    uint32_t ip = inet_addr(GET_CHAR(mIp));
    char* networkAddr = getNetworkAddr(ip, prefix);

    // address/prefix will only take effect when secondary routing table exists.
    snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 1 %s/%s",
             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
             GET_CHAR(mPrefix));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0",
             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::disableNat(CommandChain* aChain,
                              CommandCallback aCallback,
                              NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];

  if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
    uint32_t prefix = atoi(GET_CHAR(mPrefix));
    uint32_t ip = inet_addr(GET_CHAR(mIp));
    char* networkAddr = getNetworkAddr(ip, prefix);

    snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 1 %s/%s",
             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
             GET_CHAR(mPrefix));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0",
             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setDefaultInterface(CommandChain* aChain,
                                       CommandCallback aCallback,
                                       NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "resolver setdefaultif %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeDefaultRoute(CommandChain* aChain,
                                      CommandCallback aCallback,
                                      NetworkResultOptions& aResult)
{
  if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  nsTArray<nsString>& gateways = GET_FIELD(mGateways);
  NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);

  int type = getIpType(autoGateway.get());
  snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s",
           GET_FIELD(mNetId), GET_CHAR(mIfname),
           type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());

  struct MyCallback {
    static void callback(CommandCallback::CallbackType aOriginalCallback,
                         CommandChain* aChain,
                         bool aError,
                         mozilla::dom::NetworkResultOptions& aResult)
    {
      NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
      NU_DBG("removeDefaultRoute's reason: %s", reason.get());
      if (aError && !reason.EqualsASCII("removeRoute() failed (No such process)")) {
        return aOriginalCallback(aChain, aError, aResult);
      }

      GET_FIELD(mLoopIndex)++;
      return removeDefaultRoute(aChain, aOriginalCallback, aResult);
    }
  };

  CommandCallback wrappedCallback(MyCallback::callback, aCallback);
  doCommand(command, aChain, wrappedCallback);
}

void NetworkUtils::setInterfaceDns(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  int written;

  if (SDK_VERSION >= 20) {
    written = snprintf_literal(command, "resolver setnetdns %d %s",
                               GET_FIELD(mNetId), GET_CHAR(mDomain));
  } else {
    written = snprintf_literal(command, "resolver setifdns %s %s",
                               GET_CHAR(mIfname), GET_CHAR(mDomain));
  }

  nsTArray<nsString>& dnses = GET_FIELD(mDnses);
  uint32_t length = dnses.Length();

  for (uint32_t i = 0; i < length; i++) {
    NS_ConvertUTF16toUTF8 autoDns(dnses[i]);

    int ret = snprintf(command + written, sizeof(command) - written, " %s", autoDns.get());
    if (ret <= 1) {
      command[written] = '\0';
      continue;
    }

    if (((size_t)ret + written) >= sizeof(command)) {
      command[written] = '\0';
      break;
    }

    written += ret;
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::getInterfaceList(CommandChain* aChain,
                                    CommandCallback aCallback,
                                    NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "interface list");

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::getConfig(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "interface getcfg %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setConfig(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  if (SDK_VERSION >= 16) {
    snprintf(command, MAX_COMMAND_SIZE - 1, "interface setcfg %s %s %s %s",
                      GET_CHAR(mIfname),
                      GET_CHAR(mIp),
                      GET_CHAR(mPrefix),
                      GET_CHAR(mLink));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1, "interface setcfg %s %s %s [%s]",
                      GET_CHAR(mIfname),
                      GET_CHAR(mIp),
                      GET_CHAR(mPrefix),
                      GET_CHAR(mLink));
  }
  doCommand(command, aChain, aCallback);
}

void NetworkUtils::clearAddrForInterface(CommandChain* aChain,
                                         CommandCallback aCallback,
                                         NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "interface clearaddrs %s", GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::createNetwork(CommandChain* aChain,
                                 CommandCallback aCallback,
                                 NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network create %d", GET_FIELD(mNetId));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::destroyNetwork(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network destroy %d", GET_FIELD(mNetId));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addInterfaceToNetwork(CommandChain* aChain,
                                         CommandCallback aCallback,
                                         NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add %d %s",
           GET_FIELD(mNetId), GET_CHAR(mIfname));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addRouteToInterface(CommandChain* aChain,
                                       CommandCallback aCallback,
                                       NetworkResultOptions& aResult)
{
  struct MyCallback {
    static void callback(CommandCallback::CallbackType aOriginalCallback,
                         CommandChain* aChain,
                         bool aError,
                         mozilla::dom::NetworkResultOptions& aResult)
    {
      NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
      NU_DBG("addRouteToInterface's reason: %s", reason.get());
      if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) {
        NU_DBG("Ignore \"File exists\" error when adding host route.");
        return aOriginalCallback(aChain, false, aResult);
      }
      aOriginalCallback(aChain, aError, aResult);
    }
  };

  CommandCallback wrappedCallback(MyCallback::callback, aCallback);
  modifyRouteOnInterface(aChain, wrappedCallback, aResult, true);
}

void NetworkUtils::removeRouteFromInterface(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult)
{
  modifyRouteOnInterface(aChain, aCallback, aResult, false);
}

void NetworkUtils::modifyRouteOnInterface(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult,
                                          bool aDoAdd)
{
  char command[MAX_COMMAND_SIZE];

  // AOSP adds host route to its interface table but it doesn't work for
  // B2G because we cannot set fwmark per application. So, we add
  // all host routes to legacy_system table except scope link route.

  nsCString ipOrSubnetIp = NS_ConvertUTF16toUTF8(GET_FIELD(mIp));
  nsCString gatewayOrEmpty;
  const char* legacyOrEmpty = "legacy 0 "; // Add to legacy by default.
  if (GET_FIELD(mGateway).IsEmpty()) {
    ipOrSubnetIp = getSubnetIp(ipOrSubnetIp, GET_FIELD(mPrefixLength));
    legacyOrEmpty = ""; // Add to interface table for scope link route.
  } else {
    gatewayOrEmpty = nsCString(" ") + NS_ConvertUTF16toUTF8(GET_FIELD(mGateway));
  }

  const char* action = aDoAdd ? "add" : "remove";

  snprintf(command, MAX_COMMAND_SIZE - 1, "network route %s%s %d %s %s/%d%s",
           legacyOrEmpty, action,
           GET_FIELD(mNetId), GET_CHAR(mIfname), ipOrSubnetIp.get(),
           GET_FIELD(mPrefixLength), gatewayOrEmpty.get());

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult)
{
  if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
    aCallback(aChain, false, aResult);
    return;
  }

  char command[MAX_COMMAND_SIZE];
  nsTArray<nsString>& gateways = GET_FIELD(mGateways);
  NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);

  int type = getIpType(autoGateway.get());
  snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s",
           GET_FIELD(mNetId), GET_CHAR(mIfname),
           type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());

  struct MyCallback {
    static void callback(CommandCallback::CallbackType aOriginalCallback,
                         CommandChain* aChain,
                         bool aError,
                         mozilla::dom::NetworkResultOptions& aResult)
    {
      NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
      NU_DBG("addDefaultRouteToNetwork's reason: %s", reason.get());
      if (aError && !reason.EqualsASCII("addRoute() failed (File exists)")) {
        return aOriginalCallback(aChain, aError, aResult);
      }

      GET_FIELD(mLoopIndex)++;
      return addDefaultRouteToNetwork(aChain, aOriginalCallback, aResult);
    }
  };

  CommandCallback wrappedCallback(MyCallback::callback, aCallback);
  doCommand(command, aChain, wrappedCallback);
}

void NetworkUtils::setDefaultNetwork(CommandChain* aChain,
                                     CommandCallback aCallback,
                                     NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "network default set %d", GET_FIELD(mNetId));

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::addRouteToSecondaryTable(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult) {

  char command[MAX_COMMAND_SIZE];

  if (SDK_VERSION >= 20) {
    snprintf(command, MAX_COMMAND_SIZE - 1,
             "network route add %d %s %s/%s %s",
             GET_FIELD(mNetId),
             GET_CHAR(mIfname),
             GET_CHAR(mIp),
             GET_CHAR(mPrefix),
             GET_CHAR(mGateway));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1,
             "interface route add %s secondary %s %s %s",
             GET_CHAR(mIfname),
             GET_CHAR(mIp),
             GET_CHAR(mPrefix),
             GET_CHAR(mGateway));
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::removeRouteFromSecondaryTable(CommandChain* aChain,
                                                 CommandCallback aCallback,
                                                 NetworkResultOptions& aResult) {
  char command[MAX_COMMAND_SIZE];

  if (SDK_VERSION >= 20) {
    snprintf(command, MAX_COMMAND_SIZE - 1,
             "network route remove %d %s %s/%s %s",
             GET_FIELD(mNetId),
             GET_CHAR(mIfname),
             GET_CHAR(mIp),
             GET_CHAR(mPrefix),
             GET_CHAR(mGateway));
  } else {
    snprintf(command, MAX_COMMAND_SIZE - 1,
             "interface route remove %s secondary %s %s %s",
             GET_CHAR(mIfname),
             GET_CHAR(mIp),
             GET_CHAR(mPrefix),
             GET_CHAR(mGateway));
  }

  doCommand(command, aChain, aCallback);
}

void NetworkUtils::setIpv6Enabled(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult,
                                  bool aEnabled)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "interface ipv6 %s %s",
           GET_CHAR(mIfname), aEnabled ? "enable" : "disable");

  struct MyCallback {
    static void callback(CommandCallback::CallbackType aOriginalCallback,
                         CommandChain* aChain,
                         bool aError,
                         mozilla::dom::NetworkResultOptions& aResult)
    {
      aOriginalCallback(aChain, false, aResult);
    }
  };

  CommandCallback wrappedCallback(MyCallback::callback, aCallback);
  doCommand(command, aChain, wrappedCallback);
}

void NetworkUtils::enableIpv6(CommandChain* aChain,
                              CommandCallback aCallback,
                              NetworkResultOptions& aResult)
{
  setIpv6Enabled(aChain, aCallback, aResult, true);
}

void NetworkUtils::disableIpv6(CommandChain* aChain,
                               CommandCallback aCallback,
                               NetworkResultOptions& aResult)
{
  setIpv6Enabled(aChain, aCallback, aResult, false);
}

void NetworkUtils::setMtu(CommandChain* aChain,
                          CommandCallback aCallback,
                          NetworkResultOptions& aResult)
{
  char command[MAX_COMMAND_SIZE];
  snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %ld",
           GET_CHAR(mIfname), GET_FIELD(mMtu));

  doCommand(command, aChain, aCallback);
}

#undef GET_CHAR
#undef GET_FIELD

/*
 * Netd command success/fail function
 */
#define ASSIGN_FIELD(prop)  aResult.prop = aChain->getParams().prop;
#define ASSIGN_FIELD_VALUE(prop, value)  aResult.prop = value;

template<size_t N>
void NetworkUtils::runChain(const NetworkParams& aParams,
                            const CommandFunc (&aCmds)[N],
                            ErrorCallback aError)
{
  CommandChain* chain = new CommandChain(aParams, aCmds, N, aError);
  gCommandChainQueue.AppendElement(chain);

  if (gCommandChainQueue.Length() > 1) {
    NU_DBG("%d command chains are queued. Wait!", gCommandChainQueue.Length());
    return;
  }

  NetworkResultOptions result;
  NetworkUtils::next(gCommandChainQueue[0], false, result);
}

// Called to clean up the command chain and process the queued command chain if any.
void NetworkUtils::finalizeSuccess(CommandChain* aChain,
                                   NetworkResultOptions& aResult)
{
  next(aChain, false, aResult);
}

void NetworkUtils::wifiTetheringFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  // Notify the main thread.
  postMessage(aOptions, aResult);

  // If one of the stages fails, we try roll back to ensure
  // we don't leave the network systems in limbo.
  ASSIGN_FIELD_VALUE(mEnable, false)
  runChain(aOptions, sWifiFailChain, nullptr);
}

void NetworkUtils::wifiTetheringSuccess(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
{
  ASSIGN_FIELD(mEnable)

  if (aChain->getParams().mEnable) {
    MOZ_ASSERT(!gWifiTetheringParms);
    gWifiTetheringParms = new NetworkParams(aChain->getParams());
  }
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::usbTetheringFail(NetworkParams& aOptions,
                                    NetworkResultOptions& aResult)
{
  // Notify the main thread.
  postMessage(aOptions, aResult);
  // Try to roll back to ensure
  // we don't leave the network systems in limbo.
  // This parameter is used to disable ipforwarding.
  {
    aOptions.mEnable = false;
    runChain(aOptions, sUSBFailChain, nullptr);
  }

  // Disable usb rndis function.
  {
    NetworkParams options;
    options.mEnable = false;
    options.mReport = false;
    gNetworkUtils->enableUsbRndis(options);
  }
}

void NetworkUtils::usbTetheringSuccess(CommandChain* aChain,
                                       CommandCallback aCallback,
                                       NetworkResultOptions& aResult)
{
  ASSIGN_FIELD(mEnable)
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::networkInterfaceAlarmFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::networkInterfaceAlarmSuccess(CommandChain* aChain,
                                                CommandCallback aCallback,
                                                NetworkResultOptions& aResult)
{
  // TODO : error is not used , and it is conflict with boolean type error.
  // params.error = parseFloat(params.resultReason);
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::updateUpStreamFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::updateUpStreamSuccess(CommandChain* aChain,
                                         CommandCallback aCallback,
                                         NetworkResultOptions& aResult)
{
  ASSIGN_FIELD(mCurExternalIfname)
  ASSIGN_FIELD(mCurInternalIfname)
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::setDhcpServerFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  aResult.mSuccess = false;
  postMessage(aOptions, aResult);
}

void NetworkUtils::setDhcpServerSuccess(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult)
{
  aResult.mSuccess = true;
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::wifiOperationModeFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::wifiOperationModeSuccess(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult)
{
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::setDnsFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::defaultAsyncSuccessHandler(CommandChain* aChain,
                                              CommandCallback aCallback,
                                              NetworkResultOptions& aResult)
{
  NU_DBG("defaultAsyncSuccessHandler");
  aResult.mRet = true;
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::defaultAsyncFailureHandler(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  aResult.mRet = false;
  postMessage(aOptions, aResult);
}

void NetworkUtils::getInterfacesFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::getInterfacesSuccess(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
{
  char buf[BUF_SIZE];
  NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
  memcpy(buf, reason.get(), strlen(reason.get()));

  nsTArray<nsCString> result;
  split(buf, INTERFACE_DELIMIT, result);

  nsTArray<nsString> interfaceList;
  uint32_t length = result.Length();
  convertUTF8toUTF16(result, interfaceList, length);

  aResult.mInterfaceList.Construct();
  for (uint32_t i = 0; i < length; i++) {
    aResult.mInterfaceList.Value().AppendElement(interfaceList[i], fallible_t());
  }

  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::getInterfaceConfigFail(NetworkParams& aOptions,
                                          NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::getInterfaceConfigSuccess(CommandChain* aChain,
                                             CommandCallback aCallback,
                                             NetworkResultOptions& aResult)
{
  char buf[BUF_SIZE];
  NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
  memcpy(buf, reason.get(), strlen(reason.get()));

  nsTArray<nsCString> result;
  split(buf, NETD_MESSAGE_DELIMIT, result);

  ASSIGN_FIELD_VALUE(mMacAddr, NS_ConvertUTF8toUTF16(result[0]))
  ASSIGN_FIELD_VALUE(mIpAddr, NS_ConvertUTF8toUTF16(result[1]))
  ASSIGN_FIELD_VALUE(mPrefixLength, atol(result[2].get()))

  if (result[3].Find("up")) {
    ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("up"))
  } else {
    ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("down"))
  }

  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

void NetworkUtils::setInterfaceConfigFail(NetworkParams& aOptions,
                                          NetworkResultOptions& aResult)
{
  postMessage(aOptions, aResult);
}

void NetworkUtils::setInterfaceConfigSuccess(CommandChain* aChain,
                                             CommandCallback aCallback,
                                             NetworkResultOptions& aResult)
{
  postMessage(aChain->getParams(), aResult);
  finalizeSuccess(aChain, aResult);
}

#undef ASSIGN_FIELD
#undef ASSIGN_FIELD_VALUE

NetworkUtils::NetworkUtils(MessageCallback aCallback)
 : mMessageCallback(aCallback)
{
  mNetUtils = new NetUtils();

  char value[Property::VALUE_MAX_LENGTH];
  Property::Get("ro.build.version.sdk", value, nullptr);
  SDK_VERSION = atoi(value);

  Property::Get(IPV6_TETHERING, value, "0");
  SUPPORT_IPV6_TETHERING = atoi(value);

  gNetworkUtils = this;
}

NetworkUtils::~NetworkUtils()
{
}

#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aOptions.prop).get()
#define GET_FIELD(prop) aOptions.prop

// Hoist this type definition to global to avoid template
// instantiation error on gcc 4.4 used by ICS emulator.
typedef CommandResult (NetworkUtils::*CommandHandler)(NetworkParams&);
struct CommandHandlerEntry
{
  const char* mCommandName;
  CommandHandler mCommandHandler;
};

void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
{
  const static CommandHandlerEntry
    COMMAND_HANDLER_TABLE[] = {

    // For command 'testCommand', BUILD_ENTRY(testCommand) will generate
    // {"testCommand", NetworkUtils::testCommand}
    #define BUILD_ENTRY(c) {#c, &NetworkUtils::c}

    BUILD_ENTRY(removeNetworkRoute),
    BUILD_ENTRY(setDNS),
    BUILD_ENTRY(setDefaultRoute),
    BUILD_ENTRY(removeDefaultRoute),
    BUILD_ENTRY(addHostRoute),
    BUILD_ENTRY(removeHostRoute),
    BUILD_ENTRY(addSecondaryRoute),
    BUILD_ENTRY(removeSecondaryRoute),
    BUILD_ENTRY(setNetworkInterfaceAlarm),
    BUILD_ENTRY(enableNetworkInterfaceAlarm),
    BUILD_ENTRY(disableNetworkInterfaceAlarm),
    BUILD_ENTRY(setTetheringAlarm),
    BUILD_ENTRY(removeTetheringAlarm),
    BUILD_ENTRY(getTetheringStatus),
    BUILD_ENTRY(setWifiOperationMode),
    BUILD_ENTRY(setDhcpServer),
    BUILD_ENTRY(setWifiTethering),
    BUILD_ENTRY(setUSBTethering),
    BUILD_ENTRY(enableUsbRndis),
    BUILD_ENTRY(updateUpStream),
    BUILD_ENTRY(configureInterface),
    BUILD_ENTRY(dhcpRequest),
    BUILD_ENTRY(stopDhcp),
    BUILD_ENTRY(enableInterface),
    BUILD_ENTRY(disableInterface),
    BUILD_ENTRY(resetConnections),
    BUILD_ENTRY(createNetwork),
    BUILD_ENTRY(destroyNetwork),
    BUILD_ENTRY(getNetId),
    BUILD_ENTRY(getInterfaces),
    BUILD_ENTRY(getInterfaceConfig),
    BUILD_ENTRY(setInterfaceConfig),
    BUILD_ENTRY(setMtu),

    #undef BUILD_ENTRY
  };

  // Loop until we find the command name which matches aOptions.mCmd.
  CommandHandler handler = nullptr;
  for (size_t i = 0; i < mozilla::ArrayLength(COMMAND_HANDLER_TABLE); i++) {
    if (aOptions.mCmd.EqualsASCII(COMMAND_HANDLER_TABLE[i].mCommandName)) {
      handler = COMMAND_HANDLER_TABLE[i].mCommandHandler;
      break;
    }
  }

  if (!handler) {
    // Command not found in COMMAND_HANDLER_TABLE.
    WARN("unknown message: %s", NS_ConvertUTF16toUTF8(aOptions.mCmd).get());
    return;
  }

  // The handler would return one of the following 3 values
  // to be wrapped to CommandResult:
  //
  //   1) |int32_t| for mostly synchronous native function calls.
  //   2) |NetworkResultOptions| to populate additional results. (e.g. dhcpRequest)
  //   3) |CommandResult::Pending| to indicate the result is not
  //      obtained yet.
  //
  // If the handler returns "Pending", the handler should take the
  // responsibility for posting result to main thread.
  CommandResult commandResult = (this->*handler)(aOptions);
  if (!commandResult.isPending()) {
    postMessage(aOptions, commandResult.mResult);
  }
}

/**
 * Handle received data from netd.
 */
void NetworkUtils::onNetdMessage(NetdCommand* aCommand)
{
  char* data = (char*)aCommand->mData;

  // get code & reason.
  char* result = strtok(data, NETD_MESSAGE_DELIMIT);

  if (!result) {
    nextNetdCommand();
    return;
  }
  uint32_t code = atoi(result);

  if (!isBroadcastMessage(code) && SDK_VERSION >= 16) {
    strtok(nullptr, NETD_MESSAGE_DELIMIT);
  }

  char* reason = strtok(nullptr, "\0");

  if (isBroadcastMessage(code)) {
    NU_DBG("Receiving broadcast message from netd.");
    NU_DBG("          ==> Code: %d  Reason: %s", code, reason);
    sendBroadcastMessage(code, reason);

    if (code == NETD_COMMAND_INTERFACE_CHANGE) {
      if (gWifiTetheringParms) {
        char linkdownReason[MAX_COMMAND_SIZE];
        snprintf(linkdownReason, MAX_COMMAND_SIZE - 1,
                 "Iface linkstate %s down",
                 NS_ConvertUTF16toUTF8(gWifiTetheringParms->mIfname).get());

        if (!strcmp(reason, linkdownReason)) {
          NU_DBG("Wifi link down, restarting tethering.");
          runChain(*gWifiTetheringParms, sWifiRetryChain, wifiTetheringFail);
        }
      }
    }

    nextNetdCommand();
    return;
  }

   // Set pending to false before we handle next command.
  NU_DBG("Receiving \"%s\" command response from netd.", gCurrentCommand.command);
  NU_DBG("          ==> Code: %d  Reason: %s", code, reason);

  gReason.AppendElement(nsCString(reason));

  // 1xx response code regards as command is proceeding, we need to wait for
  // final response code such as 2xx, 4xx and 5xx before sending next command.
  if (isProceeding(code)) {
    return;
  }

  if (isComplete(code)) {
    gPending = false;
  }

  {
    char buf[BUF_SIZE];
    join(gReason, INTERFACE_DELIMIT, BUF_SIZE, buf);

    NetworkResultOptions result;
    result.mResultCode = code;
    result.mResultReason = NS_ConvertUTF8toUTF16(buf);
    (gCurrentCommand.callback)(gCurrentCommand.chain, isError(code), result);
    gReason.Clear();
  }

  // Handling pending commands if any.
  if (isComplete(code)) {
    nextNetdCommand();
  }
}

/**
 * Start/Stop DHCP server.
 */
CommandResult NetworkUtils::setDhcpServer(NetworkParams& aOptions)
{
  if (aOptions.mEnabled) {
    aOptions.mWifiStartIp = aOptions.mStartIp;
    aOptions.mWifiEndIp = aOptions.mEndIp;
    aOptions.mIp = aOptions.mServerIp;
    aOptions.mPrefix = aOptions.mMaskLength;
    aOptions.mLink = NS_ConvertUTF8toUTF16("up");

    runChain(aOptions, sStartDhcpServerChain, setDhcpServerFail);
  } else {
    runChain(aOptions, sStopDhcpServerChain, setDhcpServerFail);
  }
  return CommandResult::Pending();
}

/**
 * Set DNS servers for given network interface.
 */
CommandResult NetworkUtils::setDNS(NetworkParams& aOptions)
{
  uint32_t length = aOptions.mDnses.Length();

  if (length > 0) {
    for (uint32_t i = 0; i < length; i++) {
      NS_ConvertUTF16toUTF8 autoDns(aOptions.mDnses[i]);

      char dns_prop_key[Property::VALUE_MAX_LENGTH];
      snprintf_literal(dns_prop_key, "net.dns%d", i+1);
      Property::Set(dns_prop_key, autoDns.get());
    }
  } else {
    // Set dnses from system properties.
    IFProperties interfaceProperties;
    getIFProperties(GET_CHAR(mIfname), interfaceProperties);

    Property::Set("net.dns1", interfaceProperties.dns1);
    Property::Set("net.dns2", interfaceProperties.dns2);
  }

  // Bump the DNS change property.
  char dnschange[Property::VALUE_MAX_LENGTH];
  Property::Get("net.dnschange", dnschange, "0");

  char num[Property::VALUE_MAX_LENGTH];
  snprintf(num, Property::VALUE_MAX_LENGTH - 1, "%d", atoi(dnschange) + 1);
  Property::Set("net.dnschange", num);

  // DNS needs to be set through netd since JellyBean (4.3).
  if (SDK_VERSION >= 20) {
    // Lollipop.
    static CommandFunc COMMAND_CHAIN[] = {
      setInterfaceDns,
      addDefaultRouteToNetwork,
      defaultAsyncSuccessHandler
    };
    NetIdManager::NetIdInfo netIdInfo;
    if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
      return -1;
    }
    aOptions.mNetId = netIdInfo.mNetId;
    runChain(aOptions, COMMAND_CHAIN, setDnsFail);
    return CommandResult::Pending();
  }
  if (SDK_VERSION >= 18) {
    // JB, KK.
    static CommandFunc COMMAND_CHAIN[] = {
    #if ANDROID_VERSION == 18
      // Since we don't use per-interface DNS lookup feature on JB,
      // we need to set the default DNS interface whenever setting the
      // DNS name server.
      setDefaultInterface,
    #endif
      setInterfaceDns,
      defaultAsyncSuccessHandler
    };
    runChain(aOptions, COMMAND_CHAIN, setDnsFail);
    return CommandResult::Pending();
  }

  return SUCCESS;
}

CommandResult NetworkUtils::configureInterface(NetworkParams& aOptions)
{
  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
  return mNetUtils->do_ifc_configure(
    autoIfname.get(),
    aOptions.mIpaddr,
    aOptions.mMask,
    aOptions.mGateway_long,
    aOptions.mDns1_long,
    aOptions.mDns2_long
  );
}

CommandResult NetworkUtils::stopDhcp(NetworkParams& aOptions)
{
  return mNetUtils->do_dhcp_stop(GET_CHAR(mIfname));
}

CommandResult NetworkUtils::dhcpRequest(NetworkParams& aOptions) {
    mozilla::dom::NetworkResultOptions result;

    NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
    char ipaddr[Property::VALUE_MAX_LENGTH];
    char gateway[Property::VALUE_MAX_LENGTH];
    uint32_t prefixLength;
    char dns1[Property::VALUE_MAX_LENGTH];
    char dns2[Property::VALUE_MAX_LENGTH];
    char server[Property::VALUE_MAX_LENGTH];
    uint32_t lease;
    char vendorinfo[Property::VALUE_MAX_LENGTH];
    int32_t ret = mNetUtils->do_dhcp_do_request(autoIfname.get(),
                                                ipaddr,
                                                gateway,
                                                &prefixLength,
                                                dns1,
                                                dns2,
                                                server,
                                                &lease,
                                                vendorinfo);

    RETURN_IF_FAILED(ret);

    result.mIpaddr_str = NS_ConvertUTF8toUTF16(ipaddr);
    result.mGateway_str = NS_ConvertUTF8toUTF16(gateway);
    result.mDns1_str = NS_ConvertUTF8toUTF16(dns1);
    result.mDns2_str = NS_ConvertUTF8toUTF16(dns2);
    result.mServer_str = NS_ConvertUTF8toUTF16(server);
    result.mVendor_str = NS_ConvertUTF8toUTF16(vendorinfo);
    result.mLease = lease;
    result.mPrefixLength = prefixLength;
    result.mMask = makeMask(prefixLength);

    uint32_t inet4; // only support IPv4 for now.

#define INET_PTON(var, field)                                                 \
  PR_BEGIN_MACRO                                                              \
    inet_pton(AF_INET, var, &inet4);                                          \
    result.field = inet4;                                                    \
  PR_END_MACRO

    INET_PTON(ipaddr, mIpaddr);
    INET_PTON(gateway, mGateway);

    if (dns1[0] != '\0') {
      INET_PTON(dns1, mDns1);
    }

    if (dns2[0] != '\0') {
      INET_PTON(dns2, mDns2);
    }

    INET_PTON(server, mServer);

    char inet_str[64];
    if (inet_ntop(AF_INET, &result.mMask, inet_str, sizeof(inet_str))) {
      result.mMask_str = NS_ConvertUTF8toUTF16(inet_str);
    }

    return result;
}

CommandResult NetworkUtils::enableInterface(NetworkParams& aOptions) {
  return mNetUtils->do_ifc_enable(
    NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
}

CommandResult NetworkUtils::disableInterface(NetworkParams& aOptions) {
  return mNetUtils->do_ifc_disable(
    NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
}

CommandResult NetworkUtils::resetConnections(NetworkParams& aOptions) {
  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
  return mNetUtils->do_ifc_reset_connections(
    NS_ConvertUTF16toUTF8(aOptions.mIfname).get(),
    RESET_ALL_ADDRESSES);
}

/**
 * Set default route and DNS servers for given network interface.
 */
CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return setDefaultRouteLegacy(aOptions);
  }

  static CommandFunc COMMAND_CHAIN[] = {
    addDefaultRouteToNetwork,
    setDefaultNetwork,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("No such interface");
    return -1;
  }

  aOptions.mNetId = netIdInfo.mNetId;
  aOptions.mLoopIndex = 0;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

/**
 * Set default route and DNS servers for given network interface by obsoleted libnetutils.
 */
CommandResult NetworkUtils::setDefaultRouteLegacy(NetworkParams& aOptions)
{
  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);

  uint32_t length = aOptions.mGateways.Length();
  if (length > 0) {
    for (uint32_t i = 0; i < length; i++) {
      NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);

      int type = getIpType(autoGateway.get());
      if (type != AF_INET && type != AF_INET6) {
        continue;
      }

      if (type == AF_INET6) {
        RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, autoGateway.get()));
      } else { /* type == AF_INET */
        RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(autoGateway.get())));
      }
    }
  } else {
    // Set default froute from system properties.
    char key[Property::KEY_MAX_LENGTH];
    char gateway[Property::KEY_MAX_LENGTH];

    snprintf(key, sizeof key - 1, "net.%s.gw", autoIfname.get());
    Property::Get(key, gateway, "");

    int type = getIpType(gateway);
    if (type != AF_INET && type != AF_INET6) {
      return EAFNOSUPPORT;
    }

    if (type == AF_INET6) {
      RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, gateway));
    } else { /* type == AF_INET */
      RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(gateway)));
    }
  }

  // Set the default DNS interface.
  if (SDK_VERSION >= 18) {
    // For JB, KK only.
    static CommandFunc COMMAND_CHAIN[] = {
      setDefaultInterface,
      defaultAsyncSuccessHandler
    };
    runChain(aOptions, COMMAND_CHAIN, setDnsFail);
    return CommandResult::Pending();
  }

  return SUCCESS;
}

/**
 * Remove default route for given network interface.
 */
CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
{
  NU_DBG("Calling NetworkUtils::removeDefaultRoute");

  if (SDK_VERSION < 20) {
    return removeDefaultRouteLegacy(aOptions);
  }

  static CommandFunc COMMAND_CHAIN[] = {
    removeDefaultRoute,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("No such interface: %s", GET_CHAR(mIfname));
    return -1;
  }

  NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));

  aOptions.mNetId = netIdInfo.mNetId;
  aOptions.mLoopIndex = 0;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

/**
 * Remove default route for given network interface by obsoleted libnetutils.
 */
CommandResult NetworkUtils::removeDefaultRouteLegacy(NetworkParams& aOptions)
{
  // Legacy libnetutils calls before Lollipop.
  uint32_t length = aOptions.mGateways.Length();
  for (uint32_t i = 0; i < length; i++) {
    NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);

    int type = getIpType(autoGateway.get());
    if (type != AF_INET && type != AF_INET6) {
      return EAFNOSUPPORT;
    }

    WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
                                                  type == AF_INET ? "0.0.0.0" : "::",
                                                  0, autoGateway.get()));
  }

  return SUCCESS;
}

/**
 * Add host route for given network interface.
 */
CommandResult NetworkUtils::addHostRoute(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return addHostRouteLegacy(aOptions);
  }

  static CommandFunc COMMAND_CHAIN[] = {
    addRouteToInterface,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("No such interface: %s", GET_CHAR(mIfname));
    return -1;
  }

  NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));

  aOptions.mNetId = netIdInfo.mNetId;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

/**
 * Add host route for given network interface.
 */
CommandResult NetworkUtils::addHostRouteLegacy(NetworkParams& aOptions)
{
  if (aOptions.mGateway.IsEmpty()) {
    ERROR("addHostRouteLegacy does not support empty gateway.");
    return EINVAL;
  }

  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
  NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
  NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
  int type, prefix;

  type = getIpType(autoHostname.get());
  if (type != AF_INET && type != AF_INET6) {
    return EAFNOSUPPORT;
  }

  if (type != getIpType(autoGateway.get())) {
    return EINVAL;
  }

  prefix = type == AF_INET ? 32 : 128;
  return mNetUtils->do_ifc_add_route(autoIfname.get(), autoHostname.get(),
                                     prefix, autoGateway.get());
}

/**
 * Remove host route for given network interface.
 */
CommandResult NetworkUtils::removeHostRoute(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return removeHostRouteLegacy(aOptions);
  }

  static CommandFunc COMMAND_CHAIN[] = {
    removeRouteFromInterface,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("No such interface: %s", GET_CHAR(mIfname));
    return -1;
  }

  NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));

  aOptions.mNetId = netIdInfo.mNetId;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

/**
 * Remove host route for given network interface.
 */
CommandResult NetworkUtils::removeHostRouteLegacy(NetworkParams& aOptions)
{
  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
  NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
  NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
  int type, prefix;

  type = getIpType(autoHostname.get());
  if (type != AF_INET && type != AF_INET6) {
    return EAFNOSUPPORT;
  }

  if (type != getIpType(autoGateway.get())) {
    return EINVAL;
  }

  prefix = type == AF_INET ? 32 : 128;
  return mNetUtils->do_ifc_remove_route(autoIfname.get(), autoHostname.get(),
                                        prefix, autoGateway.get());
}

CommandResult NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return removeNetworkRouteLegacy(aOptions);
  }

  static CommandFunc COMMAND_CHAIN[] = {
    clearAddrForInterface,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("interface %s is not present in any network", GET_CHAR(mIfname));
    return -1;
  }

  NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));

  aOptions.mNetId = netIdInfo.mNetId;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

nsCString NetworkUtils::getSubnetIp(const nsCString& aIp, int aPrefixLength)
{
  int type = getIpType(aIp.get());

  if (AF_INET6 == type) {
    struct in6_addr in6;
    if (inet_pton(AF_INET6, aIp.get(), &in6) != 1) {
      return nsCString();
    }

    uint32_t p, i, p1, mask;
    p = aPrefixLength;
    i = 0;
    while (i < 4) {
      p1 = p > 32 ? 32 : p;
      p -= p1;
      mask = p1 ? ~0x0 << (32 - p1) : 0;
      in6.s6_addr32[i++] &= htonl(mask);
    }

    char subnetStr[INET6_ADDRSTRLEN];
    if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
      return nsCString();
    }

    return nsCString(subnetStr);
  }

  if (AF_INET == type) {
    uint32_t ip = inet_addr(aIp.get());
    uint32_t netmask = makeMask(aPrefixLength);
    uint32_t subnet = ip & netmask;
    struct in_addr addr;
    addr.s_addr = subnet;
    return nsCString(inet_ntoa(addr));
  }

  return nsCString();
}

CommandResult NetworkUtils::removeNetworkRouteLegacy(NetworkParams& aOptions)
{
  NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
  NS_ConvertUTF16toUTF8 autoIp(aOptions.mIp);

  int type = getIpType(autoIp.get());
  if (type != AF_INET && type != AF_INET6) {
    return EAFNOSUPPORT;
  }

  uint32_t prefixLength = GET_FIELD(mPrefixLength);

  if (type == AF_INET6) {
    // Calculate subnet.
    struct in6_addr in6;
    if (inet_pton(AF_INET6, autoIp.get(), &in6) != 1) {
      return EINVAL;
    }

    uint32_t p, i, p1, mask;
    p = prefixLength;
    i = 0;
    while (i < 4) {
      p1 = p > 32 ? 32 : p;
      p -= p1;
      mask = p1 ? ~0x0 << (32 - p1) : 0;
      in6.s6_addr32[i++] &= htonl(mask);
    }

    char subnetStr[INET6_ADDRSTRLEN];
    if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
      return EINVAL;
    }

    // Remove default route.
    WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));

    // Remove subnet route.
    RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL));
    return SUCCESS;
  }

  /* type == AF_INET */
  uint32_t ip = inet_addr(autoIp.get());
  uint32_t netmask = makeMask(prefixLength);
  uint32_t subnet = ip & netmask;
  const char* gateway = "0.0.0.0";
  struct in_addr addr;
  addr.s_addr = subnet;
  const char* dst = inet_ntoa(addr);

  RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(autoIfname.get()));
  RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), dst, prefixLength, gateway));
  return SUCCESS;
}

CommandResult NetworkUtils::addSecondaryRoute(NetworkParams& aOptions)
{
  static CommandFunc COMMAND_CHAIN[] = {
    addRouteToSecondaryTable,
    defaultAsyncSuccessHandler
  };

  if (SDK_VERSION >= 20) {
    NetIdManager::NetIdInfo netIdInfo;
    if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
      return -1;
    }
    aOptions.mNetId = netIdInfo.mNetId;
  }

  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::removeSecondaryRoute(NetworkParams& aOptions)
{
  static CommandFunc COMMAND_CHAIN[] = {
    removeRouteFromSecondaryTable,
    defaultAsyncSuccessHandler
  };

  if (SDK_VERSION >= 20) {
    NetIdManager::NetIdInfo netIdInfo;
    if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
      return -1;
    }
    aOptions.mNetId = netIdInfo.mNetId;
  }

  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::setNetworkInterfaceAlarm(NetworkParams& aOptions)
{
  NU_DBG("setNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
  runChain(aOptions, sNetworkInterfaceSetAlarmChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::enableNetworkInterfaceAlarm(NetworkParams& aOptions)
{
  NU_DBG("enableNetworkInterfaceAlarm: %s", GET_CHAR(mIfname));
  runChain(aOptions, sNetworkInterfaceEnableAlarmChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions)
{
  NU_DBG("disableNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
  runChain(aOptions, sNetworkInterfaceDisableAlarmChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::setTetheringAlarm(NetworkParams& aOptions)
{
  NU_DBG("setTetheringAlarm");
  runChain(aOptions, sTetheringInterfaceSetAlarmChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::removeTetheringAlarm(NetworkParams& aOptions)
{
  NU_DBG("removeTetheringAlarm");
  runChain(aOptions, sTetheringInterfaceRemoveAlarmChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::getTetheringStatus(NetworkParams& aOptions)
{
  NU_DBG("getTetheringStatus");
  runChain(aOptions, sTetheringGetStatusChain, networkInterfaceAlarmFail);
  return CommandResult::Pending();
}

/**
 * handling main thread's reload Wifi firmware request
 */
CommandResult NetworkUtils::setWifiOperationMode(NetworkParams& aOptions)
{
  NU_DBG("setWifiOperationMode: %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
  runChain(aOptions, sWifiOperationModeChain, wifiOperationModeFail);
  return CommandResult::Pending();
}

/**
 * handling main thread's enable/disable WiFi Tethering request
 */
CommandResult NetworkUtils::setWifiTethering(NetworkParams& aOptions)
{
  bool enable = aOptions.mEnable;
  IFProperties interfaceProperties;
  getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);

  if (strcmp(interfaceProperties.dns1, "")) {
    int type = getIpType(interfaceProperties.dns1);
    if (type != AF_INET6) {
      aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1);
    }
  }
  if (strcmp(interfaceProperties.dns2, "")) {
    int type = getIpType(interfaceProperties.dns2);
    if (type != AF_INET6) {
      aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2);
    }
  }
  dumpParams(aOptions, "WIFI");

  if (SDK_VERSION >= 20) {
    NetIdManager::NetIdInfo netIdInfo;
    if (!mNetIdManager.lookup(aOptions.mExternalIfname, &netIdInfo)) {
      ERROR("No such interface: %s", GET_CHAR(mExternalIfname));
      return -1;
    }
    aOptions.mNetId = netIdInfo.mNetId;
  }

  if (enable) {
    NU_DBG("Starting Wifi Tethering on %s <-> %s",
           GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
    runChain(aOptions, sWifiEnableChain, wifiTetheringFail);
  } else {
    NU_DBG("Stopping Wifi Tethering on %s <-> %s",
           GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
    runChain(aOptions, sWifiDisableChain, wifiTetheringFail);
  }
  return CommandResult::Pending();
}

CommandResult NetworkUtils::setUSBTethering(NetworkParams& aOptions)
{
  bool enable = aOptions.mEnable;
  IFProperties interfaceProperties;
  getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);

  if (strcmp(interfaceProperties.dns1, "")) {
    int type = getIpType(interfaceProperties.dns1);
    if (type != AF_INET6) {
      aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1);
    }
  }
  if (strcmp(interfaceProperties.dns2, "")) {
    int type = getIpType(interfaceProperties.dns2);
    if (type != AF_INET6) {
      aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2);
    }
  }
  dumpParams(aOptions, "USB");

  if (SDK_VERSION >= 20) {
    NetIdManager::NetIdInfo netIdInfo;
    if (!mNetIdManager.lookup(aOptions.mExternalIfname, &netIdInfo)) {
      ERROR("No such interface: %s", GET_CHAR(mExternalIfname));
      return -1;
    }
    aOptions.mNetId = netIdInfo.mNetId;
  }

  if (enable) {
    NU_DBG("Starting USB Tethering on %s <-> %s",
           GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
    runChain(aOptions, sUSBEnableChain, usbTetheringFail);
  } else {
    NU_DBG("Stopping USB Tethering on %s <-> %s",
           GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
    runChain(aOptions, sUSBDisableChain, usbTetheringFail);
  }
  return CommandResult::Pending();
}

void NetworkUtils::escapeQuote(nsCString& aString)
{
  aString.ReplaceSubstring("\\", "\\\\");
  aString.ReplaceSubstring("\"", "\\\"");
}

CommandResult NetworkUtils::checkUsbRndisState(NetworkParams& aOptions)
{
  static uint32_t retry = 0;

  char currentState[Property::VALUE_MAX_LENGTH];
  Property::Get(SYS_USB_STATE_PROPERTY, currentState, nullptr);

  nsTArray<nsCString> stateFuncs;
  split(currentState, USB_CONFIG_DELIMIT, stateFuncs);
  bool rndisPresent = stateFuncs.Contains(nsCString(USB_FUNCTION_RNDIS));

  if (aOptions.mEnable == rndisPresent) {
    NetworkResultOptions result;
    result.mEnable = aOptions.mEnable;
    result.mResult = true;
    retry = 0;
    return result;
  }
  if (retry < USB_FUNCTION_RETRY_TIMES) {
    retry++;
    usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
    return checkUsbRndisState(aOptions);
  }

  NetworkResultOptions result;
  result.mResult = false;
  retry = 0;
  return result;
}

/**
 * Modify usb function's property to turn on USB RNDIS function
 */
CommandResult NetworkUtils::enableUsbRndis(NetworkParams& aOptions)
{
  bool report = aOptions.mReport;

  // For some reason, rndis doesn't play well with diag,modem,nmea.
  // So when turning rndis on, we set sys.usb.config to either "rndis"
  // or "rndis,adb". When turning rndis off, we go back to
  // persist.sys.usb.config.
  //
  // On the otoro/unagi, persist.sys.usb.config should be one of:
  //
  //    diag,modem,nmea,mass_storage
  //    diag,modem,nmea,mass_storage,adb
  //
  // When rndis is enabled, sys.usb.config should be one of:
  //
  //    rdnis
  //    rndis,adb
  //
  // and when rndis is disabled, it should revert to persist.sys.usb.config

  char currentConfig[Property::VALUE_MAX_LENGTH];
  Property::Get(SYS_USB_CONFIG_PROPERTY, currentConfig, nullptr);

  nsTArray<nsCString> configFuncs;
  split(currentConfig, USB_CONFIG_DELIMIT, configFuncs);

  char persistConfig[Property::VALUE_MAX_LENGTH];
  Property::Get(PERSIST_SYS_USB_CONFIG_PROPERTY, persistConfig, nullptr);

  nsTArray<nsCString> persistFuncs;
  split(persistConfig, USB_CONFIG_DELIMIT, persistFuncs);

  if (aOptions.mEnable) {
    configFuncs.Clear();
    configFuncs.AppendElement(nsCString(USB_FUNCTION_RNDIS));
    if (persistFuncs.Contains(nsCString(USB_FUNCTION_ADB))) {
      configFuncs.AppendElement(nsCString(USB_FUNCTION_ADB));
    }
  } else {
    // We're turning rndis off, revert back to the persist setting.
    // adb will already be correct there, so we don't need to do any
    // further adjustments.
    configFuncs = persistFuncs;
  }

  char newConfig[Property::VALUE_MAX_LENGTH] = "";
  Property::Get(SYS_USB_CONFIG_PROPERTY, currentConfig, nullptr);
  join(configFuncs, USB_CONFIG_DELIMIT, Property::VALUE_MAX_LENGTH, newConfig);
  if (strcmp(currentConfig, newConfig)) {
    Property::Set(SYS_USB_CONFIG_PROPERTY, newConfig);
  }

  // Trigger the timer to check usb state and report the result to NetworkManager.
  if (report) {
    usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
    return checkUsbRndisState(aOptions);
  }
  return SUCCESS;
}

/**
 * handling upstream interface change event.
 */
CommandResult NetworkUtils::updateUpStream(NetworkParams& aOptions)
{
  runChain(aOptions, sUpdateUpStreamChain, updateUpStreamFail);
  return CommandResult::Pending();
}

/**
 * handling upstream interface change event.
 */
CommandResult NetworkUtils::createNetwork(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return SUCCESS;
  }

  static CommandFunc COMMAND_CHAIN[] = {
    createNetwork,
    enableIpv6,
    addInterfaceToNetwork,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  mNetIdManager.acquire(GET_FIELD(mIfname), &netIdInfo);
  if (netIdInfo.mRefCnt > 1) {
    // Already created. Just return.
    NU_DBG("Interface %s (%d) has been created.", GET_CHAR(mIfname),
                                                  netIdInfo.mNetId);
    return SUCCESS;
  }

  NU_DBG("Request netd to create a network with netid %d", netIdInfo.mNetId);
  // Newly created netid. Ask netd to create network.
  aOptions.mNetId = netIdInfo.mNetId;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);

  return CommandResult::Pending();
}

/**
 * handling upstream interface change event.
 */
CommandResult NetworkUtils::destroyNetwork(NetworkParams& aOptions)
{
  if (SDK_VERSION < 20) {
    return SUCCESS;
  }

  static CommandFunc COMMAND_CHAIN[] = {
    disableIpv6,
    destroyNetwork,
    defaultAsyncSuccessHandler,
  };

  NetIdManager::NetIdInfo netIdInfo;
  if (!mNetIdManager.release(GET_FIELD(mIfname), &netIdInfo)) {
    ERROR("No existing netid for %s", GET_CHAR(mIfname));
    return -1;
  }

  if (netIdInfo.mRefCnt > 0) {
    // Still be referenced. Just return.
    NU_DBG("Someone is still using this interface.");
    return SUCCESS;
  }

  NU_DBG("Interface %s (%d) is no longer used. Tell netd to destroy.",
         GET_CHAR(mIfname), netIdInfo.mNetId);

  aOptions.mNetId = netIdInfo.mNetId;
  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
  return CommandResult::Pending();
}

/**
 * Query the netId associated with the given network interface name.
 */
CommandResult NetworkUtils::getNetId(NetworkParams& aOptions)
{
  NetworkResultOptions result;

  if (SDK_VERSION < 20) {
    // For pre-Lollipop, use the interface name as the fallback.
    result.mNetId = GET_FIELD(mIfname);
    return result;
  }

  NetIdManager::NetIdInfo netIdInfo;
  if (-1 == mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
    return ESRCH;
  }
  result.mNetId.AppendInt(netIdInfo.mNetId, 10);
  return result;
}

/**
 * Get existing network interfaces.
 */
CommandResult NetworkUtils::getInterfaces(NetworkParams& aOptions)
{
  runChain(aOptions, sGetInterfacesChain, getInterfacesFail);
  return CommandResult::Pending();
}

/**
 * Get network config of a specified interface.
 */
CommandResult NetworkUtils::getInterfaceConfig(NetworkParams& aOptions)
{
  runChain(aOptions, sGetInterfaceConfigChain, getInterfaceConfigFail);
  return CommandResult::Pending();
}

/**
 * Set network config for a specified interface.
 */
CommandResult NetworkUtils::setInterfaceConfig(NetworkParams& aOptions)
{
  runChain(aOptions, sSetInterfaceConfigChain, setInterfaceConfigFail);
  return CommandResult::Pending();
}

CommandResult NetworkUtils::setMtu(NetworkParams& aOptions)
{
  // Setting/getting mtu is supported since Kitkat.
  if (SDK_VERSION < 19) {
    ERROR("setMtu is not supported in current SDK_VERSION.");
    return -1;
  }

  static CommandFunc COMMAND_CHAIN[] = {
    setMtu,
    defaultAsyncSuccessHandler,
  };

  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
  return CommandResult::Pending();
}

void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
{
  NetworkResultOptions result;
  switch(code) {
    case NETD_COMMAND_INTERFACE_CHANGE:
      result.mTopic = NS_ConvertUTF8toUTF16("netd-interface-change");
      break;
    case NETD_COMMAND_BANDWIDTH_CONTROLLER:
      result.mTopic = NS_ConvertUTF8toUTF16("netd-bandwidth-control");
      break;
    default:
      return;
  }

  result.mBroadcast = true;
  result.mReason = NS_ConvertUTF8toUTF16(reason);
  postMessage(result);
}

inline uint32_t NetworkUtils::netdResponseType(uint32_t code)
{
  return (code / 100) * 100;
}

inline bool NetworkUtils::isBroadcastMessage(uint32_t code)
{
  uint32_t type = netdResponseType(code);
  return type == NETD_COMMAND_UNSOLICITED;
}

inline bool NetworkUtils::isError(uint32_t code)
{
  uint32_t type = netdResponseType(code);
  return type != NETD_COMMAND_PROCEEDING && type != NETD_COMMAND_OKAY;
}

inline bool NetworkUtils::isComplete(uint32_t code)
{
  uint32_t type = netdResponseType(code);
  return type != NETD_COMMAND_PROCEEDING;
}

inline bool NetworkUtils::isProceeding(uint32_t code)
{
  uint32_t type = netdResponseType(code);
  return type == NETD_COMMAND_PROCEEDING;
}

void NetworkUtils::dumpParams(NetworkParams& aOptions, const char* aType)
{
#ifdef _DEBUG
  NU_DBG("Dump params:");
  NU_DBG("     ifname: %s", GET_CHAR(mIfname));
  NU_DBG("     ip: %s", GET_CHAR(mIp));
  NU_DBG("     link: %s", GET_CHAR(mLink));
  NU_DBG("     prefix: %s", GET_CHAR(mPrefix));
  NU_DBG("     wifiStartIp: %s", GET_CHAR(mWifiStartIp));
  NU_DBG("     wifiEndIp: %s", GET_CHAR(mWifiEndIp));
  NU_DBG("     usbStartIp: %s", GET_CHAR(mUsbStartIp));
  NU_DBG("     usbEndIp: %s", GET_CHAR(mUsbEndIp));
  NU_DBG("     dnsserver1: %s", GET_CHAR(mDns1));
  NU_DBG("     dnsserver2: %s", GET_CHAR(mDns2));
  NU_DBG("     internalIfname: %s", GET_CHAR(mInternalIfname));
  NU_DBG("     externalIfname: %s", GET_CHAR(mExternalIfname));
  if (!strcmp(aType, "WIFI")) {
    NU_DBG("     wifictrlinterfacename: %s", GET_CHAR(mWifictrlinterfacename));
    NU_DBG("     ssid: %s", GET_CHAR(mSsid));
    NU_DBG("     security: %s", GET_CHAR(mSecurity));
    NU_DBG("     key: %s", GET_CHAR(mKey));
  }
#endif
}

#undef GET_CHAR
#undef GET_FIELD
