/*
 * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 * v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */
#define _GNU_SOURCE

#include <assert.h>
#include <stdbool.h>
#include <string.h>

#if !defined(_WIN32)
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
# if defined(__linux)
#   include <linux/if_packet.h> /* sockaddr_ll */
# endif /* __linux */
#endif /* _WIN32 */

#include "dds/ddsrt/heap.h"
#include "dds/ddsrt/log.h"
#include "dds/ddsrt/sockets_priv.h"

extern inline struct timeval *
ddsrt_duration_to_timeval_ceil(dds_duration_t reltime, struct timeval *tv);

#if DDSRT_HAVE_IPV6
const struct in6_addr ddsrt_in6addr_any = IN6ADDR_ANY_INIT;
const struct in6_addr ddsrt_in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
#endif

const int afs[] = {
#ifdef __linux
  AF_PACKET,
#endif /* __linux */
#if DDSRT_HAVE_IPV6
  AF_INET6,
#endif /* DDSRT_HAVE_IPV6 */
  AF_INET,
  DDSRT_AF_TERM /* Terminator */
};

const int *const os_supp_afs = afs;

socklen_t
ddsrt_sockaddr_get_size(const struct sockaddr *const sa)
{
  socklen_t sz;

  assert(sa != NULL);

  switch(sa->sa_family) {
#if DDSRT_HAVE_IPV6
    case AF_INET6:
      sz = sizeof(struct sockaddr_in6);
      break;
#endif /* DDSRT_HAVE_IPV6 */
#ifdef __linux
    case AF_PACKET:
      sz = sizeof(struct sockaddr_ll);
      break;
#endif /* __linux */
    default:
      assert(sa->sa_family == AF_INET);
      sz = sizeof(struct sockaddr_in);
      break;
  }

  return sz;
}

uint16_t ddsrt_sockaddr_get_port(const struct sockaddr *const sa)
{
  unsigned short port = 0;

  switch(sa->sa_family) {
#if DDSRT_HAVE_IPV6
    case AF_INET6:
      port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
      break;
#endif /* DDSRT_HAVE_IPV6 */
    default:
      assert(sa->sa_family == AF_INET);
      port = ntohs(((struct sockaddr_in *)sa)->sin_port);
      break;
  }

  return port;
}

bool
ddsrt_sockaddr_isunspecified(const struct sockaddr *__restrict sa)
{
  assert(sa != NULL);

  switch(sa->sa_family) {
#if DDSRT_HAVE_IPV6
    case AF_INET6:
      return IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)sa)->sin6_addr);
#endif
    case AF_INET:
      return (((struct sockaddr_in *)sa)->sin_addr.s_addr == 0);
  }

  return false;
}

bool
ddsrt_sockaddr_isloopback(const struct sockaddr *__restrict sa)
{
  assert(sa != NULL);

  switch (sa->sa_family) {
#if DDSRT_HAVE_IPV6
    case AF_INET6:
      return IN6_IS_ADDR_LOOPBACK(
        &((const struct sockaddr_in6 *)sa)->sin6_addr);
#endif /* DDSRT_HAVE_IPV6 */
    case AF_INET:
      return (((const struct sockaddr_in *)sa)->sin_addr.s_addr
                  == htonl(INADDR_LOOPBACK));
  }

  return false;
}

bool
ddsrt_sockaddr_insamesubnet(
  const struct sockaddr *sa1,
  const struct sockaddr *sa2,
  const struct sockaddr *mask)
{
  bool eq = false;

  if (sa1->sa_family != sa2->sa_family ||
      sa1->sa_family != mask->sa_family)
  {
    return false;
  }

  switch (sa1->sa_family) {
    case AF_INET: {
      eq = ((((struct sockaddr_in *)sa1)->sin_addr.s_addr &
             ((struct sockaddr_in *)mask)->sin_addr.s_addr)
                 ==
            (((struct sockaddr_in *)sa2)->sin_addr.s_addr &
             ((struct sockaddr_in *)mask)->sin_addr.s_addr));
      } break;
#if DDSRT_HAVE_IPV6
    case AF_INET6: {
      struct sockaddr_in6 *sin61, *sin62, *mask6;
      size_t i, n = sizeof(sin61->sin6_addr.s6_addr);
      sin61 = (struct sockaddr_in6 *)sa1;
      sin62 = (struct sockaddr_in6 *)sa2;
      mask6 = (struct sockaddr_in6 *)mask;
      eq = true;
      for (i = 0; eq && i < n; i++) {
        eq = ((sin61->sin6_addr.s6_addr[i] &
               mask6->sin6_addr.s6_addr[i])
                 ==
              (sin62->sin6_addr.s6_addr[i] &
               mask6->sin6_addr.s6_addr[i]));
        }
      } break;
#endif
  }

  return eq;
}

dds_retcode_t
ddsrt_sockaddrfromstr(int af, const char *str, void *sa)
{
  assert(str != NULL);
  assert(sa != NULL);

  switch (af) {
    case AF_INET: {
      struct in_addr buf;
      if (inet_pton(af, str, &buf) != 1) {
        return DDS_RETCODE_BAD_PARAMETER;
      } else {
        memset(sa, 0, sizeof(struct sockaddr_in));
        ((struct sockaddr_in *)sa)->sin_family = AF_INET;
        memcpy(&((struct sockaddr_in *)sa)->sin_addr, &buf, sizeof(buf));
      }
    } break;
#if DDSRT_HAVE_IPV6
    case AF_INET6: {
      struct in6_addr buf;
      if (inet_pton(af, str, &buf) != 1) {
        return DDS_RETCODE_BAD_PARAMETER;
      } else {
        memset(sa, 0, sizeof(struct sockaddr_in6));
        ((struct sockaddr_in6 *)sa)->sin6_family = AF_INET6;
        memcpy(&((struct sockaddr_in6 *)sa)->sin6_addr, &buf, sizeof(buf));
      }
    } break;
#endif
    default:
      return DDS_RETCODE_BAD_PARAMETER;
  }

  return DDS_RETCODE_OK;
}

