/*
 *
 * Copyright 2016, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <grpc/support/port_platform.h>
#if GRPC_ARES == 1 && !defined(GRPC_UV)

#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h"
#include "src/core/lib/iomgr/sockaddr.h"
#include "src/core/lib/iomgr/socket_utils_posix.h"

#include <string.h>
#include <sys/types.h>

#include <ares.h>
#include <grpc/support/alloc.h>
#include <grpc/support/host_port.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/time.h>
#include <grpc/support/useful.h>
#include "src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h"
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/iomgr/iomgr_internal.h"
#include "src/core/lib/iomgr/sockaddr_utils.h"
#include "src/core/lib/support/string.h"

static gpr_once g_basic_init = GPR_ONCE_INIT;
static gpr_mu g_init_mu;

typedef struct grpc_ares_request {
  /** following members are set in grpc_resolve_address_ares_impl */
  /** host to resolve, parsed from the name to resolve */
  char *host;
  /** port to fill in sockaddr_in, parsed from the name to resolve */
  char *port;
  /** default port to use */
  char *default_port;
  /** closure to call when the request completes */
  grpc_closure *on_done;
  /** the pointer to receive the resolved addresses */
  grpc_resolved_addresses **addrs_out;
  /** the evernt driver used by this request */
  grpc_ares_ev_driver *ev_driver;
  /** number of ongoing queries */
  gpr_refcount pending_queries;

  /** mutex guarding the rest of the state */
  gpr_mu mu;
  /** is there at least one successful query, set in on_done_cb */
  bool success;
  /** the errors explaining the request failure, set in on_done_cb */
  grpc_error *error;
} grpc_ares_request;

static void do_basic_init(void) { gpr_mu_init(&g_init_mu); }

static uint16_t strhtons(const char *port) {
  if (strcmp(port, "http") == 0) {
    return htons(80);
  } else if (strcmp(port, "https") == 0) {
    return htons(443);
  }
  return htons((unsigned short)atoi(port));
}

static void grpc_ares_request_unref(grpc_exec_ctx *exec_ctx,
                                    grpc_ares_request *r) {
  /* If there are no pending queries, invoke on_done callback and destroy the
     request */
  if (gpr_unref(&r->pending_queries)) {
    /* TODO(zyc): Sort results with RFC6724 before invoking on_done. */
    if (exec_ctx == NULL) {
      /* A new exec_ctx is created here, as the c-ares interface does not
         provide one in ares_host_callback. It's safe to schedule on_done with
         the newly created exec_ctx, since the caller has been warned not to
         acquire locks in on_done. ares_dns_resolver is using combiner to
         protect resources needed by on_done. */
      grpc_exec_ctx new_exec_ctx = GRPC_EXEC_CTX_INIT;
      grpc_closure_sched(&new_exec_ctx, r->on_done, r->error);
      grpc_exec_ctx_finish(&new_exec_ctx);
    } else {
      grpc_closure_sched(exec_ctx, r->on_done, r->error);
    }
    gpr_mu_destroy(&r->mu);
    grpc_ares_ev_driver_destroy(r->ev_driver);
    gpr_free(r->host);
    gpr_free(r->port);
    gpr_free(r->default_port);
    gpr_free(r);
  }
}

static void on_done_cb(void *arg, int status, int timeouts,
                       struct hostent *hostent) {
  grpc_ares_request *r = (grpc_ares_request *)arg;
  gpr_mu_lock(&r->mu);
  if (status == ARES_SUCCESS) {
    GRPC_ERROR_UNREF(r->error);
    r->error = GRPC_ERROR_NONE;
    r->success = true;
    grpc_resolved_addresses **addresses = r->addrs_out;
    if (*addresses == NULL) {
      *addresses = gpr_malloc(sizeof(grpc_resolved_addresses));
      (*addresses)->naddrs = 0;
      (*addresses)->addrs = NULL;
    }
    size_t prev_naddr = (*addresses)->naddrs;
    size_t i;
    for (i = 0; hostent->h_addr_list[i] != NULL; i++) {
    }
    (*addresses)->naddrs += i;
    (*addresses)->addrs =
        gpr_realloc((*addresses)->addrs,
                    sizeof(grpc_resolved_address) * (*addresses)->naddrs);
    for (i = prev_naddr; i < (*addresses)->naddrs; i++) {
      memset(&(*addresses)->addrs[i], 0, sizeof(grpc_resolved_address));
      if (hostent->h_addrtype == AF_INET6) {
        (*addresses)->addrs[i].len = sizeof(struct sockaddr_in6);
        struct sockaddr_in6 *addr =
            (struct sockaddr_in6 *)&(*addresses)->addrs[i].addr;
        addr->sin6_family = (sa_family_t)hostent->h_addrtype;
        addr->sin6_port = strhtons(r->port);

        char output[INET6_ADDRSTRLEN];
        memcpy(&addr->sin6_addr, hostent->h_addr_list[i - prev_naddr],
               sizeof(struct in6_addr));
        ares_inet_ntop(AF_INET6, &addr->sin6_addr, output, INET6_ADDRSTRLEN);
        gpr_log(GPR_DEBUG,
                "c-ares resolver gets a AF_INET6 result: \n"
                "  addr: %s\n  port: %s\n  sin6_scope_id: %d\n",
                output, r->port, addr->sin6_scope_id);
      } else {
        (*addresses)->addrs[i].len = sizeof(struct sockaddr_in);
        struct sockaddr_in *addr =
            (struct sockaddr_in *)&(*addresses)->addrs[i].addr;
        memcpy(&addr->sin_addr, hostent->h_addr_list[i - prev_naddr],
               sizeof(struct in_addr));
        addr->sin_family = (sa_family_t)hostent->h_addrtype;
        addr->sin_port = strhtons(r->port);

        char output[INET_ADDRSTRLEN];
        ares_inet_ntop(AF_INET, &addr->sin_addr, output, INET_ADDRSTRLEN);
        gpr_log(GPR_DEBUG,
                "c-ares resolver gets a AF_INET result: \n"
                "  addr: %s\n  port: %s\n",
                output, r->port);
      }
    }
  } else if (!r->success) {
    char *error_msg;
    gpr_asprintf(&error_msg, "C-ares status is not ARES_SUCCESS: %s",
                 ares_strerror(status));
    grpc_error *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
    gpr_free(error_msg);
    if (r->error == GRPC_ERROR_NONE) {
      r->error = error;
    } else {
      r->error = grpc_error_add_child(error, r->error);
    }
  }
  gpr_mu_unlock(&r->mu);
  grpc_ares_request_unref(NULL, r);
}

