// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "base64-inl.h"
#include "cares_wrap.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_errors.h"
#include "req_wrap-inl.h"
#include "util-inl.h"
#include "v8.h"
#include "uv.h"

#include <cerrno>
#include <cstring>
#include <memory>
#include <vector>
#include <unordered_set>

#ifndef T_CAA
# define T_CAA    257 /* Certification Authority Authorization */
#endif

// OpenBSD does not define these
#ifndef AI_ALL
# define AI_ALL 0
#endif
#ifndef AI_V4MAPPED
# define AI_V4MAPPED 0
#endif


namespace node {
namespace cares_wrap {

using v8::Array;
using v8::Context;
using v8::EscapableHandleScope;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;

namespace {

Mutex ares_library_mutex;

inline uint16_t cares_get_16bit(const unsigned char* p) {
  return static_cast<uint32_t>(p[0] << 8U) | (static_cast<uint32_t>(p[1]));
}

void ares_poll_cb(uv_poll_t* watcher, int status, int events) {
  NodeAresTask* task = ContainerOf(&NodeAresTask::poll_watcher, watcher);
  ChannelWrap* channel = task->channel;

  /* Reset the idle timer */
  uv_timer_again(channel->timer_handle());

  if (status < 0) {
    /* An error happened. Just pretend that the socket is both readable and */
    /* writable. */
    ares_process_fd(channel->cares_channel(), task->sock, task->sock);
    return;
  }

  /* Process DNS responses */
  ares_process_fd(channel->cares_channel(),
                  events & UV_READABLE ? task->sock : ARES_SOCKET_BAD,
                  events & UV_WRITABLE ? task->sock : ARES_SOCKET_BAD);
}


void ares_poll_close_cb(uv_poll_t* watcher) {
  std::unique_ptr<NodeAresTask> free_me(
        ContainerOf(&NodeAresTask::poll_watcher, watcher));
}


/* Callback from ares when socket operation is started */
void ares_sockstate_cb(void* data, ares_socket_t sock, int read, int write) {
  ChannelWrap* channel = static_cast<ChannelWrap*>(data);
  NodeAresTask* task;

  NodeAresTask lookup_task;
  lookup_task.sock = sock;
  auto it = channel->task_list()->find(&lookup_task);

  task = (it == channel->task_list()->end()) ? nullptr : *it;

  if (read || write) {
    if (!task) {
      /* New socket */
      channel->StartTimer();

      task = NodeAresTask::Create(channel, sock);
      if (task == nullptr) {
        /* This should never happen unless we're out of memory or something */
        /* is seriously wrong. The socket won't be polled, but the query will */
        /* eventually time out. */
        return;
      }

      channel->task_list()->insert(task);
    }

    /* This should never fail. If it fails anyway, the query will eventually */
    /* time out. */
    uv_poll_start(&task->poll_watcher,
                  (read ? UV_READABLE : 0) | (write ? UV_WRITABLE : 0),
                  ares_poll_cb);

  } else {
    /* read == 0 and write == 0 this is c-ares's way of notifying us that */
    /* the socket is now closed. We must free the data associated with */
    /* socket. */
    CHECK(task &&
          "When an ares socket is closed we should have a handle for it");

    channel->task_list()->erase(it);
    channel->env()->CloseHandle(&task->poll_watcher, ares_poll_close_cb);

    if (channel->task_list()->empty()) {
      channel->CloseTimer();
    }
  }
}

Local<Array> HostentToNames(Environment* env, struct hostent* host) {
  EscapableHandleScope scope(env->isolate());

  std::vector<Local<Value>> names;

  for (uint32_t i = 0; host->h_aliases[i] != nullptr; ++i)
    names.emplace_back(OneByteString(env->isolate(), host->h_aliases[i]));

  Local<Array> ret = Array::New(env->isolate(), names.data(), names.size());

  return scope.Escape(ret);
}

Local<Array> HostentToNames(Environment* env,
                            struct hostent* host,
                            Local<Array> names) {
  size_t offset = names->Length();

  for (uint32_t i = 0; host->h_aliases[i] != nullptr; ++i) {
    names->Set(
        env->context(),
        i + offset,
        OneByteString(env->isolate(), host->h_aliases[i])).Check();
  }

  return names;
}

template <typename T>
Local<Array> AddrTTLToArray(
    Environment* env,
    const T* addrttls,
    size_t naddrttls) {
  MaybeStackBuffer<Local<Value>, 8> ttls(naddrttls);
  for (size_t i = 0; i < naddrttls; i++)
    ttls[i] = Integer::NewFromUnsigned(env->isolate(), addrttls[i].ttl);

  return Array::New(env->isolate(), ttls.out(), naddrttls);
}

int ParseGeneralReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    int* type,
    Local<Array> ret,
    void* addrttls = nullptr,
    int* naddrttls = nullptr) {
  HandleScope handle_scope(env->isolate());
  hostent* host;

  int status;
  switch (*type) {
    case ns_t_a:
    case ns_t_cname:
    case ns_t_cname_or_a:
      status = ares_parse_a_reply(buf,
                                  len,
                                  &host,
                                  static_cast<ares_addrttl*>(addrttls),
                                  naddrttls);
      break;
    case ns_t_aaaa:
      status = ares_parse_aaaa_reply(buf,
                                     len,
                                     &host,
                                     static_cast<ares_addr6ttl*>(addrttls),
                                     naddrttls);
      break;
    case ns_t_ns:
      status = ares_parse_ns_reply(buf, len, &host);
      break;
    case ns_t_ptr:
      status = ares_parse_ptr_reply(buf, len, nullptr, 0, AF_INET, &host);
      break;
    default:
      CHECK(0 && "Bad NS type");
      break;
  }

  if (status != ARES_SUCCESS)
    return status;

  CHECK_NOT_NULL(host);
  HostEntPointer ptr(host);

  /* If it's `CNAME`, return the CNAME value;
   * And if it's `CNAME_OR_A` and it has value in `h_name` and `h_aliases[0]`,
   * we consider it's a CNAME record, otherwise we consider it's an A record. */
  if ((*type == ns_t_cname_or_a && ptr->h_name && ptr->h_aliases[0]) ||
      *type == ns_t_cname) {
    // A cname lookup always returns a single record but we follow the
    // common API here.
    *type = ns_t_cname;
    ret->Set(env->context(),
             ret->Length(),
             OneByteString(env->isolate(), ptr->h_name)).Check();
    return ARES_SUCCESS;
  }

  if (*type == ns_t_cname_or_a)
    *type = ns_t_a;

  if (*type == ns_t_ns) {
    HostentToNames(env, ptr.get(), ret);
  } else if (*type == ns_t_ptr) {
    uint32_t offset = ret->Length();
    for (uint32_t i = 0; ptr->h_aliases[i] != nullptr; i++) {
      auto alias = OneByteString(env->isolate(), ptr->h_aliases[i]);
      ret->Set(env->context(), i + offset, alias).Check();
    }
  } else {
    uint32_t offset = ret->Length();
    char ip[INET6_ADDRSTRLEN];
    for (uint32_t i = 0; ptr->h_addr_list[i] != nullptr; ++i) {
      uv_inet_ntop(ptr->h_addrtype, ptr->h_addr_list[i], ip, sizeof(ip));
      auto address = OneByteString(env->isolate(), ip);
      ret->Set(env->context(), i + offset, address).Check();
    }
  }

  return ARES_SUCCESS;
}

int ParseMxReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    Local<Array> ret,
    bool need_type = false) {
  HandleScope handle_scope(env->isolate());

  struct ares_mx_reply* mx_start;
  int status = ares_parse_mx_reply(buf, len, &mx_start);
  if (status != ARES_SUCCESS)
    return status;

  uint32_t offset = ret->Length();
  ares_mx_reply* current = mx_start;
  for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
    Local<Object> mx_record = Object::New(env->isolate());
    mx_record->Set(env->context(),
                   env->exchange_string(),
                   OneByteString(env->isolate(), current->host)).Check();
    mx_record->Set(env->context(),
                   env->priority_string(),
                   Integer::New(env->isolate(), current->priority)).Check();
    if (need_type)
      mx_record->Set(env->context(),
                     env->type_string(),
                     env->dns_mx_string()).Check();

    ret->Set(env->context(), i + offset, mx_record).Check();
  }

