#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>

#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif

#include <Ecore.h>
#include <Ecore_Con.h>

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif

#include <ctype.h>

#include "ecore_con_suite.h"
#include "ecore_con_private.h"

struct log_ctx {
   const char *dom;
   const char *msg;
   int level;
   unsigned int did;
};

/* tests should not output on success, just uncomment this for debugging */
//#define SHOW_LOG 1

static void
_eina_test_safety_print_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, void *data, va_list args EINA_UNUSED)
{
   struct log_ctx *ctx = data;
   const char *str;

   if (ctx->level != level) goto log;

   if (strcmp(fmt, "%s") != 0)
     str = fmt;
   else
     {
        va_list cp_args;
        va_copy(cp_args, args);
        str = va_arg(cp_args, const char *);
        va_end(cp_args);
     }

   if (ctx->dom)
     {
        if ((!d->name) || (strcmp(ctx->dom, d->name) != 0))
          goto log;
     }

   if (ctx->msg)
     {
        if (strcmp(ctx->msg, str) != 0)
          goto log;
     }

   ctx->did++;

#ifndef SHOW_LOG
   return;
#endif
 log:

   eina_log_print_cb_stderr(d, level, file, fnc, line, fmt, NULL, args);
}

#define TRAP_ERRORS_BEGIN(_dom, _level, _msg) \
  do \
    { \
      struct log_ctx _log_ctx = { \
        .dom = #_dom, \
        .level = EINA_LOG_LEVEL_ ## _level, \
        .msg = _msg, \
      }; \
      eina_log_print_cb_set(_eina_test_safety_print_cb, &_log_ctx)

#define TRAP_ERRORS_FINISH(cnt) \
      eina_log_print_cb_set(eina_log_print_cb_stderr, NULL); \
      if (_log_ctx.did != cnt) \
        { \
           ck_abort_msg("Expected error %u (did: %u) messages to be logged to domain=%s, level=%d, mesage='%s'", cnt, _log_ctx.did, _log_ctx.dom, _log_ctx.level, _log_ctx.msg); \
        } \
    } \
  while (0)

static Eina_Value
_timeout(void *data,
         const Eina_Value t,
         const Eina_Future *dead EINA_UNUSED)
{
   Eina_Bool *did = data;

   if (t.type == EINA_VALUE_TYPE_ERROR)
     return t;

   *did = EINA_TRUE;
   ck_abort_msg("timed out!");

   return t;
}

#define LOOP_WITH_TIMEOUT(t) \
  do \
    { \
       Eina_Bool _did_timeout = EINA_FALSE; \
       Eina_Future *_timeout_future = efl_loop_timeout(efl_main_loop_get(), t); \
       eina_future_then(_timeout_future, _timeout, &_did_timeout, NULL); \
       mark_point(); \
       ecore_main_loop_begin(); \
       if (!_did_timeout) eina_future_cancel(_timeout_future); \
       else ck_abort_msg("Timed out!"); \
    } \
  while (0)


struct resolve_ctx {
   Eina_Value *results;
   Eina_Stringshare *canonical_name;
   Eina_Stringshare *request_address;
   Eina_Future *future;
   Eina_Error err;
};

static void
_resolve_cleanup(struct resolve_ctx *ctx)
{
   mark_point();

   if (ctx->results) eina_value_free(ctx->results);
   ctx->results = NULL;

   ctx->err = 0;
   eina_stringshare_replace(&ctx->canonical_name, NULL);
   eina_stringshare_replace(&ctx->request_address, NULL);

   if (ctx->future) eina_future_cancel(ctx->future);
   ctx->future = NULL;
}

static Eina_Bool
_resolve_found(const struct resolve_ctx *ctx, const char *string)
{
   const Efl_Net_Ip_Address *o;
   unsigned int i, len;

   ck_assert_ptr_ne(ctx->results, NULL);

   EINA_VALUE_ARRAY_FOREACH(ctx->results, len, i, o)
     {
        if (strcmp(string, efl_net_ip_address_string_get(o)) == 0)
          return EINA_TRUE;
     }

   return EINA_FALSE;
}