void grpc_resolve_address_ares_impl(grpc_exec_ctx *exec_ctx, const char *name,
                                    const char *default_port,
                                    grpc_pollset_set *interested_parties,
                                    grpc_closure *on_done,
                                    grpc_resolved_addresses **addrs) {
  /* TODO(zyc): Enable tracing after #9603 is checked in */
  /* if (grpc_dns_trace) {
      gpr_log(GPR_DEBUG, "resolve_address (blocking): name=%s, default_port=%s",
              name, default_port);
     } */

  /* parse name, splitting it into host and port parts */
  char *host;
  char *port;
  gpr_split_host_port(name, &host, &port);
  if (host == NULL) {
    grpc_error *err = grpc_error_set_str(
        GRPC_ERROR_CREATE_FROM_STATIC_STRING("unparseable host:port"),
        GRPC_ERROR_STR_TARGET_ADDRESS, grpc_slice_from_copied_string(name));
    grpc_closure_sched(exec_ctx, on_done, err);
    goto error_cleanup;
  } else if (port == NULL) {
    if (default_port == NULL) {
      grpc_error *err = grpc_error_set_str(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("no port in name"),
          GRPC_ERROR_STR_TARGET_ADDRESS, grpc_slice_from_copied_string(name));
      grpc_closure_sched(exec_ctx, on_done, err);
      goto error_cleanup;
    }
    port = gpr_strdup(default_port);
  }

  grpc_ares_ev_driver *ev_driver;
  grpc_error *err = grpc_ares_ev_driver_create(&ev_driver, interested_parties);
  if (err != GRPC_ERROR_NONE) {
    GRPC_LOG_IF_ERROR("grpc_ares_ev_driver_create() failed", err);
    goto error_cleanup;
  }

  grpc_ares_request *r = gpr_malloc(sizeof(grpc_ares_request));
  gpr_mu_init(&r->mu);
  r->ev_driver = ev_driver;
  r->on_done = on_done;
  r->addrs_out = addrs;
  r->default_port = gpr_strdup(default_port);
  r->port = port;
  r->host = host;
  r->success = false;
  r->error = GRPC_ERROR_NONE;
  ares_channel *channel = grpc_ares_ev_driver_get_channel(r->ev_driver);
  gpr_ref_init(&r->pending_queries, 2);
  if (grpc_ipv6_loopback_available()) {
    gpr_ref(&r->pending_queries);
    ares_gethostbyname(*channel, r->host, AF_INET6, on_done_cb, r);
  }
  ares_gethostbyname(*channel, r->host, AF_INET, on_done_cb, r);
  /* TODO(zyc): Handle CNAME records here. */
  grpc_ares_ev_driver_start(exec_ctx, r->ev_driver);
  grpc_ares_request_unref(exec_ctx, r);
  return;

error_cleanup:
  gpr_free(host);
  gpr_free(port);
}

void (*grpc_resolve_address_ares)(
    grpc_exec_ctx *exec_ctx, const char *name, const char *default_port,
    grpc_pollset_set *interested_parties, grpc_closure *on_done,
    grpc_resolved_addresses **addrs) = grpc_resolve_address_ares_impl;

grpc_error *grpc_ares_init(void) {
  gpr_once_init(&g_basic_init, do_basic_init);
  gpr_mu_lock(&g_init_mu);
  int status = ares_library_init(ARES_LIB_INIT_ALL);
  gpr_mu_unlock(&g_init_mu);

  if (status != ARES_SUCCESS) {
    char *error_msg;
    gpr_asprintf(&error_msg, "ares_library_init failed: %s",
                 ares_strerror(status));
    grpc_error *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
    gpr_free(error_msg);
    return error;
  }
  return GRPC_ERROR_NONE;
}

void grpc_ares_cleanup(void) {
  gpr_mu_lock(&g_init_mu);
  ares_library_cleanup();
  gpr_mu_unlock(&g_init_mu);
}

#endif /* GRPC_ARES == 1 && !defined(GRPC_UV) */