  ares_free_data(mx_start);
  return ARES_SUCCESS;
}

int ParseCaaReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    Local<Array> ret,
    bool need_type = false) {
  HandleScope handle_scope(env->isolate());

  struct ares_caa_reply* caa_start;
  int status = ares_parse_caa_reply(buf, len, &caa_start);
  if (status != ARES_SUCCESS)
    return status;

  uint32_t offset = ret->Length();
  ares_caa_reply* current = caa_start;
  for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
    Local<Object> caa_record = Object::New(env->isolate());

    caa_record->Set(env->context(),
                    env->dns_critical_string(),
                    Integer::New(env->isolate(), current->critical)).Check();
    caa_record->Set(env->context(),
                    OneByteString(env->isolate(), current->property),
                    OneByteString(env->isolate(), current->value)).Check();
    if (need_type)
      caa_record->Set(env->context(),
                      env->type_string(),
                      env->dns_caa_string()).Check();

    ret->Set(env->context(), i + offset, caa_record).Check();
  }

  ares_free_data(caa_start);
  return ARES_SUCCESS;
}

int ParseTxtReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    Local<Array> ret,
    bool need_type = false) {
  HandleScope handle_scope(env->isolate());

  struct ares_txt_ext* txt_out;

  int status = ares_parse_txt_reply_ext(buf, len, &txt_out);
  if (status != ARES_SUCCESS)
    return status;

  Local<Array> txt_chunk;

  struct ares_txt_ext* current = txt_out;
  uint32_t i = 0, j;
  uint32_t offset = ret->Length();
  for (j = 0; current != nullptr; current = current->next) {
    Local<String> txt =
        OneByteString(env->isolate(), current->txt, current->length);

    // New record found - write out the current chunk
    if (current->record_start) {
      if (!txt_chunk.IsEmpty()) {
        if (need_type) {
          Local<Object> elem = Object::New(env->isolate());
          elem->Set(env->context(), env->entries_string(), txt_chunk).Check();
          elem->Set(env->context(),
                    env->type_string(),
                    env->dns_txt_string()).Check();
          ret->Set(env->context(), offset + i++, elem).Check();
        } else {
          ret->Set(env->context(), offset + i++, txt_chunk).Check();
        }
      }

      txt_chunk = Array::New(env->isolate());
      j = 0;
    }

    txt_chunk->Set(env->context(), j++, txt).Check();
  }

  // Push last chunk if it isn't empty
  if (!txt_chunk.IsEmpty()) {
    if (need_type) {
      Local<Object> elem = Object::New(env->isolate());
      elem->Set(env->context(), env->entries_string(), txt_chunk).Check();
      elem->Set(env->context(),
                env->type_string(),
                env->dns_txt_string()).Check();
      ret->Set(env->context(), offset + i, elem).Check();
    } else {
      ret->Set(env->context(), offset + i, txt_chunk).Check();
    }
  }

  ares_free_data(txt_out);
  return ARES_SUCCESS;
}


int ParseSrvReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    Local<Array> ret,
    bool need_type = false) {
  HandleScope handle_scope(env->isolate());

  struct ares_srv_reply* srv_start;
  int status = ares_parse_srv_reply(buf, len, &srv_start);
  if (status != ARES_SUCCESS)
    return status;

  ares_srv_reply* current = srv_start;
  int offset = ret->Length();
  for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
    Local<Object> srv_record = Object::New(env->isolate());
    srv_record->Set(env->context(),
                    env->name_string(),
                    OneByteString(env->isolate(), current->host)).Check();
    srv_record->Set(env->context(),
                    env->port_string(),
                    Integer::New(env->isolate(), current->port)).Check();
    srv_record->Set(env->context(),
                    env->priority_string(),
                    Integer::New(env->isolate(), current->priority)).Check();
    srv_record->Set(env->context(),
                    env->weight_string(),
                    Integer::New(env->isolate(), current->weight)).Check();
    if (need_type)
      srv_record->Set(env->context(),
                      env->type_string(),
                      env->dns_srv_string()).Check();

    ret->Set(env->context(), i + offset, srv_record).Check();
  }

  ares_free_data(srv_start);
  return ARES_SUCCESS;
}


int ParseNaptrReply(
    Environment* env,
    const unsigned char* buf,
    int len,
    Local<Array> ret,
    bool need_type = false) {
  HandleScope handle_scope(env->isolate());

  ares_naptr_reply* naptr_start;
  int status = ares_parse_naptr_reply(buf, len, &naptr_start);

  if (status != ARES_SUCCESS)
    return status;

  ares_naptr_reply* current = naptr_start;
  int offset = ret->Length();
  for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
    Local<Object> naptr_record = Object::New(env->isolate());
    naptr_record->Set(env->context(),
                      env->flags_string(),
                      OneByteString(env->isolate(), current->flags)).Check();
    naptr_record->Set(env->context(),
                      env->service_string(),
                      OneByteString(env->isolate(),
                                    current->service)).Check();
    naptr_record->Set(env->context(),
                      env->regexp_string(),
                      OneByteString(env->isolate(),
                                    current->regexp)).Check();
    naptr_record->Set(env->context(),
                      env->replacement_string(),
                      OneByteString(env->isolate(),
                                    current->replacement)).Check();
    naptr_record->Set(env->context(),
                      env->order_string(),
                      Integer::New(env->isolate(), current->order)).Check();
    naptr_record->Set(env->context(),
                      env->preference_string(),
                      Integer::New(env->isolate(),
                                   current->preference)).Check();
    if (need_type)
      naptr_record->Set(env->context(),
                        env->type_string(),
                        env->dns_naptr_string()).Check();

    ret->Set(env->context(), i + offset, naptr_record).Check();
  }

  ares_free_data(naptr_start);
  return ARES_SUCCESS;
}