#define _assert_found(...) _assert_found_internal(__FILE__, __LINE__, __VA_ARGS__)
static void
_assert_found_internal(const char *file, int line, const struct resolve_ctx *ctx, const char *string, Eina_Bool expected, Eina_Error err)
{
   Eina_Bool found;
   unsigned int i, len;
   const Efl_Net_Ip_Address *o;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-extra-args"
  if (ctx->err != err)
     _ck_assert_failed(file, line, "Failed",
                       "Expected error=%d (%s), got %d (%s) resolving=%s",
                       err, err ? eina_error_msg_get(err) : "success",
                       ctx->err, ctx->err ? eina_error_msg_get(ctx->err) : "success",
                       string,
                       NULL);
#pragma GCC diagnostic pop

   if (err) return;

   found = _resolve_found(ctx, string);
   if (found == expected) return;

   fprintf(stderr, "ERROR: did%s expect '%s' in results:\n",
           expected ? "" : " NOT", string);

   EINA_VALUE_ARRAY_FOREACH(ctx->results, len, i, o)
     fprintf(stderr, "result %u: %s\n", i, efl_net_ip_address_string_get(o));

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-extra-args"
   _ck_assert_failed(file, line, "Failed",
                     "Expected found=%hhu, got %hhu resolving=%s",
                     expected, found,
                     string,
                     NULL);
#pragma GCC diagnostic pop
}