dds_retcode_t ddsrt_sockaddrtostr(const void *sa, char *buf, size_t size)
{
  const char *ptr;

  assert(sa != NULL);
  assert(buf != NULL);

  switch (((struct sockaddr *)sa)->sa_family) {
    case AF_INET:
      ptr = inet_ntop(
        AF_INET, &((struct sockaddr_in *)sa)->sin_addr, buf, (socklen_t)size);
      break;
#if DDSRT_HAVE_IPV6
    case AF_INET6:
      ptr = inet_ntop(
        AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, buf, (socklen_t)size);
      break;
#endif
    default:
      return DDS_RETCODE_BAD_PARAMETER;
  }

  if (ptr == NULL) {
    return DDS_RETCODE_NOT_ENOUGH_SPACE;
  }

  return DDS_RETCODE_OK;
}

#if DDSRT_HAVE_DNS
dds_retcode_t
ddsrt_gethostbyname(const char *name, int af, ddsrt_hostent_t **hentp)
{
  int gai_err = 0;
  struct addrinfo hints, *res = NULL;
  ddsrt_hostent_t *hent = NULL;

  assert(name != NULL);
  assert(hentp != NULL);

  switch (af) {
#if DDSRT_HAVE_IPV6
    case AF_INET6:
#endif
    case AF_INET:
    case AF_UNSPEC:
      break;
    default:
      return DDS_RETCODE_BAD_PARAMETER;
  }

  /* Windows returns all registered addresses on the local computer if the
     "nodename" parameter is an empty string. *NIX return HOST_NOT_FOUND.
     Deny empty hostnames to keep behavior across platforms consistent. */
  if (strlen(name) == 0) {
    return DDS_RETCODE_HOST_NOT_FOUND;
  }

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = af;

  gai_err = getaddrinfo(name, NULL, &hints, &res);
  /* gai_strerror cannot be used because Windows does not offer a thread-safe
     implementation and lwIP (there maybe others as well) does not offer an
     implementation at all.

     NOTE: Error codes returned by getaddrinfo map directly onto Windows
           Socket error codes and WSAGetLastError can be used instead. */
  DDS_TRACE("getaddrinfo for %s returned %d\n", name, gai_err);
  switch (gai_err) {
#if defined(EAI_AGAIN)
    case EAI_AGAIN:
      /* Name server returned a temporary failure indication. */
      return DDS_RETCODE_TRY_AGAIN;
#endif
    case EAI_FAIL:
      /* Name server returned a permanent failure indication. */
      return DDS_RETCODE_ERROR;
/* Windows defines EAI_NODATA to EAI_NONAME. */
#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
    case EAI_NODATA:
      /* Host exists, but does not have any network addresses defined. */
      return DDS_RETCODE_NO_DATA;
#endif
#if defined(EAI_ADDRFAMILY)
    case EAI_ADDRFAMILY: /* Host has no addresses in requested family. */
#endif
#if defined(EAI_NOSECURENAME) /* Windows */
    case EAI_NOSECURENAME:
#endif
    case EAI_NONAME:
      /* Host does not exist. */
      return DDS_RETCODE_HOST_NOT_FOUND;
    case EAI_MEMORY:
      /* Out of memory. */
      return DDS_RETCODE_OUT_OF_RESOURCES;
#if defined(EAI_SYSTEM)
    case EAI_SYSTEM:
      /* Other system error. */
      return DDS_RETCODE_ERROR;
#endif
    case EAI_BADFLAGS: /* Invalid flags in hints.ai_flags. */
    case EAI_FAMILY: /* Address family not supported. */
    case EAI_SERVICE: /* Service not available for socket type. */
    case EAI_SOCKTYPE: /* Socket type not supported. */
    case 0: {
      struct addrinfo *ai;
      size_t addrno, naddrs, size;

      assert(gai_err == 0);
      assert(res != NULL);

      naddrs = 0;
      for (ai = res; ai != NULL; ai = ai->ai_next) {
        naddrs++;
      }

      size = sizeof(*hent) + (naddrs * sizeof(hent->addrs[0]));
      if ((hent = ddsrt_calloc_s(1, size)) != NULL) {
           hent->naddrs = naddrs;
        for (addrno = 0, ai = res;
             addrno < naddrs && ai != NULL;
             addrno++, ai = ai->ai_next)
        {
          memcpy(&hent->addrs[addrno], res->ai_addr, res->ai_addrlen);
        }
      } else {
        return DDS_RETCODE_OUT_OF_RESOURCES;
      }

      freeaddrinfo(res);
    } break;
    default:
      DDS_ERROR("getaddrinfo returned unkown error %d\n", gai_err);
      return DDS_RETCODE_ERROR;
  }

  *hentp = hent;
  return DDS_RETCODE_OK;
}
#endif /* DDSRT_HAVE_DNS */