int ParseSoaReply(
    Environment* env,
    unsigned char* buf,
    int len,
    Local<Object>* ret) {
  EscapableHandleScope handle_scope(env->isolate());

  // Manage memory using standardard smart pointer std::unique_tr
  struct AresDeleter {
    void operator()(char* ptr) const noexcept { ares_free_string(ptr); }
  };
  using ares_unique_ptr = std::unique_ptr<char[], AresDeleter>;

  // Can't use ares_parse_soa_reply() here which can only parse single record
  const unsigned int ancount = cares_get_16bit(buf + 6);
  unsigned char* ptr = buf + NS_HFIXEDSZ;
  char* name_temp = nullptr;
  long temp_len;  // NOLINT(runtime/int)
  int status = ares_expand_name(ptr, buf, len, &name_temp, &temp_len);
  if (status != ARES_SUCCESS) {
    // returns EBADRESP in case of invalid input
    return status == ARES_EBADNAME ? ARES_EBADRESP : status;
  }

  const ares_unique_ptr name(name_temp);

  if (ptr + temp_len + NS_QFIXEDSZ > buf + len) {
    return ARES_EBADRESP;
  }
  ptr += temp_len + NS_QFIXEDSZ;

  for (unsigned int i = 0; i < ancount; i++) {
    char* rr_name_temp = nullptr;
    long rr_temp_len;  // NOLINT(runtime/int)
    int status2 = ares_expand_name(ptr, buf, len, &rr_name_temp, &rr_temp_len);

    if (status2 != ARES_SUCCESS)
      return status2 == ARES_EBADNAME ? ARES_EBADRESP : status2;

    const ares_unique_ptr rr_name(rr_name_temp);

    ptr += rr_temp_len;
    if (ptr + NS_RRFIXEDSZ > buf + len) {
      return ARES_EBADRESP;
    }

    const int rr_type = cares_get_16bit(ptr);
    const int rr_len = cares_get_16bit(ptr + 8);
    ptr += NS_RRFIXEDSZ;

    // only need SOA
    if (rr_type == ns_t_soa) {
      char* nsname_temp = nullptr;
      long nsname_temp_len;  // NOLINT(runtime/int)

      int status3 = ares_expand_name(ptr, buf, len,
                                     &nsname_temp,
                                     &nsname_temp_len);
      if (status3 != ARES_SUCCESS) {
        return status3 == ARES_EBADNAME ? ARES_EBADRESP : status3;
      }
      const ares_unique_ptr nsname(nsname_temp);
      ptr += nsname_temp_len;

      char* hostmaster_temp = nullptr;
      long hostmaster_temp_len;  // NOLINT(runtime/int)
      int status4 = ares_expand_name(ptr, buf, len,
                                     &hostmaster_temp,
                                     &hostmaster_temp_len);
      if (status4 != ARES_SUCCESS) {
        return status4 == ARES_EBADNAME ? ARES_EBADRESP : status4;
      }
      const ares_unique_ptr hostmaster(hostmaster_temp);
      ptr += hostmaster_temp_len;

      if (ptr + 5 * 4 > buf + len) {
        return ARES_EBADRESP;
      }

      const unsigned int serial = ReadUint32BE(ptr + 0 * 4);
      const unsigned int refresh = ReadUint32BE(ptr + 1 * 4);
      const unsigned int retry = ReadUint32BE(ptr + 2 * 4);
      const unsigned int expire = ReadUint32BE(ptr + 3 * 4);
      const unsigned int minttl = ReadUint32BE(ptr + 4 * 4);

      Local<Object> soa_record = Object::New(env->isolate());
      soa_record->Set(env->context(),
                      env->nsname_string(),
                      OneByteString(env->isolate(), nsname.get())).Check();
      soa_record->Set(env->context(),
                      env->hostmaster_string(),
                      OneByteString(env->isolate(),
                                    hostmaster.get())).Check();
      soa_record->Set(env->context(),
                      env->serial_string(),
                      Integer::NewFromUnsigned(env->isolate(), serial)).Check();
      soa_record->Set(env->context(),
                      env->refresh_string(),
                      Integer::New(env->isolate(), refresh)).Check();
      soa_record->Set(env->context(),
                      env->retry_string(),
                      Integer::New(env->isolate(), retry)).Check();
      soa_record->Set(env->context(),
                      env->expire_string(),
                      Integer::New(env->isolate(), expire)).Check();
      soa_record->Set(env->context(),
                      env->minttl_string(),
                      Integer::NewFromUnsigned(env->isolate(), minttl)).Check();
      soa_record->Set(env->context(),
                      env->type_string(),
                      env->dns_soa_string()).Check();


      *ret = handle_scope.Escape(soa_record);
      break;
    }

    ptr += rr_len;
  }

  return ARES_SUCCESS;
}
}  // anonymous namespace

ChannelWrap::ChannelWrap(
      Environment* env,
      Local<Object> object,
      int timeout,
      int tries)
    : AsyncWrap(env, object, PROVIDER_DNSCHANNEL),
      timeout_(timeout),
      tries_(tries) {
  MakeWeak();

  Setup();
}

void ChannelWrap::MemoryInfo(MemoryTracker* tracker) const {
  if (timer_handle_ != nullptr)
    tracker->TrackField("timer_handle", *timer_handle_);
  tracker->TrackField("task_list", task_list_, "NodeAresTask::List");
}

void ChannelWrap::New(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
  CHECK_EQ(args.Length(), 2);
  CHECK(args[0]->IsInt32());
  CHECK(args[1]->IsInt32());
  const int timeout = args[0].As<Int32>()->Value();
  const int tries = args[1].As<Int32>()->Value();
  Environment* env = Environment::GetCurrent(args);
  new ChannelWrap(env, args.This(), timeout, tries);
}

GetAddrInfoReqWrap::GetAddrInfoReqWrap(
    Environment* env,
    Local<Object> req_wrap_obj,
    bool verbatim)
    : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETADDRINFOREQWRAP),
      verbatim_(verbatim) {}

GetNameInfoReqWrap::GetNameInfoReqWrap(
    Environment* env,
    Local<Object> req_wrap_obj)
    : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_GETNAMEINFOREQWRAP) {}