static Eina_Value
_resolve_done(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
{
   struct resolve_ctx *ctx = data;
   Eina_Value_Array desc = { 0 };

   mark_point();

   if (eina_value_type_get(&v) == EINA_VALUE_TYPE_ERROR)
     {
        eina_value_error_get(&v, &ctx->err);
        goto end;
     }

   eina_value_struct_get(&v, "canonical_name", &ctx->canonical_name);
   eina_value_struct_get(&v, "request_address", &ctx->request_address);
   eina_value_struct_get(&v, "results", &desc);

   ctx->results = eina_value_new(EINA_VALUE_TYPE_ARRAY);
   eina_value_pset(ctx->results, &desc);

 end:
   ecore_main_loop_quit();

   ctx->future = NULL;

   mark_point();

   return v;
}

static void
_resolve(struct resolve_ctx *ctx, const char *address, int family, int flags)
{
   ctx->future = efl_net_ip_address_resolve(
                                            address, family, flags);
   ck_assert_ptr_ne(ctx->future, NULL);
   ctx->future = eina_future_then(ctx->future, _resolve_done, ctx, NULL);

   LOOP_WITH_TIMEOUT(10);
}

/* IPv4 *****************************************************************/

static void
_ipv4_check(Eo *o, const struct sockaddr_in *addr)
{
   Eina_Slice slice = { .mem = &addr->sin_addr, .len = sizeof(addr->sin_addr) };
   Eina_Slice rs;
   char buf[INET_ADDRSTRLEN + sizeof(":65536")] = "";

   ck_assert_ptr_ne(o, NULL);

   ck_assert_int_eq(efl_net_ip_address_family_get(o), AF_INET);
   ck_assert_int_eq(efl_net_ip_address_port_get(o), eina_ntohs(addr->sin_port));

   rs = efl_net_ip_address_get(o);
   ck_assert_int_eq(eina_slice_compare(rs, slice), 0);

   inet_ntop(AF_INET, slice.mem, buf, INET_ADDRSTRLEN);

   if (addr->sin_port)
     {
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                 ":%hu", eina_htons(addr->sin_port));
     }
   ck_assert_ptr_ne(efl_net_ip_address_string_get(o), NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), buf);
}

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_manual_ok)
{
   struct sockaddr_in addr = {
     .sin_family = AF_INET,
   };
   Eina_Slice slice = { .mem = &addr.sin_addr, .len = sizeof(addr.sin_addr) };
   Eo *o;

   addr.sin_port = eina_htons(12345);
   addr.sin_addr.s_addr = eina_htonl(0xabcdefafU);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv4_check(o, &addr);
   efl_unref(o);

   addr.sin_port = eina_htons(8081);
   addr.sin_addr.s_addr = eina_htonl(0);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv4_check(o, &addr);
   efl_unref(o);

   addr.sin_port = eina_htons(0);
   addr.sin_addr.s_addr = eina_htonl(0x12345678);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET),
               efl_net_ip_address_set(efl_added, slice));
   _ipv4_check(o, &addr);
   efl_unref(o);
}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_manual_fail)
{
   uint8_t c = 123;
   Eina_Slice wrong_slice = { .mem = &c, .len = 1 };
   struct sockaddr_in addr = {
     .sin_family = AF_INET,
   };
   Eina_Slice slice = { .mem = &addr.sin_addr, .len = sizeof(addr.sin_addr) };
   Eo *o;

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: pd->addr.sa_family == 0 is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL);
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, 12345));
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(2);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: pd->addr.sa_family == 0 is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_set(efl_added, wrong_slice));
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(2);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: pd->addr.sa_family == 0 is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_port_set(efl_added, 1234));
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(2);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: pd->addr.sa_family == 0 is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_port_set(efl_added, 1234),
               efl_net_ip_address_set(efl_added, wrong_slice));
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(3);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: rw_slice.len != address.len is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET),
               efl_net_ip_address_set(efl_added, wrong_slice));
   _ipv4_check(o, &addr);
   efl_unref(o);
   TRAP_ERRORS_FINISH(1);

   addr.sin_port = eina_htons(12345);
   addr.sin_addr.s_addr = eina_htonl(0xabcdefafU);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv4_check(o, &addr);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "port already set to %hu, new %hu");
   efl_net_ip_address_port_set(o, eina_ntohs(addr.sin_port));
   TRAP_ERRORS_FINISH(0);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "port already set to %hu, new %hu");
   efl_net_ip_address_port_set(o, 999);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: rw_slice.len != address.len is true");
   slice.len = 1;
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "address already set to %s, new %s");
   slice.len = sizeof(addr.sin_addr.s_addr);
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(0);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "address already set to %s, new %s");
   addr.sin_addr.s_addr = eina_htonl(0x12345678);
   slice.len = sizeof(addr.sin_addr.s_addr);
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(1);

   efl_unref(o);
}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_create_ok)
{
   struct sockaddr_in addr = {
     .sin_family = AF_INET,
   };
   Eina_Slice slice = { .mem = &addr.sin_addr, .len = sizeof(addr.sin_addr) };
   Eo *o;

   addr.sin_port = eina_htons(12345);
   addr.sin_addr.s_addr = eina_htonl(0xabcdefafU);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin_port),
                                 slice);
   _ipv4_check(o, &addr);
   efl_unref(o);

   addr.sin_port = eina_htons(8081);
   addr.sin_addr.s_addr = eina_htonl(0);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin_port),
                                 slice);
   _ipv4_check(o, &addr);
   efl_unref(o);

   addr.sin_port = eina_htons(0);
   addr.sin_addr.s_addr = eina_htonl(0x12345678);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin_port),
                                 slice);
   _ipv4_check(o, &addr);
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_create_fail)
{
   uint8_t c = 123;
   Eina_Slice wrong_slice = { .mem = &c, .len = 1 };
   Eo *o;

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: address.len != 4 && address.len != 16 is true");
   o = efl_net_ip_address_create(
                                 1234,
                                 wrong_slice);
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(1);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_create_sockaddr_ok)
{
   struct sockaddr_in addr = {
     .sin_family = AF_INET,
   };
   Eo *o;

   addr.sin_port = eina_htons(12345);
   addr.sin_addr.s_addr = eina_htonl(0xabcdefafU);
   o = efl_net_ip_address_create_sockaddr(&addr);
   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
   _ipv4_check(o, &addr);
   efl_unref(o);

   addr.sin_port = eina_htons(0);
   addr.sin_addr.s_addr = eina_htonl(0);
   o = efl_net_ip_address_create_sockaddr(&addr);
   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
   _ipv4_check(o, &addr);
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_create_sockaddr_fail)
{
   struct sockaddr_in addr = {
     .sin_family = AF_UNSPEC,
   };
   Eo *o;

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: sockaddr == NULL");
   o = efl_net_ip_address_create_sockaddr(NULL);
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: (sockaddr->sa_family != AF_INET) && (sockaddr->sa_family != AF_INET6) is true");
   o = efl_net_ip_address_create_sockaddr(&addr);
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(1);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_parse_ok)
{
   Eo *o;

   o = efl_net_ip_address_parse( "127.0.0.1:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "127.0.0.1:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "127.0.0.1:0");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "127.0.0.1");
   efl_unref(o);

   o = efl_net_ip_address_parse( "127.0.0.1");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "127.0.0.1");
   efl_unref(o);

   o = efl_net_ip_address_parse( "192.168.0.123:80");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "192.168.0.123:80");
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_parse_fail)
{
   Eo *o;

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: numeric_address == NULL");
   o = efl_net_ip_address_parse( NULL);
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(1);

   /* incomplete numbers */
   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "127.");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "127.0.0.");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

   /* hostnames are not numeric, shouldn't return an object */
   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "google.com");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

   /* port names are not numeric, shouldn't return an object */
   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "127.0.0.1:http");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_resolve_ok)
{
   struct resolve_ctx ctx = { };

   _resolve(&ctx, "localhost:http", 0, 0);
   _assert_found(&ctx, "127.0.0.1:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "localhost", 0, 0);
   _assert_found(&ctx, "127.0.0.1", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "127.0.0.1", 0, 0);
   _assert_found(&ctx, "127.0.0.1", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "127.0.0.1:http", 0, 0);
   _assert_found(&ctx, "127.0.0.1:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "127.0.0.1:80", 0, 0);
   _assert_found(&ctx, "127.0.0.1:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "localhost:80", 0, 0);
   _assert_found(&ctx, "127.0.0.1:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "localhost:http", AF_INET, 0);
   _assert_found(&ctx, "[::1]:80", EINA_FALSE, 0);
   _resolve_cleanup(&ctx);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_resolve_fail)
{
   struct resolve_ctx ctx = { };

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: address == NULL");
   ctx.future = efl_net_ip_address_resolve(
                                           NULL, 0, 0);
   ck_assert_ptr_eq(ctx.future, NULL);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: (family != AF_UNSPEC) && (family != AF_INET) && (family != AF_INET6) is true");
   ctx.future = efl_net_ip_address_resolve(
                                           "localhost", 1234, 0);
   ck_assert_ptr_eq(ctx.future, NULL);
   TRAP_ERRORS_FINISH(1);

   _resolve(&ctx, "xxlocalhost:xxhttp", 0, 0);
   _assert_found(&ctx, "xxlocalhost:xxhttp", EINA_FALSE, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "[::1]:http", AF_INET, 0);
   _assert_found(&ctx, "[::1]:http", EINA_FALSE, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
   _resolve_cleanup(&ctx);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv4_checks)
{
   const struct test {
      uint32_t addr;
      Eina_Bool is_a, is_b, is_c, is_d, is_multicast, is_loopback, is_any;
   } *itr, tests[] = {
#define TEST(_addr) \
     { .addr = _addr, \
     .is_a = IN_CLASSA(_addr), \
     .is_b = IN_CLASSB(_addr), \
     .is_c = IN_CLASSC(_addr), \
     .is_d = IN_CLASSD(_addr), \
     .is_multicast = IN_MULTICAST(_addr) , \
     .is_loopback = _addr == INADDR_LOOPBACK, \
     .is_any = _addr == INADDR_ANY, \
     }
     TEST(INADDR_LOOPBACK),
     TEST(INADDR_ANY),
     TEST(0x0a000001),
     TEST(0x80000002),
     TEST(0xc0000003),
     TEST(0xe0000004),
#undef TEST
   };

   for (itr = tests; itr < tests + sizeof(tests)/sizeof(tests[0]); itr++)
     {
        struct sockaddr_in a = {
          .sin_family = AF_INET,
          .sin_port = 0,
          .sin_addr.s_addr = eina_htonl(itr->addr),
        };
        Eo *o = efl_net_ip_address_create_sockaddr(&a);
        ck_assert_ptr_ne(o, NULL);

        ck_assert_int_eq(efl_net_ip_address_ipv4_class_a_check(o), itr->is_a);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_b_check(o), itr->is_b);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_c_check(o), itr->is_c);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_d_check(o), itr->is_d);
        ck_assert_int_eq(efl_net_ip_address_multicast_check(o), itr->is_multicast);
        ck_assert_int_eq(efl_net_ip_address_loopback_check(o), itr->is_loopback);
        ck_assert_int_eq(efl_net_ip_address_any_check(o), itr->is_any);

        ck_assert_int_eq(efl_net_ip_address_ipv6_v4mapped_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv6_v4compat_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv6_local_link_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv6_local_site_check(o), EINA_FALSE);

        efl_unref(o);
     }

}
EFL_END_TEST

/* IPv6 *****************************************************************/

static void
_ipv6_check(Eo *o, const struct sockaddr_in6 *addr)
{
   Eina_Slice slice = { .mem = &addr->sin6_addr, .len = sizeof(addr->sin6_addr) };
   Eina_Slice rs;
   char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";

   ck_assert_ptr_ne(o, NULL);

   ck_assert_int_eq(efl_net_ip_address_family_get(o), AF_INET6);
   ck_assert_int_eq(efl_net_ip_address_port_get(o), eina_ntohs(addr->sin6_port));

   rs = efl_net_ip_address_get(o);
   ck_assert_int_eq(eina_slice_compare(rs, slice), 0);

   buf[0] = '[';
   inet_ntop(AF_INET6, slice.mem, buf + 1, INET6_ADDRSTRLEN);
   buf[strlen(buf)] = ']';

   if (addr->sin6_port)
     {
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                 ":%hu", eina_htons(addr->sin6_port));
     }

   ck_assert_ptr_ne(efl_net_ip_address_string_get(o), NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), buf);
}