/* This is called once per second by loop->timer. It is used to constantly */
/* call back into c-ares for possibly processing timeouts. */
void ChannelWrap::AresTimeout(uv_timer_t* handle) {
  ChannelWrap* channel = static_cast<ChannelWrap*>(handle->data);
  CHECK_EQ(channel->timer_handle(), handle);
  CHECK_EQ(false, channel->task_list()->empty());
  ares_process_fd(channel->cares_channel(), ARES_SOCKET_BAD, ARES_SOCKET_BAD);
}


void NodeAresTask::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("channel", channel);
}

/* Allocates and returns a new NodeAresTask */
NodeAresTask* NodeAresTask::Create(ChannelWrap* channel, ares_socket_t sock) {
  auto task = new NodeAresTask();

  task->channel = channel;
  task->sock = sock;

  if (uv_poll_init_socket(channel->env()->event_loop(),
                          &task->poll_watcher, sock) < 0) {
    /* This should never happen. */
    delete task;
    return nullptr;
  }

  return task;
}

void ChannelWrap::Setup() {
  struct ares_options options;
  memset(&options, 0, sizeof(options));
  options.flags = ARES_FLAG_NOCHECKRESP;
  options.sock_state_cb = ares_sockstate_cb;
  options.sock_state_cb_data = this;
  options.timeout = timeout_;
  options.tries = tries_;

  int r;
  if (!library_inited_) {
    Mutex::ScopedLock lock(ares_library_mutex);
    // Multiple calls to ares_library_init() increase a reference counter,
    // so this is a no-op except for the first call to it.
    r = ares_library_init(ARES_LIB_INIT_ALL);
    if (r != ARES_SUCCESS)
      return env()->ThrowError(ToErrorCodeString(r));
  }

  /* We do the call to ares_init_option for caller. */
  const int optmask =
      ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS |
      ARES_OPT_SOCK_STATE_CB | ARES_OPT_TRIES;
  r = ares_init_options(&channel_, &options, optmask);

  if (r != ARES_SUCCESS) {
    Mutex::ScopedLock lock(ares_library_mutex);
    ares_library_cleanup();
    return env()->ThrowError(ToErrorCodeString(r));
  }

  library_inited_ = true;
}

void ChannelWrap::StartTimer() {
  if (timer_handle_ == nullptr) {
    timer_handle_ = new uv_timer_t();
    timer_handle_->data = static_cast<void*>(this);
    uv_timer_init(env()->event_loop(), timer_handle_);
  } else if (uv_is_active(reinterpret_cast<uv_handle_t*>(timer_handle_))) {
    return;
  }
  int timeout = timeout_;
  if (timeout == 0) timeout = 1;
  if (timeout < 0 || timeout > 1000) timeout = 1000;
  uv_timer_start(timer_handle_, AresTimeout, timeout, timeout);
}

void ChannelWrap::CloseTimer() {
  if (timer_handle_ == nullptr)
    return;

  env()->CloseHandle(timer_handle_, [](uv_timer_t* handle) { delete handle; });
  timer_handle_ = nullptr;
}

ChannelWrap::~ChannelWrap() {
  ares_destroy(channel_);

  if (library_inited_) {
    Mutex::ScopedLock lock(ares_library_mutex);
    // This decreases the reference counter increased by ares_library_init().
    ares_library_cleanup();
  }

  CloseTimer();
}


void ChannelWrap::ModifyActivityQueryCount(int count) {
  active_query_count_ += count;
  CHECK_GE(active_query_count_, 0);
}


/**
 * This function is to check whether current servers are fallback servers
 * when cares initialized.
 *
 * The fallback servers of cares is [ "127.0.0.1" ] with no user additional
 * setting.
 */
void ChannelWrap::EnsureServers() {
  /* if last query is OK or servers are set by user self, do not check */
  if (query_last_ok_ || !is_servers_default_) {
    return;
  }

  ares_addr_port_node* servers = nullptr;

  ares_get_servers_ports(channel_, &servers);

  /* if no server or multi-servers, ignore */
  if (servers == nullptr) return;
  if (servers->next != nullptr) {
    ares_free_data(servers);
    is_servers_default_ = false;
    return;
  }

  /* if the only server is not 127.0.0.1, ignore */
  if (servers[0].family != AF_INET ||
      servers[0].addr.addr4.s_addr != htonl(INADDR_LOOPBACK) ||
      servers[0].tcp_port != 0 ||
      servers[0].udp_port != 0) {
    ares_free_data(servers);
    is_servers_default_ = false;
    return;
  }

  ares_free_data(servers);
  servers = nullptr;

  /* destroy channel and reset channel */
  ares_destroy(channel_);

  CloseTimer();
  Setup();
}

int AnyTraits::Send(QueryWrap<AnyTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_any);
  return 0;
}

int ATraits::Send(QueryWrap<ATraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_a);
  return 0;
}

int AaaaTraits::Send(QueryWrap<AaaaTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_aaaa);
  return 0;
}

int CaaTraits::Send(QueryWrap<CaaTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, T_CAA);
  return 0;
}

int CnameTraits::Send(QueryWrap<CnameTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_cname);
  return 0;
}

int MxTraits::Send(QueryWrap<MxTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_mx);
  return 0;
}

int NsTraits::Send(QueryWrap<NsTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_ns);
  return 0;
}

int TxtTraits::Send(QueryWrap<TxtTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_txt);
  return 0;
}

int SrvTraits::Send(QueryWrap<SrvTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_srv);
  return 0;
}

int PtrTraits::Send(QueryWrap<PtrTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_ptr);
  return 0;
}

int NaptrTraits::Send(QueryWrap<NaptrTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_naptr);
  return 0;
}

int SoaTraits::Send(QueryWrap<SoaTraits>* wrap, const char* name) {
  wrap->AresQuery(name, ns_c_in, ns_t_soa);
  return 0;
}

int AnyTraits::Parse(
    QueryAnyWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> ret = Array::New(env->isolate());
  int type, status, old_count;

  /* Parse A records or CNAME records */
  ares_addrttl addrttls[256];
  int naddrttls = arraysize(addrttls);

  type = ns_t_cname_or_a;
  status = ParseGeneralReply(env,
                             buf,
                             len,
                             &type,
                             ret,
                             addrttls,
                             &naddrttls);
  uint32_t a_count = ret->Length();
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  if (type == ns_t_a) {
    CHECK_EQ(static_cast<uint32_t>(naddrttls), a_count);
    for (uint32_t i = 0; i < a_count; i++) {
      Local<Object> obj = Object::New(env->isolate());
      obj->Set(env->context(),
                env->address_string(),
                ret->Get(env->context(), i).ToLocalChecked()).Check();
      obj->Set(env->context(),
                env->ttl_string(),
                Integer::NewFromUnsigned(
                  env->isolate(), addrttls[i].ttl)).Check();
      obj->Set(env->context(),
                env->type_string(),
                env->dns_a_string()).Check();
      ret->Set(env->context(), i, obj).Check();
    }
  } else {
    for (uint32_t i = 0; i < a_count; i++) {
      Local<Object> obj = Object::New(env->isolate());
      obj->Set(env->context(),
                env->value_string(),
                ret->Get(env->context(), i).ToLocalChecked()).Check();
      obj->Set(env->context(),
                env->type_string(),
                env->dns_cname_string()).Check();
      ret->Set(env->context(), i, obj).Check();
    }
  }

  /* Parse AAAA records */
  ares_addr6ttl addr6ttls[256];
  int naddr6ttls = arraysize(addr6ttls);

  type = ns_t_aaaa;
  status = ParseGeneralReply(env,
                             buf,
                             len,
                             &type,
                             ret,
                             addr6ttls,
                             &naddr6ttls);
  uint32_t aaaa_count = ret->Length() - a_count;
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  CHECK_EQ(aaaa_count, static_cast<uint32_t>(naddr6ttls));
  CHECK_EQ(ret->Length(), a_count + aaaa_count);
  for (uint32_t i = a_count; i < ret->Length(); i++) {
    Local<Object> obj = Object::New(env->isolate());
    obj->Set(env->context(),
              env->address_string(),
              ret->Get(env->context(), i).ToLocalChecked()).Check();
    obj->Set(env->context(),
              env->ttl_string(),
              Integer::NewFromUnsigned(
                env->isolate(), addr6ttls[i - a_count].ttl)).Check();
    obj->Set(env->context(),
              env->type_string(),
              env->dns_aaaa_string()).Check();
    ret->Set(env->context(), i, obj).Check();
  }

  /* Parse MX records */
  status = ParseMxReply(env, buf, len, ret, true);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  /* Parse NS records */
  type = ns_t_ns;
  old_count = ret->Length();
  status = ParseGeneralReply(env, buf, len, &type, ret);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  for (uint32_t i = old_count; i < ret->Length(); i++) {
    Local<Object> obj = Object::New(env->isolate());
    obj->Set(env->context(),
             env->value_string(),
             ret->Get(env->context(), i).ToLocalChecked()).Check();
    obj->Set(env->context(),
              env->type_string(),
              env->dns_ns_string()).Check();
    ret->Set(env->context(), i, obj).Check();
  }

  /* Parse TXT records */
  status = ParseTxtReply(env, buf, len, ret, true);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  /* Parse SRV records */
  status = ParseSrvReply(env, buf, len, ret, true);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  /* Parse PTR records */
  type = ns_t_ptr;
  old_count = ret->Length();
  status = ParseGeneralReply(env, buf, len, &type, ret);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;
  for (uint32_t i = old_count; i < ret->Length(); i++) {
    Local<Object> obj = Object::New(env->isolate());
    obj->Set(env->context(),
              env->value_string(),
              ret->Get(env->context(), i).ToLocalChecked()).Check();
    obj->Set(env->context(),
              env->type_string(),
              env->dns_ptr_string()).Check();
    ret->Set(env->context(), i, obj).Check();
  }

  /* Parse NAPTR records */
  status = ParseNaptrReply(env, buf, len, ret, true);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  /* Parse SOA records */
  Local<Object> soa_record = Local<Object>();
  status = ParseSoaReply(env, buf, len, &soa_record);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  if (!soa_record.IsEmpty())
    ret->Set(env->context(), ret->Length(), soa_record).Check();

  /* Parse CAA records */
  status = ParseCaaReply(env, buf, len, ret, true);
  if (status != ARES_SUCCESS && status != ARES_ENODATA)
    return status;

  wrap->CallOnComplete(ret);
  return 0;
}

int ATraits::Parse(
    QueryAWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  ares_addrttl addrttls[256];
  int naddrttls = arraysize(addrttls), status;
  Local<Array> ret = Array::New(env->isolate());

  int type = ns_t_a;
  status = ParseGeneralReply(env,
                             buf,
                             len,
                             &type,
                             ret,
                             addrttls,
                             &naddrttls);
  if (status != ARES_SUCCESS)
    return status;

  Local<Array> ttls = AddrTTLToArray<ares_addrttl>(env, addrttls, naddrttls);

  wrap->CallOnComplete(ret, ttls);
  return 0;
}

int AaaaTraits::Parse(
    QueryAaaaWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  ares_addr6ttl addrttls[256];
  int naddrttls = arraysize(addrttls), status;
  Local<Array> ret = Array::New(env->isolate());

  int type = ns_t_aaaa;
  status = ParseGeneralReply(env,
                             buf,
                             len,
                             &type,
                             ret,
                             addrttls,
                             &naddrttls);
  if (status != ARES_SUCCESS)
    return status;

  Local<Array> ttls = AddrTTLToArray<ares_addr6ttl>(env, addrttls, naddrttls);

  wrap->CallOnComplete(ret, ttls);
  return 0;
}

int CaaTraits::Parse(
    QueryCaaWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> ret = Array::New(env->isolate());
  int status = ParseCaaReply(env, buf, len, ret);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(ret);
  return 0;
}

int CnameTraits::Parse(
    QueryCnameWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> ret = Array::New(env->isolate());
  int type = ns_t_cname;
  int status = ParseGeneralReply(env, buf, len, &type, ret);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(ret);
  return 0;
}

int MxTraits::Parse(
    QueryMxWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> mx_records = Array::New(env->isolate());
  int status = ParseMxReply(env, buf, len, mx_records);

  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(mx_records);
  return 0;
}

int NsTraits::Parse(
    QueryNsWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  int type = ns_t_ns;
  Local<Array> names = Array::New(env->isolate());
  int status = ParseGeneralReply(env, buf, len, &type, names);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(names);
  return 0;
}

int TxtTraits::Parse(
    QueryTxtWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> txt_records = Array::New(env->isolate());
  int status = ParseTxtReply(env, buf, len, txt_records);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(txt_records);
  return 0;
}

int SrvTraits::Parse(
    QuerySrvWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> srv_records = Array::New(env->isolate());
  int status = ParseSrvReply(env, buf, len, srv_records);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(srv_records);
  return 0;
}

int PtrTraits::Parse(
    QueryPtrWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  int type = ns_t_ptr;
  Local<Array> aliases = Array::New(env->isolate());

  int status = ParseGeneralReply(env, buf, len, &type, aliases);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(aliases);
  return 0;
}

int NaptrTraits::Parse(
    QueryNaptrWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Array> naptr_records = Array::New(env->isolate());
  int status = ParseNaptrReply(env, buf, len, naptr_records);
  if (status != ARES_SUCCESS)
    return status;

  wrap->CallOnComplete(naptr_records);
  return 0;
}