static void
_ipv6_set(struct sockaddr_in6 *addr, uint16_t s1, uint16_t s2, uint16_t s3, uint16_t s4, uint16_t s5, uint16_t s6, uint16_t s7, uint16_t s8)
{
   uint16_t s[8] = { s1, s2, s3, s4, s5, s6, s7, s8 };
   memcpy(&addr->sin6_addr, s, 16);
}

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_manual_ok)
{
   struct sockaddr_in6 addr = {
     .sin6_family = AF_INET6,
   };
   Eina_Slice slice = { .mem = &addr.sin6_addr, .len = sizeof(addr.sin6_addr) };
   Eo *o;

   addr.sin6_port = eina_htons(12345);
   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET6),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin6_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv6_check(o, &addr);
   efl_unref(o);

   addr.sin6_port = eina_htons(8081);
   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET6),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin6_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv6_check(o, &addr);
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_manual_fail)
{
   uint8_t c = 123;
   Eina_Slice wrong_slice = { .mem = &c, .len = 1 };
   struct sockaddr_in6 addr = {
     .sin6_family = AF_INET6,
   };
   Eina_Slice slice = { .mem = &addr.sin6_addr, .len = sizeof(addr.sin6_addr) };
   Eo *o;

   /* generic errors checked at ecore_test_efl_net_ip_address_ipv4_manual_fail */

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: rw_slice.len != address.len is true");
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET6),
               efl_net_ip_address_set(efl_added, wrong_slice));
   _ipv6_check(o, &addr);
   efl_unref(o);
   TRAP_ERRORS_FINISH(1);

   addr.sin6_port = eina_htons(12345);
   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 1);
   o = efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, NULL,
               efl_net_ip_address_family_set(efl_added, AF_INET6),
               efl_net_ip_address_port_set(efl_added, eina_ntohs(addr.sin6_port)),
               efl_net_ip_address_set(efl_added, slice));
   _ipv6_check(o, &addr);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "port already set to %hu, new %hu");
   efl_net_ip_address_port_set(o, eina_ntohs(addr.sin6_port));
   TRAP_ERRORS_FINISH(0);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "port already set to %hu, new %hu");
   efl_net_ip_address_port_set(o, 999);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: rw_slice.len != address.len is true");
   slice.len = 1;
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(1);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "address already set to %s, new %s");
   slice.len = sizeof(addr.sin6_addr);
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(0);

   TRAP_ERRORS_BEGIN(ecore_con, ERR, "address already set to %s, new %s");
   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
   slice.len = sizeof(addr.sin6_addr);
   efl_net_ip_address_set(o, slice);
   TRAP_ERRORS_FINISH(1);

   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_create_ok)
{
   struct sockaddr_in6 addr = {
     .sin6_family = AF_INET6,
   };
   Eina_Slice slice = { .mem = &addr.sin6_addr, .len = sizeof(addr.sin6_addr) };
   Eo *o;

   addr.sin6_port = eina_htons(12365);
   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin6_port),
                                 slice);
   _ipv6_check(o, &addr);
   efl_unref(o);

   addr.sin6_port = eina_htons(8081);
   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin6_port),
                                 slice);
   _ipv6_check(o, &addr);
   efl_unref(o);

   addr.sin6_port = eina_htons(0);
   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 1);
   o = efl_net_ip_address_create(
                                 eina_ntohs(addr.sin6_port),
                                 slice);
   _ipv6_check(o, &addr);
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_create_sockaddr_ok)
{
   struct sockaddr_in6 addr = {
     .sin6_family = AF_INET6,
   };
   Eo *o;

   addr.sin6_port = eina_htons(12345);
   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
   o = efl_net_ip_address_create_sockaddr(&addr);
   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
   _ipv6_check(o, &addr);
   efl_unref(o);

   addr.sin6_port = eina_htons(0);
   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
   o = efl_net_ip_address_create_sockaddr(&addr);
   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
   _ipv6_check(o, &addr);
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_parse_ok)
{
   Eo *o;

   o = efl_net_ip_address_parse( "[::1]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::1]:0");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::1]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
   efl_unref(o);

   o = efl_net_ip_address_parse( "::1");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::]:0");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
   efl_unref(o);

   o = efl_net_ip_address_parse( "::");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
   efl_unref(o);

   /* IPv4 Mapped */
   o = efl_net_ip_address_parse( "[::ffff:192.168.0.1]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::ffff:192.168.0.1]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::ffff:192.168.0.1]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::ffff:192.168.0.1]");
   efl_unref(o);

   /* IPv4 Compatible */
   o = efl_net_ip_address_parse( "[::192.168.0.1]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::192.168.0.1]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[::192.168.0.1]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::192.168.0.1]");
   efl_unref(o);

   /* Link Local */
   o = efl_net_ip_address_parse( "[fe80::1]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fe80::1]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[fe80::1]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fe80::1]");
   efl_unref(o);

   /* Site Local */
   o = efl_net_ip_address_parse( "[fc00::1]:12345");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fc00::1]:12345");
   efl_unref(o);

   o = efl_net_ip_address_parse( "[fc00::1]");
   ck_assert_ptr_ne(o, NULL);
   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fc00::1]");
   efl_unref(o);

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_parse_fail)
{
   Eo *o;

   /* generic error (null ptr) checked in ipv4_parse_fail */

   /* incomplete numbers */
   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "::9999999");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "ab:cd:ef:gh");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

   /* port names are not numeric, shouldn't return an object */
   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
   o = efl_net_ip_address_parse( "[::1]:http");
   ck_assert_ptr_eq(o, NULL);
   TRAP_ERRORS_FINISH(0); /* no error messages! */

}
EFL_END_TEST