int SoaTraits::Parse(
    QuerySoaWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(response->is_host))
    return ARES_EBADRESP;

  unsigned char* buf = response->buf.data;
  int len = response->buf.size;

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  ares_soa_reply* soa_out;
  int status = ares_parse_soa_reply(buf, len, &soa_out);

  if (status != ARES_SUCCESS)
    return status;

  Local<Object> soa_record = Object::New(env->isolate());

  soa_record->Set(env->context(),
                  env->nsname_string(),
                  OneByteString(env->isolate(), soa_out->nsname)).Check();
  soa_record->Set(env->context(),
                  env->hostmaster_string(),
                  OneByteString(env->isolate(), soa_out->hostmaster)).Check();
  soa_record->Set(env->context(),
                  env->serial_string(),
                  Integer::NewFromUnsigned(
                      env->isolate(), soa_out->serial)).Check();
  soa_record->Set(env->context(),
                  env->refresh_string(),
                  Integer::New(env->isolate(), soa_out->refresh)).Check();
  soa_record->Set(env->context(),
                  env->retry_string(),
                  Integer::New(env->isolate(), soa_out->retry)).Check();
  soa_record->Set(env->context(),
                  env->expire_string(),
                  Integer::New(env->isolate(), soa_out->expire)).Check();
  soa_record->Set(env->context(),
                  env->minttl_string(),
                  Integer::NewFromUnsigned(
                      env->isolate(), soa_out->minttl)).Check();

  ares_free_data(soa_out);

  wrap->CallOnComplete(soa_record);
  return 0;
}

int ReverseTraits::Send(GetHostByAddrWrap* wrap, const char* name) {
  int length, family;
  char address_buffer[sizeof(struct in6_addr)];

  if (uv_inet_pton(AF_INET, name, &address_buffer) == 0) {
    length = sizeof(struct in_addr);
    family = AF_INET;
  } else if (uv_inet_pton(AF_INET6, name, &address_buffer) == 0) {
    length = sizeof(struct in6_addr);
    family = AF_INET6;
  } else {
    return UV_EINVAL;  // So errnoException() reports a proper error.
  }

  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
      TRACING_CATEGORY_NODE2(dns, native), "reverse", wrap,
      "name", TRACE_STR_COPY(name),
      "family", family == AF_INET ? "ipv4" : "ipv6");

  ares_gethostbyaddr(
      wrap->channel()->cares_channel(),
      address_buffer,
      length,
      family,
      GetHostByAddrWrap::Callback,
      wrap->MakeCallbackPointer());
  return 0;
}

int ReverseTraits::Parse(
    GetHostByAddrWrap* wrap,
    const std::unique_ptr<ResponseData>& response) {
  if (UNLIKELY(!response->is_host))
    return ARES_EBADRESP;

  struct hostent* host = response->host.get();

  Environment* env = wrap->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());
  wrap->CallOnComplete(HostentToNames(env, host));
  return 0;
}

namespace {
template <class Wrap>
static void Query(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  ChannelWrap* channel;
  ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());

  CHECK_EQ(false, args.IsConstructCall());
  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());

  Local<Object> req_wrap_obj = args[0].As<Object>();
  Local<String> string = args[1].As<String>();
  auto wrap = std::make_unique<Wrap>(channel, req_wrap_obj);

  node::Utf8Value name(env->isolate(), string);
  channel->ModifyActivityQueryCount(1);
  int err = wrap->Send(*name);
  if (err) {
    channel->ModifyActivityQueryCount(-1);
  } else {
    // Release ownership of the pointer allowing the ownership to be transferred
    USE(wrap.release());
  }

  args.GetReturnValue().Set(err);
}


void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
  std::unique_ptr<GetAddrInfoReqWrap> req_wrap {
      static_cast<GetAddrInfoReqWrap*>(req->data)};
  Environment* env = req_wrap->env();

  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Value> argv[] = {
    Integer::New(env->isolate(), status),
    Null(env->isolate())
  };

  uint64_t n = 0;
  const bool verbatim = req_wrap->verbatim();

  if (status == 0) {
    Local<Array> results = Array::New(env->isolate());

    auto add = [&] (bool want_ipv4, bool want_ipv6) -> Maybe<bool> {
      for (auto p = res; p != nullptr; p = p->ai_next) {
        CHECK_EQ(p->ai_socktype, SOCK_STREAM);

        const char* addr;
        if (want_ipv4 && p->ai_family == AF_INET) {
          addr = reinterpret_cast<char*>(
              &(reinterpret_cast<struct sockaddr_in*>(p->ai_addr)->sin_addr));
        } else if (want_ipv6 && p->ai_family == AF_INET6) {
          addr = reinterpret_cast<char*>(
              &(reinterpret_cast<struct sockaddr_in6*>(p->ai_addr)->sin6_addr));
        } else {
          continue;
        }

        char ip[INET6_ADDRSTRLEN];
        if (uv_inet_ntop(p->ai_family, addr, ip, sizeof(ip)))
          continue;

        Local<String> s = OneByteString(env->isolate(), ip);
        if (results->Set(env->context(), n, s).IsNothing())
          return Nothing<bool>();
        n++;
      }
      return Just(true);
    };

    if (add(true, verbatim).IsNothing())
      return;
    if (verbatim == false) {
      if (add(false, true).IsNothing())
        return;
    }

    // No responses were found to return
    if (n == 0) {
      argv[0] = Integer::New(env->isolate(), UV_EAI_NODATA);
    }

    argv[1] = results;
  }

  uv_freeaddrinfo(res);

  TRACE_EVENT_NESTABLE_ASYNC_END2(
      TRACING_CATEGORY_NODE2(dns, native), "lookup", req_wrap.get(),
      "count", n, "verbatim", verbatim);

  // Make the callback into JavaScript
  req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
}


void AfterGetNameInfo(uv_getnameinfo_t* req,
                      int status,
                      const char* hostname,
                      const char* service) {
  std::unique_ptr<GetNameInfoReqWrap> req_wrap {
      static_cast<GetNameInfoReqWrap*>(req->data)};
  Environment* env = req_wrap->env();

  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Value> argv[] = {
    Integer::New(env->isolate(), status),
    Null(env->isolate()),
    Null(env->isolate())
  };

  if (status == 0) {
    // Success
    Local<String> js_hostname = OneByteString(env->isolate(), hostname);
    Local<String> js_service = OneByteString(env->isolate(), service);
    argv[1] = js_hostname;
    argv[2] = js_service;
  }

  TRACE_EVENT_NESTABLE_ASYNC_END2(
      TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
      "hostname", TRACE_STR_COPY(hostname),
      "service", TRACE_STR_COPY(service));

  // Make the callback into JavaScript
  req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
}

using ParseIPResult =
    decltype(static_cast<ares_addr_port_node*>(nullptr)->addr);

int ParseIP(const char* ip, ParseIPResult* result = nullptr) {
  ParseIPResult tmp;
  if (result == nullptr) result = &tmp;
  if (0 == uv_inet_pton(AF_INET, ip, result)) return 4;
  if (0 == uv_inet_pton(AF_INET6, ip, result)) return 6;
  return 0;
}

void CanonicalizeIP(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  node::Utf8Value ip(isolate, args[0]);

  ParseIPResult result;
  const int rc = ParseIP(*ip, &result);
  if (rc == 0) return;

  char canonical_ip[INET6_ADDRSTRLEN];
  const int af = (rc == 4 ? AF_INET : AF_INET6);
  CHECK_EQ(0, uv_inet_ntop(af, &result, canonical_ip, sizeof(canonical_ip)));
  Local<String> val = String::NewFromUtf8(isolate, canonical_ip)
      .ToLocalChecked();
  args.GetReturnValue().Set(val);
}

void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());
  CHECK(args[2]->IsInt32());
  CHECK(args[4]->IsBoolean());
  Local<Object> req_wrap_obj = args[0].As<Object>();
  node::Utf8Value hostname(env->isolate(), args[1]);

  int32_t flags = 0;
  if (args[3]->IsInt32()) {
    flags = args[3].As<Int32>()->Value();
  }

  int family;

  switch (args[2].As<Int32>()->Value()) {
    case 0:
      family = AF_UNSPEC;
      break;
    case 4:
      family = AF_INET;
      break;
    case 6:
      family = AF_INET6;
      break;
    default:
      CHECK(0 && "bad address family");
  }

  auto req_wrap = std::make_unique<GetAddrInfoReqWrap>(env,
                                                       req_wrap_obj,
                                                       args[4]->IsTrue());

  struct addrinfo hints;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = family;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = flags;

  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
      TRACING_CATEGORY_NODE2(dns, native), "lookup", req_wrap.get(),
      "hostname", TRACE_STR_COPY(*hostname),
      "family",
      family == AF_INET ? "ipv4" : family == AF_INET6 ? "ipv6" : "unspec");

  int err = req_wrap->Dispatch(uv_getaddrinfo,
                               AfterGetAddrInfo,
                               *hostname,
                               nullptr,
                               &hints);
  if (err == 0)
    // Release ownership of the pointer allowing the ownership to be transferred
    USE(req_wrap.release());

  args.GetReturnValue().Set(err);
}


void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsString());
  CHECK(args[2]->IsUint32());
  Local<Object> req_wrap_obj = args[0].As<Object>();
  node::Utf8Value ip(env->isolate(), args[1]);
  const unsigned port = args[2]->Uint32Value(env->context()).FromJust();
  struct sockaddr_storage addr;

  CHECK(uv_ip4_addr(*ip, port, reinterpret_cast<sockaddr_in*>(&addr)) == 0 ||
        uv_ip6_addr(*ip, port, reinterpret_cast<sockaddr_in6*>(&addr)) == 0);

  auto req_wrap = std::make_unique<GetNameInfoReqWrap>(env, req_wrap_obj);

  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
      TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
      "ip", TRACE_STR_COPY(*ip), "port", port);

  int err = req_wrap->Dispatch(uv_getnameinfo,
                               AfterGetNameInfo,
                               reinterpret_cast<struct sockaddr*>(&addr),
                               NI_NAMEREQD);
  if (err == 0)
    // Release ownership of the pointer allowing the ownership to be transferred
    USE(req_wrap.release());

  args.GetReturnValue().Set(err);
}


void GetServers(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  ChannelWrap* channel;
  ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());

  Local<Array> server_array = Array::New(env->isolate());

  ares_addr_port_node* servers;

  int r = ares_get_servers_ports(channel->cares_channel(), &servers);
  CHECK_EQ(r, ARES_SUCCESS);
  auto cleanup = OnScopeLeave([&]() { ares_free_data(servers); });

  ares_addr_port_node* cur = servers;

  for (uint32_t i = 0; cur != nullptr; ++i, cur = cur->next) {
    char ip[INET6_ADDRSTRLEN];

    const void* caddr = static_cast<const void*>(&cur->addr);
    int err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip));
    CHECK_EQ(err, 0);

    Local<Value> ret[] = {
      OneByteString(env->isolate(), ip),
      Integer::New(env->isolate(), cur->udp_port)
    };

    if (server_array->Set(env->context(), i,
                          Array::New(env->isolate(), ret, arraysize(ret)))
          .IsNothing()) {
      return;
    }
  }

  args.GetReturnValue().Set(server_array);
}


void SetServers(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  ChannelWrap* channel;
  ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());

  if (channel->active_query_count()) {
    return args.GetReturnValue().Set(DNS_ESETSRVPENDING);
  }

  CHECK(args[0]->IsArray());

  Local<Array> arr = Local<Array>::Cast(args[0]);

  uint32_t len = arr->Length();

  if (len == 0) {
    int rv = ares_set_servers(channel->cares_channel(), nullptr);
    return args.GetReturnValue().Set(rv);
  }

  std::vector<ares_addr_port_node> servers(len);
  ares_addr_port_node* last = nullptr;

  int err;

  for (uint32_t i = 0; i < len; i++) {
    CHECK(arr->Get(env->context(), i).ToLocalChecked()->IsArray());

    Local<Array> elm =
        Local<Array>::Cast(arr->Get(env->context(), i).ToLocalChecked());

    CHECK(elm->Get(env->context(),
                   0).ToLocalChecked()->Int32Value(env->context()).FromJust());
    CHECK(elm->Get(env->context(), 1).ToLocalChecked()->IsString());
    CHECK(elm->Get(env->context(),
                   2).ToLocalChecked()->Int32Value(env->context()).FromJust());

    int fam = elm->Get(env->context(), 0)
        .ToLocalChecked()->Int32Value(env->context()).FromJust();
    node::Utf8Value ip(env->isolate(),
                       elm->Get(env->context(), 1).ToLocalChecked());
    int port = elm->Get(env->context(), 2)
        .ToLocalChecked()->Int32Value(env->context()).FromJust();

    ares_addr_port_node* cur = &servers[i];

    cur->tcp_port = cur->udp_port = port;
    switch (fam) {
      case 4:
        cur->family = AF_INET;
        err = uv_inet_pton(AF_INET, *ip, &cur->addr);
        break;
      case 6:
        cur->family = AF_INET6;
        err = uv_inet_pton(AF_INET6, *ip, &cur->addr);
        break;
      default:
        CHECK(0 && "Bad address family.");
    }

    if (err)
      break;

    cur->next = nullptr;

    if (last != nullptr)
      last->next = cur;

    last = cur;
  }

  if (err == 0)
    err = ares_set_servers_ports(channel->cares_channel(), &servers[0]);
  else
    err = ARES_EBADSTR;

  if (err == ARES_SUCCESS)
    channel->set_is_servers_default(false);

  args.GetReturnValue().Set(err);
}