static const char *
_ipv6_localhost_check(void)
{
#ifndef ETC_HOSTS
#ifdef _WIN32
#define ETC_HOSTS "C:\\Windows\\System32\\Drivers\\etc\\hosts"
#else
#define ETC_HOSTS "/etc/hosts"
#endif
#endif
   Eina_File *f;
   Eina_Iterator *it;
   Eina_File_Line *line;
   static const char localhost[] = "localhost";
   static const char localhost6[] = "localhost6";
   const char *found = NULL;

   f = eina_file_open(ETC_HOSTS, EINA_FALSE);
   if (!f)
     {
        fprintf(stderr, "WARNING: your system misses %s: %s\n", ETC_HOSTS, eina_error_msg_get(eina_error_get() ? : errno));
        return NULL;
     }

   it = eina_file_map_lines(f);
   if (!it)
     {
        fprintf(stderr, "ERROR: could not map lines of %s\n", ETC_HOSTS);
        goto end;
     }
   EINA_ITERATOR_FOREACH(it, line)
     {
        const char *p;

        if (line->length < strlen("::1 localhost")) continue;

        for (p = line->start; p < line->end; p++)
          if (!isspace(p[0])) break;

        if (p >= line->end) continue;
        if (p[0] == '#') continue;
        if ((size_t)(line->end - p) < strlen("::1 localhost")) continue;
        if (memcmp(p, "::1", strlen("::1")) != 0) continue;

        p += strlen("::1");

        while (p < line->end)
          {
             const char *e;

             if (!isspace(p[0]))
               {
                  p = line->end;
                  break;
               }
             p++;

             for (; p < line->end; p++)
               if (!isspace(p[0])) break;
             if (p >= line->end) break;
             if (p[0] == '#')
               {
                  p = line->end;
                  break;
               }

             for (e = p; e < line->end; e++)
               if (isspace(e[0])) break;

             if (e > p)
               {
                  size_t len = e - p;
                  if ((len == strlen(localhost)) && (memcmp(p, localhost, strlen(localhost)) == 0))
                    found = localhost;
                  else if ((len == strlen(localhost6)) && (memcmp(p, localhost6, strlen(localhost6)) == 0))
                    found = localhost6;
               }
             p = e;
          }
        if (found) break;
     }
   eina_iterator_free(it);

 end:
   if (!found) fprintf(stderr, "WARNING: your system miss '::1 localhost' or '::1 localhost6' in %s\n", ETC_HOSTS);
   eina_file_close(f);
   return found;
}

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_resolve_ok)
{
   struct resolve_ctx ctx = { };
   char buf[4096];
   const char *localhost_str;

   localhost_str = _ipv6_localhost_check();

   if (localhost_str)
     {
        snprintf(buf, sizeof(buf), "%s:http", localhost_str);
        _resolve(&ctx, buf, 0, 0);
        _assert_found(&ctx, "[::1]:80", EINA_TRUE, 0);
        _resolve_cleanup(&ctx);

        _resolve(&ctx, localhost_str, 0, 0);
        _assert_found(&ctx, "[::1]", EINA_TRUE, 0);
        _resolve_cleanup(&ctx);
     }

   _resolve(&ctx, "::1", 0, 0);
   _assert_found(&ctx, "[::1]", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

#if defined(AI_V4MAPPED) && (AI_V4MAPPED > 0)
   _resolve(&ctx, "127.0.0.1", AF_INET6, AI_V4MAPPED);
   _assert_found(&ctx, "[::ffff:127.0.0.1]", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);
#endif

   _resolve(&ctx, "[::1]:http", 0, 0);
   _assert_found(&ctx, "[::1]:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   _resolve(&ctx, "[::1]:80", 0, 0);
   _assert_found(&ctx, "[::1]:80", EINA_TRUE, 0);
   _resolve_cleanup(&ctx);

   if (localhost_str)
     {
        snprintf(buf, sizeof(buf), "%s:80", localhost_str);
        _resolve(&ctx, buf, 0, 0);
        _assert_found(&ctx, "[::1]:80", EINA_TRUE, 0);
        _resolve_cleanup(&ctx);

        snprintf(buf, sizeof(buf), "%s:http", localhost_str);
        _resolve(&ctx, buf, AF_INET6, 0);
        _assert_found(&ctx, "127.0.0.1:80", EINA_FALSE, 0);
        _resolve_cleanup(&ctx);
     }

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_resolve_fail)
{
   struct resolve_ctx ctx = { };

   /* generic checks at ipv4_resolve_fail */

#if defined(AI_V4MAPPED) && (AI_V4MAPPED > 0)
   _resolve(&ctx, "127.0.0.1:http", AF_INET6, AI_CANONNAME); /* do NOT set V4MAPPED, but use non-zero */
   _assert_found(&ctx, "127.0.0.1:http", EINA_FALSE, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
   _resolve_cleanup(&ctx);
#endif

}
EFL_END_TEST

EFL_START_TEST(ecore_test_efl_net_ip_address_ipv6_checks)
{
   const struct test {
      const char *str;
   } *itr, tests[] = {
#define TEST(_addr) { .str = _addr }
     TEST("::1"),
     TEST("::"),
     TEST("1:2:3:4:5:6:7:8"),
     TEST("::ffff:192.168.0.1"),
     TEST("::192.168.0.1"),
     TEST("fe80::1"),
     TEST("fc00::2"),
#undef TEST
   };

   for (itr = tests; itr < tests + sizeof(tests)/sizeof(tests[0]); itr++)
     {
        struct sockaddr_in6 a = {
          .sin6_family = AF_INET6,
          .sin6_port = 0,
        };
        const struct in6_addr any = { };
        struct in6_addr *ia = &a.sin6_addr;
        int r;

        r = inet_pton(AF_INET6, itr->str, ia);
        ck_assert_int_eq(r, 1);

        Eo *o = efl_net_ip_address_create_sockaddr(&a);
        ck_assert_ptr_ne(o, NULL);

        ck_assert_int_eq(efl_net_ip_address_ipv4_class_a_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_b_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_c_check(o), EINA_FALSE);
        ck_assert_int_eq(efl_net_ip_address_ipv4_class_d_check(o), EINA_FALSE);

        ck_assert_int_eq(efl_net_ip_address_multicast_check(o), IN6_IS_ADDR_MULTICAST(ia));
        ck_assert_int_eq(efl_net_ip_address_loopback_check(o), IN6_IS_ADDR_LOOPBACK(ia));
        ck_assert_int_eq(efl_net_ip_address_any_check(o), memcmp(ia, &any, 16) == 0);

        ck_assert_int_eq(efl_net_ip_address_ipv6_v4mapped_check(o), IN6_IS_ADDR_V4MAPPED(ia));
        ck_assert_int_eq(efl_net_ip_address_ipv6_v4compat_check(o), IN6_IS_ADDR_V4COMPAT(ia));
        ck_assert_int_eq(efl_net_ip_address_ipv6_local_link_check(o), IN6_IS_ADDR_LINKLOCAL(ia));
        ck_assert_int_eq(efl_net_ip_address_ipv6_local_site_check(o), IN6_IS_ADDR_SITELOCAL(ia));

        efl_unref(o);
     }

}
EFL_END_TEST

void ecore_con_test_efl_net_ip_address(TCase *tc)
{
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_manual_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_manual_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_create_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_create_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_create_sockaddr_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_create_sockaddr_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_parse_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_parse_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_resolve_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_resolve_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv4_checks);

   if (!getenv("EFL_TEST_ECORE_CON_IPV6"))
     {
        fputs("\nWARNING: your system has IPv6 disabled. Skipping IPv6 tests!\n\n"
              "\nexport EFL_TEST_ECORE_CON_IPV6=1\n"
              "\nTo enable it\n",
              stderr);
        return;
     }

   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_manual_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_manual_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_create_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_create_sockaddr_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_parse_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_parse_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_resolve_ok);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_resolve_fail);
   tcase_add_test(tc, ecore_test_efl_net_ip_address_ipv6_checks);
}