void SetLocalAddress(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  ChannelWrap* channel;
  ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());

  CHECK_EQ(args.Length(), 2);
  CHECK(args[0]->IsString());

  Isolate* isolate = args.GetIsolate();
  node::Utf8Value ip0(isolate, args[0]);

  unsigned char addr0[sizeof(struct in6_addr)];
  unsigned char addr1[sizeof(struct in6_addr)];
  int type0 = 0;

  // This function accepts 2 arguments.  The first may be either an IPv4
  // address or an IPv6 address.  If present, the second argument must be the
  // other type of address.  Otherwise, the unspecified type of IP is set
  // to 0 (any).

  if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) {
    ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr0));
    type0 = 4;
  } else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) {
    ares_set_local_ip6(channel->cares_channel(), addr0);
    type0 = 6;
  } else {
    THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
    return;
  }

  if (!args[1]->IsUndefined()) {
    CHECK(args[1]->IsString());
    node::Utf8Value ip1(isolate, args[1]);

    if (uv_inet_pton(AF_INET, *ip1, &addr1) == 0) {
      if (type0 == 4) {
        THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses.");
        return;
      } else {
        ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr1));
      }
    } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) {
      if (type0 == 6) {
        THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses.");
        return;
      } else {
        ares_set_local_ip6(channel->cares_channel(), addr1);
      }
    } else {
      THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
      return;
    }
  } else {
    // No second arg specified
    if (type0 == 4) {
      memset(&addr1, 0, sizeof(addr1));
      ares_set_local_ip6(channel->cares_channel(), addr1);
    } else {
      ares_set_local_ip4(channel->cares_channel(), 0);
    }
  }
}

void Cancel(const FunctionCallbackInfo<Value>& args) {
  ChannelWrap* channel;
  ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());

  TRACE_EVENT_INSTANT0(TRACING_CATEGORY_NODE2(dns, native),
      "cancel", TRACE_EVENT_SCOPE_THREAD);

  ares_cancel(channel->cares_channel());
}

const char EMSG_ESETSRVPENDING[] = "There are pending queries.";
void StrError(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  int code = args[0]->Int32Value(env->context()).FromJust();
  const char* errmsg = (code == DNS_ESETSRVPENDING) ?
    EMSG_ESETSRVPENDING :
    ares_strerror(code);
  args.GetReturnValue().Set(OneByteString(env->isolate(), errmsg));
}

}  // namespace

inline void safe_free_hostent(struct hostent* host) {
  int idx;

  if (host->h_addr_list != nullptr) {
    idx = 0;
    while (host->h_addr_list[idx]) {
      free(host->h_addr_list[idx++]);
    }
    free(host->h_addr_list);
    host->h_addr_list = nullptr;
  }

  if (host->h_aliases != nullptr) {
    idx = 0;
    while (host->h_aliases[idx]) {
      free(host->h_aliases[idx++]);
    }
    free(host->h_aliases);
    host->h_aliases = nullptr;
  }

  free(host->h_name);
  free(host);
}

void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context,
                void* priv) {
  Environment* env = Environment::GetCurrent(context);

  env->SetMethod(target, "getaddrinfo", GetAddrInfo);
  env->SetMethod(target, "getnameinfo", GetNameInfo);
  env->SetMethodNoSideEffect(target, "canonicalizeIP", CanonicalizeIP);

  env->SetMethod(target, "strerror", StrError);

  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "AF_INET"),
              Integer::New(env->isolate(), AF_INET)).Check();
  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "AF_INET6"),
              Integer::New(env->isolate(), AF_INET6)).Check();
  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(),
                                                    "AF_UNSPEC"),
              Integer::New(env->isolate(), AF_UNSPEC)).Check();
  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(),
                                                    "AI_ADDRCONFIG"),
              Integer::New(env->isolate(), AI_ADDRCONFIG)).Check();
  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(),
                                                    "AI_ALL"),
              Integer::New(env->isolate(), AI_ALL)).Check();
  target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(),
                                                    "AI_V4MAPPED"),
              Integer::New(env->isolate(), AI_V4MAPPED)).Check();

  Local<FunctionTemplate> aiw =
      BaseObject::MakeLazilyInitializedJSTemplate(env);
  aiw->Inherit(AsyncWrap::GetConstructorTemplate(env));
  env->SetConstructorFunction(target, "GetAddrInfoReqWrap", aiw);

  Local<FunctionTemplate> niw =
      BaseObject::MakeLazilyInitializedJSTemplate(env);
  niw->Inherit(AsyncWrap::GetConstructorTemplate(env));
  env->SetConstructorFunction(target, "GetNameInfoReqWrap", niw);

  Local<FunctionTemplate> qrw =
      BaseObject::MakeLazilyInitializedJSTemplate(env);
  qrw->Inherit(AsyncWrap::GetConstructorTemplate(env));
  env->SetConstructorFunction(target, "QueryReqWrap", qrw);

  Local<FunctionTemplate> channel_wrap =
      env->NewFunctionTemplate(ChannelWrap::New);
  channel_wrap->InstanceTemplate()->SetInternalFieldCount(
      ChannelWrap::kInternalFieldCount);
  channel_wrap->Inherit(AsyncWrap::GetConstructorTemplate(env));

  env->SetProtoMethod(channel_wrap, "queryAny", Query<QueryAnyWrap>);
  env->SetProtoMethod(channel_wrap, "queryA", Query<QueryAWrap>);
  env->SetProtoMethod(channel_wrap, "queryAaaa", Query<QueryAaaaWrap>);
  env->SetProtoMethod(channel_wrap, "queryCaa", Query<QueryCaaWrap>);
  env->SetProtoMethod(channel_wrap, "queryCname", Query<QueryCnameWrap>);
  env->SetProtoMethod(channel_wrap, "queryMx", Query<QueryMxWrap>);
  env->SetProtoMethod(channel_wrap, "queryNs", Query<QueryNsWrap>);
  env->SetProtoMethod(channel_wrap, "queryTxt", Query<QueryTxtWrap>);
  env->SetProtoMethod(channel_wrap, "querySrv", Query<QuerySrvWrap>);
  env->SetProtoMethod(channel_wrap, "queryPtr", Query<QueryPtrWrap>);
  env->SetProtoMethod(channel_wrap, "queryNaptr", Query<QueryNaptrWrap>);
  env->SetProtoMethod(channel_wrap, "querySoa", Query<QuerySoaWrap>);
  env->SetProtoMethod(channel_wrap, "getHostByAddr", Query<GetHostByAddrWrap>);

  env->SetProtoMethodNoSideEffect(channel_wrap, "getServers", GetServers);
  env->SetProtoMethod(channel_wrap, "setServers", SetServers);
  env->SetProtoMethod(channel_wrap, "setLocalAddress", SetLocalAddress);
  env->SetProtoMethod(channel_wrap, "cancel", Cancel);

  env->SetConstructorFunction(target, "ChannelWrap", channel_wrap);
}

}  // namespace cares_wrap
}  // namespace node

NODE_MODULE_CONTEXT_AWARE_INTERNAL(cares_wrap, node::cares_wrap::Initialize)
