barbieri pushed a commit to branch master.

http://git.enlightenment.org/core/efl.git/commit/?id=633ec445b8f167339fa451350319f47a4b01900b

commit 633ec445b8f167339fa451350319f47a4b01900b
Author: Gustavo Sverzut Barbieri <[email protected]>
Date:   Mon Dec 12 02:23:29 2016 -0200

    efl_net: add Efl.Net.Ip_Address
    
    This is a string parser, serializer and asynchronous resolver.
    
    It's purpose is to convert to and from the strings we use in our
    dialers and servers, such as "127.0.0.1:1234" or "[::1]:1234",
    properties allow to check the family, port, address bytes (slice) and
    even get a struct sockaddr pointer to use with bind()/connect() in
    outside code.
    
    It will also offer some utilities present in netinet/in.h in an easy
    to use way, after all IN6_IS_ADDR_LOOPBACK() works one way, while
    there is no IN_LOOPBACK and comparing with INADDR_LOOPBACK will lead
    to errors since it's in network order.
    
    Last but not least, it will do asynchronous resolve of host and port
    names using an internal thread and getaddrinfo(). The results are
    delivered using a Future with an array of objects.
---
 src/Makefile_Ecore_Con.am                          |    5 +-
 src/examples/ecore/.gitignore                      |    1 +
 src/examples/ecore/Makefile.am                     |    4 +
 src/examples/ecore/efl_net_ip_address_example.c    |  137 +++
 src/lib/ecore_con/Ecore_Con_Eo.h                   |    2 +
 src/lib/ecore_con/efl_net_ip_address.c             |  569 ++++++++++
 src/lib/ecore_con/efl_net_ip_address.eo            |  246 +++++
 src/tests/ecore_con/ecore_con_suite.c              |    1 +
 src/tests/ecore_con/ecore_con_suite.h              |    1 +
 .../ecore_con/ecore_con_test_efl_net_ip_address.c  | 1154 ++++++++++++++++++++
 10 files changed, 2119 insertions(+), 1 deletion(-)

diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am
index 10285c6..3c62337 100644
--- a/src/Makefile_Ecore_Con.am
+++ b/src/Makefile_Ecore_Con.am
@@ -27,6 +27,7 @@ ecore_con_eolian_files = \
         lib/ecore_con/efl_net_control_technology.eo \
         lib/ecore_con/efl_net_control.eo \
         lib/ecore_con/efl_net_session.eo \
+        lib/ecore_con/efl_net_ip_address.eo \
        lib/ecore_con/ecore_con_eet_base.eo \
        lib/ecore_con/ecore_con_eet_server_obj.eo \
        lib/ecore_con/ecore_con_eet_client_obj.eo
@@ -109,7 +110,8 @@ lib/ecore_con/efl_net_socket_ssl.c \
 lib/ecore_con/efl_net_ssl_context.c \
 lib/ecore_con/efl_net_dialer_ssl.c \
 lib/ecore_con/efl_net_server_ssl.c \
-lib/ecore_con/ecore_con_local.c
+lib/ecore_con/ecore_con_local.c \
+lib/ecore_con/efl_net_ip_address.c
 
 if EFL_NET_CONTROL_BACKEND_CONNMAN
 lib_ecore_con_libecore_con_la_SOURCES += \
@@ -179,6 +181,7 @@ tests/ecore_con/ecore_con_suite.c \
 tests/ecore_con/ecore_con_test_ecore_con.c \
 tests/ecore_con/ecore_con_test_ecore_con_url.c \
 tests/ecore_con/ecore_con_test_ecore_con_eet.c \
+tests/ecore_con/ecore_con_test_efl_net_ip_address.c \
 tests/ecore_con/ecore_con_suite.h
 
 tests_ecore_con_ecore_con_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
diff --git a/src/examples/ecore/.gitignore b/src/examples/ecore/.gitignore
index 88fc2bd..eeaf839 100644
--- a/src/examples/ecore/.gitignore
+++ b/src/examples/ecore/.gitignore
@@ -66,5 +66,6 @@
 /*.pem
 /efl_net_session_example
 /efl_net_control_example
+/efl_net_ip_address_example
 /ecore_ipc_server_example
 /ecore_ipc_client_example
diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am
index 5ac7a1d..f0f4557 100644
--- a/src/examples/ecore/Makefile.am
+++ b/src/examples/ecore/Makefile.am
@@ -97,6 +97,7 @@ efl_net_socket_ssl_dialer_example \
 efl_net_socket_ssl_server_example \
 efl_net_session_example \
 efl_net_control_example \
+efl_net_ip_address_example \
 ecore_ipc_server_example \
 ecore_ipc_client_example
 
@@ -366,6 +367,9 @@ efl_net_session_example_LDADD = $(ECORE_CON_COMMON_LDADD)
 efl_net_control_example_SOURCES = efl_net_control_example.c
 efl_net_control_example_LDADD = $(ECORE_CON_COMMON_LDADD)
 
+efl_net_ip_address_example_SOURCES = efl_net_ip_address_example.c
+efl_net_ip_address_example_LDADD = $(ECORE_CON_COMMON_LDADD)
+
 ecore_ipc_server_example_SOURCES = ecore_ipc_server_example.c
 ecore_ipc_server_example_LDADD = $(ECORE_IPC_COMMON_LDADD)
 
diff --git a/src/examples/ecore/efl_net_ip_address_example.c 
b/src/examples/ecore/efl_net_ip_address_example.c
new file mode 100644
index 0000000..8c817c4
--- /dev/null
+++ b/src/examples/ecore/efl_net_ip_address_example.c
@@ -0,0 +1,137 @@
+#define EFL_BETA_API_SUPPORT 1
+#define EFL_EO_API_SUPPORT 1
+#include <Ecore.h>
+#include <Ecore_Con.h>
+#include <Ecore_Getopt.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+static int retval = EXIT_SUCCESS;
+static Eina_List *resolving = NULL;
+
+static void
+_print_ip_addr_info(const Eo *o)
+{
+   const Eina_Slice *addr;
+   const struct sockaddr *sa;
+   char buf[INET6_ADDRSTRLEN] = "?";
+   uint16_t port;
+   size_t i;
+
+   printf("INFO: %p = %s\n", o, efl_net_ip_address_string_get(o));
+   printf("INFO:   - family: %d (%s)\n", efl_net_ip_address_family_get(o), 
efl_net_ip_address_family_get(o) == AF_INET ? "AF_INET" : "AF_INET6");
+   printf("INFO:   - port: %hu\n", efl_net_ip_address_port_get(o));
+
+   addr = efl_net_ip_address_get(o);
+   printf("INFO:   - address %zd bytes:", addr->len);
+   for (i = 0; i < addr->len; i++)
+     printf(" %#hhx", addr->bytes[i]);
+   putchar('\n');
+
+   sa = efl_net_ip_address_sockaddr_get(o);
+   if (sa->sa_family == AF_INET)
+     {
+        struct sockaddr_in *a = (struct sockaddr_in *)sa;
+        inet_ntop(sa->sa_family, &a->sin_addr, buf, sizeof(buf));
+        port = ntohs(a->sin_port);
+     }
+   else
+     {
+        struct sockaddr_in6 *a = (struct sockaddr_in6 *)sa;
+        inet_ntop(sa->sa_family, &a->sin6_addr, buf, sizeof(buf));
+        port = ntohs(a->sin6_port);
+     }
+
+   printf("INFO:   - sockaddr=%p (%d, '%s', %u)\n",
+          sa, sa->sa_family, buf, port);
+
+   printf("INFO:   - ipv4_class_a: %d\n", 
efl_net_ip_address_ipv4_class_a_check(o));
+   printf("INFO:   - ipv4_class_b: %d\n", 
efl_net_ip_address_ipv4_class_b_check(o));
+   printf("INFO:   - ipv4_class_c: %d\n", 
efl_net_ip_address_ipv4_class_c_check(o));
+   printf("INFO:   - ipv4_class_d: %d\n", 
efl_net_ip_address_ipv4_class_d_check(o));
+   printf("INFO:   - ipv6_v4mapped: %d\n", 
efl_net_ip_address_ipv6_v4mapped_check(o));
+   printf("INFO:   - ipv6_v4compat: %d\n", 
efl_net_ip_address_ipv6_v4compat_check(o));
+   printf("INFO:   - ipv6_local_link: %d\n", 
efl_net_ip_address_ipv6_local_link_check(o));
+   printf("INFO:   - ipv6_local_site: %d\n", 
efl_net_ip_address_ipv6_local_site_check(o));
+   printf("INFO:   - multicast: %d\n", efl_net_ip_address_multicast_check(o));
+   printf("INFO:   - loopback: %d\n", efl_net_ip_address_loopback_check(o));
+   printf("INFO:   - any: %d\n", efl_net_ip_address_any_check(o));
+}
+
+static void
+_resolved(void *data EINA_UNUSED, const Efl_Event *event)
+{
+   Efl_Future *future = event->object;
+   Efl_Future_Event_Success *f = event->info;
+   Efl_Net_Ip_Address_Resolve_Results *r = f->value;
+   Eina_Array_Iterator it;
+   unsigned int i;
+   const Efl_Net_Ip_Address *o;
+
+   printf("INFO: resolved '%s' to canonical name '%s':\n",
+          r->request_address, r->canonical_name);
+
+   EINA_ARRAY_ITER_NEXT(r->results, i, o, it)
+     _print_ip_addr_info(o);
+
+   putchar('\n');
+
+   resolving = eina_list_remove(resolving, future);
+   if (!resolving) ecore_main_loop_quit();
+}
+
+static void
+_resolve_failed(void *data, const Efl_Event *event)
+{
+   const char *address = data;
+   Efl_Future *future = event->object;
+   Efl_Future_Event_Failure *f = event->info;
+   fprintf(stderr, "ERROR: Failed to resolve '%s': %s\n",
+           address, eina_error_msg_get(f->error));
+   retval = EXIT_FAILURE;
+
+   resolving = eina_list_remove(resolving, future);
+   if (!resolving) ecore_main_loop_quit();
+}
+
+int
+main(int argc, char *argv[])
+{
+   int i;
+
+   ecore_init();
+   ecore_con_init();
+
+   for (i = 1; i < argc; i++)
+     {
+        const char *address = argv[i];
+        Eo *o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, address);
+        if (o)
+          {
+             _print_ip_addr_info(o);
+             efl_del(o);
+          }
+        else
+          {
+             Efl_Future *f = 
efl_net_ip_address_resolve(EFL_NET_IP_ADDRESS_CLASS, address, 0, 0);
+             if (!f)
+               {
+                  fprintf(stderr, "ERROR: cannot resolve '%s'!\n", address);
+                  retval = EXIT_FAILURE;
+               }
+             else
+               {
+                  printf("INFO: %s is not numeric, resolving...\n", address);
+                  efl_future_then(f, _resolved, _resolve_failed, NULL, 
address);
+                  resolving = eina_list_append(resolving, f);
+               }
+          }
+     }
+
+   if (resolving) ecore_main_loop_begin();
+
+   ecore_con_shutdown();
+   ecore_shutdown();
+
+   return retval;
+}
diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h
index 089c88c..a5ed29d 100644
--- a/src/lib/ecore_con/Ecore_Con_Eo.h
+++ b/src/lib/ecore_con/Ecore_Con_Eo.h
@@ -43,3 +43,5 @@
 #include "efl_net_control_access_point.eo.h"
 #include "efl_net_control.eo.h"
 #include "efl_net_session.eo.h"
+
+#include "efl_net_ip_address.eo.h"
diff --git a/src/lib/ecore_con/efl_net_ip_address.c 
b/src/lib/ecore_con/efl_net_ip_address.c
new file mode 100644
index 0000000..0109748
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_ip_address.c
@@ -0,0 +1,569 @@
+#define EFL_NET_IP_ADDRESS_PROTECTED 1
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#include "Ecore.h"
+#include "Ecore_Con.h"
+#include "ecore_con_private.h"
+
+typedef struct _Efl_Net_Ip_Address_Data {
+   char string[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+   union {
+      struct sockaddr addr;
+      struct sockaddr_in ipv4;
+      struct sockaddr_in6 ipv6;
+   };
+   Eina_Slice addr_slice;
+} Efl_Net_Ip_Address_Data;
+
+#define MY_CLASS EFL_NET_IP_ADDRESS_CLASS
+
+EOLIAN static Eo *
+_efl_net_ip_address_efl_object_finalize(Eo *o, Efl_Net_Ip_Address_Data *pd)
+{
+   const uint16_t *pport;
+
+   o = efl_finalize(efl_super(o, MY_CLASS));
+   if (!o) return NULL;
+
+   EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->addr.sa_family == 0, NULL);
+
+   if (!efl_net_ip_port_fmt(pd->string, sizeof(pd->string), &pd->addr))
+     {
+        ERR("Could not format address!");
+        return NULL;
+     }
+
+   if (pd->addr.sa_family == AF_INET6)
+     pport = &pd->ipv6.sin6_port;
+   else
+     pport = &pd->ipv4.sin_port;
+
+   if (*pport == 0) /* port == 0, no ":0" in the string */
+     {
+        char *d = strrchr(pd->string, ':');
+        EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
+        *d = '\0';
+     }
+
+   return o;
+}
+
+EOLIAN static const char *
+_efl_net_ip_address_string_get(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd)
+{
+   return pd->string;
+}
+
+EOLIAN static void
+_efl_net_ip_address_family_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, 
int family)
+{
+   if (pd->addr.sa_family == family) return;
+   EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family != 0);
+   EINA_SAFETY_ON_TRUE_RETURN((family != AF_INET) && (family != AF_INET6));
+   pd->addr.sa_family = family;
+   if (family == AF_INET6)
+     {
+        pd->addr_slice.mem = &pd->ipv6.sin6_addr;
+        pd->addr_slice.len = sizeof(pd->ipv6.sin6_addr);
+     }
+   else
+     {
+        pd->addr_slice.mem = &pd->ipv4.sin_addr;
+        pd->addr_slice.len = sizeof(pd->ipv4.sin_addr);
+     }
+}
+
+EOLIAN static int
+_efl_net_ip_address_family_get(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd)
+{
+   return pd->addr.sa_family;
+}
+
+EOLIAN static void
+_efl_net_ip_address_port_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, 
uint16_t port)
+{
+   uint16_t *pport, nport = htons(port);
+
+   EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family == 0);
+   if (pd->addr.sa_family == AF_INET6)
+     pport = &pd->ipv6.sin6_port;
+   else
+     pport = &pd->ipv4.sin_port;
+
+   if (*pport == nport) return;
+   if (*pport)
+     {
+        ERR("port already set to %hu, new %hu", ntohs(*pport), port);
+        return;
+     }
+
+   *pport = nport;
+}
+
+EOLIAN static uint16_t
+_efl_net_ip_address_port_get(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd)
+{
+   const uint16_t *pport;
+
+   EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->addr.sa_family == 0, 0);
+   if (pd->addr.sa_family == AF_INET6)
+     pport = &pd->ipv6.sin6_port;
+   else
+     pport = &pd->ipv4.sin_port;
+
+   return ntohs(*pport);
+}
+
+EOLIAN static void
+_efl_net_ip_address_address_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data 
*pd, const Eina_Slice *address)
+{
+   Eina_Rw_Slice rw_slice;
+   size_t i;
+
+   EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family == 0);
+   EINA_SAFETY_ON_NULL_RETURN(address);
+
+   rw_slice.mem = (void *)pd->addr_slice.mem;
+   rw_slice.len = pd->addr_slice.len;
+
+   EINA_SAFETY_ON_TRUE_RETURN(rw_slice.len != address->len);
+
+   if (eina_slice_compare(eina_rw_slice_slice_get(rw_slice), *address) == 0)
+     return;
+
+   for (i = 0; i < rw_slice.len; i++)
+     {
+        if (rw_slice.bytes[i])
+          {
+             char old_str[INET6_ADDRSTRLEN] = "";
+             char new_str[INET6_ADDRSTRLEN] = "";
+
+             if (!inet_ntop(pd->addr.sa_family, rw_slice.mem, old_str, 
sizeof(old_str)))
+               {
+                  old_str[0] = '?';
+                  old_str[1] = '\0';
+               }
+             if (!inet_ntop(pd->addr.sa_family, address->mem, new_str, 
sizeof(new_str)))
+               {
+                  new_str[0] = '?';
+                  new_str[1] = '\0';
+               }
+             ERR("address already set to %s, new %s", old_str, new_str);
+             return;
+          }
+     }
+
+   eina_rw_slice_copy(rw_slice, *address);
+}
+
+EOLIAN static const Eina_Slice *
+_efl_net_ip_address_address_get(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd)
+{
+   return &pd->addr_slice;
+}
+
+EOLIAN static void
+_efl_net_ip_address_sockaddr_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data 
*pd, const void *ptr)
+{
+   const struct sockaddr *sockaddr = ptr;
+
+   EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family != 0);
+   EINA_SAFETY_ON_NULL_RETURN(sockaddr);
+
+   EINA_SAFETY_ON_TRUE_RETURN((sockaddr->sa_family != AF_INET) && 
(sockaddr->sa_family != AF_INET6));
+
+   if (sockaddr->sa_family == AF_INET6)
+     {
+        memcpy(&pd->ipv6, sockaddr, sizeof(pd->ipv6));
+        pd->addr_slice.mem = &pd->ipv6.sin6_addr;
+        pd->addr_slice.len = sizeof(pd->ipv6.sin6_addr);
+     }
+   else
+     {
+        memcpy(&pd->ipv4, sockaddr, sizeof(pd->ipv4));
+        pd->addr_slice.mem = &pd->ipv4.sin_addr;
+        pd->addr_slice.len = sizeof(pd->ipv4.sin_addr);
+     }
+}
+
+EOLIAN static const void *
+_efl_net_ip_address_sockaddr_get(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data 
*pd)
+{
+   return &pd->addr;
+}
+
+#define IPV4_ADDR_GET(pd) ntohl(pd->ipv4.sin_addr.s_addr)
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv4_class_a_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET) && IN_CLASSA(IPV4_ADDR_GET(pd));
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv4_class_b_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET) && IN_CLASSB(IPV4_ADDR_GET(pd));
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv4_class_c_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET) && IN_CLASSC(IPV4_ADDR_GET(pd));
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv4_class_d_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET) && IN_CLASSD(IPV4_ADDR_GET(pd));
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv6_v4mapped_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET6) &&
+     IN6_IS_ADDR_V4MAPPED(&pd->ipv6.sin6_addr);
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv6_v4compat_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET6) &&
+     IN6_IS_ADDR_V4COMPAT(&pd->ipv6.sin6_addr);
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv6_local_link_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET6) &&
+     IN6_IS_ADDR_LINKLOCAL(&pd->ipv6.sin6_addr);
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_ipv6_local_site_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   return (pd->addr.sa_family == AF_INET6) &&
+     IN6_IS_ADDR_SITELOCAL(&pd->ipv6.sin6_addr);
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_multicast_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   if (pd->addr.sa_family == AF_INET6)
+     return IN6_IS_ADDR_MULTICAST(&pd->ipv6.sin6_addr);
+   else
+     return IN_MULTICAST(IPV4_ADDR_GET(pd));
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_loopback_check(const Eo *o EINA_UNUSED, 
Efl_Net_Ip_Address_Data *pd)
+{
+   if (pd->addr.sa_family == AF_INET6)
+     return IN6_IS_ADDR_LOOPBACK(&pd->ipv6.sin6_addr);
+   else
+     return IPV4_ADDR_GET(pd) == INADDR_LOOPBACK;
+}
+
+EOLIAN static Eina_Bool
+_efl_net_ip_address_any_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data 
*pd)
+{
+   size_t i;
+
+   for (i = 0; i < pd->addr_slice.len; i++)
+     {
+        if (pd->addr_slice.bytes[i])
+          return EINA_FALSE;
+     }
+
+   return i > 0;
+}
+
+EOLIAN static Efl_Net_Ip_Address *
+_efl_net_ip_address_create(Eo *cls, void *pd EINA_UNUSED, uint16_t port, const 
Eina_Slice address)
+{
+   int family;
+
+   EINA_SAFETY_ON_TRUE_RETURN_VAL(address.len != 4 && address.len != 16, NULL);
+
+   if (address.len == 16)
+     family = AF_INET6;
+   else
+     family = AF_INET;
+
+   return efl_add(cls, NULL,
+                  efl_net_ip_address_family_set(efl_added, family),
+                  efl_net_ip_address_port_set(efl_added, port),
+                  efl_net_ip_address_set(efl_added, &address));
+}
+
+EOLIAN static Efl_Net_Ip_Address *
+_efl_net_ip_address_create_sockaddr(Eo *cls, void *pd EINA_UNUSED, const void 
*ptr)
+{
+   const struct sockaddr *sockaddr = ptr;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(sockaddr, NULL);
+   EINA_SAFETY_ON_TRUE_RETURN_VAL((sockaddr->sa_family != AF_INET) && 
(sockaddr->sa_family != AF_INET6), NULL);
+
+   return efl_add(cls, NULL,
+                  efl_net_ip_address_sockaddr_set(efl_added, sockaddr));
+}
+
+EOLIAN static Efl_Net_Ip_Address *
+_efl_net_ip_address_parse(Eo *cls, void *pd EINA_UNUSED, const char 
*numeric_address)
+{
+   struct sockaddr_storage ss;
+   Eina_Bool r;
+   const char *address = numeric_address;
+   char *tmp = NULL;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(numeric_address, NULL);
+
+   if (numeric_address[0] != '[')
+     {
+        const char *p = strchr(numeric_address, ':');
+        if (p)
+          {
+             p = strchr(p + 1, ':');
+             if (p)
+               {
+                  size_t len = strlen(numeric_address);
+                  /* IPv6 no braces: ::1, etc... no port, add braces */
+                  tmp = malloc(len + sizeof("[]"));
+                  EINA_SAFETY_ON_NULL_RETURN_VAL(tmp, NULL);
+                  tmp[0] = '[';
+                  memcpy(tmp + 1, numeric_address, len);
+                  tmp[1 + len] = ']';
+                  address = tmp;
+               }
+          }
+     }
+
+   r = efl_net_ip_port_parse(address, &ss);
+   free(tmp);
+   if (!r)
+     {
+        DBG("could not parse numeric address: %s", numeric_address);
+        return NULL;
+     }
+
+   return efl_add(cls, NULL,
+                  efl_net_ip_address_sockaddr_set(efl_added, &ss));
+}
+
+typedef struct _Efl_Net_Ip_Address_Resolve_Context {
+   Efl_Net_Ip_Address_Resolve_Results *result;
+   Ecore_Thread *thread;
+   Efl_Promise *promise;
+} Efl_Net_Ip_Address_Resolve_Context;
+
+static void
+_efl_net_ip_address_resolve_results_free(void *data)
+{
+   Efl_Net_Ip_Address_Resolve_Results *r = data;
+
+   if (r->results)
+     {
+        Eina_Array_Iterator it;
+        unsigned int i;
+        Efl_Net_Ip_Address *o;
+
+        EINA_ARRAY_ITER_NEXT(r->results, i, o, it)
+          efl_unref(o);
+
+        eina_array_free(r->results);
+        r->results = NULL;
+     }
+
+   eina_stringshare_replace(&r->canonical_name, NULL);
+   eina_stringshare_replace(&r->request_address, NULL);
+   free(r);
+}
+
+static void
+_efl_net_ip_address_resolve_del(void *data, const Efl_Event *event EINA_UNUSED)
+{
+   Efl_Net_Ip_Address_Resolve_Context *ctx = data;
+
+   ctx->promise = NULL;
+
+   if (ctx->thread)
+     {
+        ecore_thread_cancel(ctx->thread);
+        ctx->thread = NULL;
+     }
+
+   if (ctx->result)
+     {
+        _efl_net_ip_address_resolve_results_free(ctx->result);
+        ctx->result = NULL;
+     }
+
+   free(ctx);
+}
+
+static inline int
+_efl_net_ip_address_find(const Eina_Array *array, const struct sockaddr *addr)
+{
+   Eina_Array_Iterator it;
+   unsigned int i;
+   const Efl_Net_Ip_Address *o;
+
+   if (addr->sa_family == AF_INET6)
+     {
+        EINA_ARRAY_ITER_NEXT(array, i, o, it)
+          {
+             const struct sockaddr *other = efl_net_ip_address_sockaddr_get(o);
+             if (other->sa_family == AF_INET6)
+               {
+                  if (memcmp(other,  addr, sizeof(struct sockaddr_in6)) == 0)
+                    return (int)i;
+               }
+          }
+     }
+   else
+     {
+        EINA_ARRAY_ITER_NEXT(array, i, o, it)
+          {
+             const struct sockaddr *other = efl_net_ip_address_sockaddr_get(o);
+             if (other->sa_family == AF_INET)
+               {
+                  if (memcmp(other,  addr, sizeof(struct sockaddr_in)) == 0)
+                    return (int)i;
+               }
+          }
+     }
+   return -1;
+}
+
+static void
+_efl_net_ip_address_resolve_done(void *data, const char *host, const char 
*port, const struct addrinfo *hints EINA_UNUSED, struct addrinfo *result, int 
gai_error)
+{
+   Efl_Net_Ip_Address_Resolve_Context *ctx = data;
+   Efl_Net_Ip_Address_Resolve_Results *r;
+   const struct addrinfo *a;
+
+   DBG("done resolving '%s' (host='%s', port='%s'): %s",
+       ctx->result->request_address, host, port,
+       gai_error ? gai_strerror(gai_error) : "success");
+
+   ctx->thread = NULL;
+
+   if (gai_error)
+     {
+        Eina_Error err = EFL_NET_ERROR_COULDNT_RESOLVE_HOST;
+
+        if (gai_error == EAI_SYSTEM)
+          err = errno;
+
+        efl_promise_failed_set(ctx->promise, err);
+        return;
+     }
+
+   ctx->result->results = eina_array_new(16);
+   if (!ctx->result->results)
+     {
+        efl_promise_failed_set(ctx->promise, ENOMEM);
+        return;
+     }
+
+   r = ctx->result;
+   ctx->result = NULL; /* steal for efl_promise_value_set() */
+
+   for (a = result; a != NULL; a = a->ai_next)
+     {
+        Eo *o;
+
+        if (EINA_UNLIKELY((r->canonical_name == NULL) &&
+                          (a->ai_canonname != NULL)))
+          r->canonical_name = eina_stringshare_add(a->ai_canonname);
+
+        /* some addresses get duplicated with different options that we
+         * do not care, so check for duplicates.
+         */
+        if (EINA_UNLIKELY(_efl_net_ip_address_find(r->results, a->ai_addr) >= 
0))
+          continue;
+
+        o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, 
a->ai_addr);
+        if (o)
+          {
+             if (!eina_array_push(r->results, o))
+               efl_del(o);
+          }
+     }
+   freeaddrinfo(result);
+
+   efl_promise_value_set(ctx->promise, r, 
_efl_net_ip_address_resolve_results_free);
+}
+
+EOLIAN static Efl_Future *
+_efl_net_ip_address_resolve(Eo *cls EINA_UNUSED, void *pd EINA_UNUSED, const 
char *address, int family, int flags)
+{
+   Efl_Net_Ip_Address_Resolve_Context *ctx;
+   struct addrinfo hints = { };
+   const char *host = NULL, *port = NULL;
+   char *str;
+
+   EINA_SAFETY_ON_NULL_RETURN_VAL(address, NULL);
+
+   if (family == 0) family = AF_UNSPEC;
+   EINA_SAFETY_ON_TRUE_RETURN_VAL((family != AF_UNSPEC) && (family != AF_INET) 
&& (family != AF_INET6), NULL);
+
+   if (flags == 0) flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_CANONNAME;
+   hints.ai_family = family;
+   hints.ai_flags = flags;
+
+   str = strdup(address);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(str, NULL);
+
+   efl_net_ip_port_split(str, &host, &port);
+   if ((!host) || (host[0] == '\0'))
+     {
+        host = address;
+        port = "0";
+     }
+   if (!port) port = "0";
+
+   ctx = calloc(1, sizeof(Efl_Net_Ip_Address_Resolve_Context));
+   EINA_SAFETY_ON_NULL_GOTO(ctx, error_ctx);
+
+   ctx->result = calloc(1, sizeof(Efl_Net_Ip_Address_Resolve_Results));
+   EINA_SAFETY_ON_NULL_GOTO(ctx->result, error_result);
+
+   ctx->result->request_address = eina_stringshare_add(address);
+   EINA_SAFETY_ON_NULL_GOTO(ctx->result->request_address, 
error_result_address);
+
+   ctx->thread = efl_net_ip_resolve_async_new(host, port, &hints, 
_efl_net_ip_address_resolve_done, ctx);
+   EINA_SAFETY_ON_NULL_GOTO(ctx->thread, error_thread);
+
+   ctx->promise = efl_add(EFL_PROMISE_CLASS, ecore_main_loop_get(),
+                          efl_event_callback_add(efl_added, EFL_EVENT_DEL, 
_efl_net_ip_address_resolve_del, ctx));
+   EINA_SAFETY_ON_NULL_GOTO(ctx->promise, error_promise);
+
+   free(str);
+   return efl_promise_future_get(ctx->promise);
+
+ error_promise:
+   ecore_thread_cancel(ctx->thread);
+ error_thread:
+   eina_stringshare_del(ctx->result->request_address);
+ error_result_address:
+   free(ctx->result);
+ error_result:
+   free(ctx);
+ error_ctx:
+   free(str);
+   return NULL;
+}
+
+#include "efl_net_ip_address.eo.c"
diff --git a/src/lib/ecore_con/efl_net_ip_address.eo 
b/src/lib/ecore_con/efl_net_ip_address.eo
new file mode 100644
index 0000000..9398c29
--- /dev/null
+++ b/src/lib/ecore_con/efl_net_ip_address.eo
@@ -0,0 +1,246 @@
+import eina_types;
+
+struct Efl.Net.Ip.Address.Resolve_Results {
+    [[The results of @Efl.Net.Ip.Address.resolve call.
+
+      This structure is created by @Efl.Net.Ip.Address.resolve.
+      XXX should be destroyed manually?
+
+      @since 1.19
+    ]]
+    request_address: string; [[The 'address' argument given to 
Efl.Net.Ip.Address.resolve]]
+    canonical_name: string; [[The canonical name, if it was requested in 
flags]]
+    results: array<Efl.Net.Ip.Address>; [[The resolved objects. Do not modify 
this array, but you can keep reference to elements using efl_ref() and 
efl_unref()]]
+}
+
+class Efl.Net.Ip.Address (Efl.Object) {
+    [[An Internet Protocol (IP) Address.
+
+      This class is a set of helpers to translate to and from address
+      strings used in Efl.Net. For IP they take the formats:
+
+        - IPv4 complete: 127.0.0.1:1234
+        - IPv4 no port: 127.0.0.1
+        - IPv6 complete: [::1]:1234
+        - IPv6 no port: [::1]
+        - IPv6 no braces (implies no port): ::1
+
+      However in other libraries one may use the address numbers or
+      even a 'struct sockaddr' handle and translating by yourself may
+      be too much work. To convert to a string, just create an
+      instance with @.create or @.create_sockaddr and then query
+      @.string. To convert from numeric string to sockaddr, create an
+      instance with @.parse and then query @.sockaddr.
+
+      To resolve a host and port name to numbers use @.resolve, this
+      will asynchronously resolve and return the results in a promise.
+
+      The result of @.string can be passed to @Efl.Net.Dialer.dial and
+      @Efl.Net.Server.serve
+
+      @since 1.19
+    ]]
+
+    methods {
+        create @class {
+            [[Create an object given family, port and address.
+
+              This is a convenience to create an object in a single
+              call. To create based on 'struct sockaddr', see
+              @.create_sockaddr. To create from string, see @.parse.
+            ]]
+            params {
+                port: uint16; [[Port number in Host/Native endianess]]
+                address: const(Eina.Slice); [[Address bytes. If 4 bytes, 
AF_INET will be used. If 16 bytes, AF_INET6 will be used. All other sizes will 
result in failure.]]
+            }
+            return: own(Efl.Net.Ip.Address); [[newly created object or $NULL 
if parameters were invalid.]]
+        }
+
+        create_sockaddr @class {
+            [[Create an object given sockaddr
+
+              This is a convenience to create an object in a single call.
+              To create based on native port and address bytes,
+              use @.create, to create from string use @.parse.
+            ]]
+            params {
+                sockaddr: const(void_ptr) @nonull; [[The pointer to struct 
sockaddr-compatible handle as per <netinet/in.h>.]]
+            }
+            return: own(Efl.Net.Ip.Address); [[newly created object or $NULL 
if parameter was invalid.]]
+        }
+
+        parse @class {
+            [[Parse a numeric address and return an object representing it.
+
+              This parses a numeric IPv4 or IPv6 address and optional
+              port, returning an object representing it on success or
+              $NULL on failure.
+
+              The address may be in the formats:
+
+               - IPv4 complete: 127.0.0.1:1234
+               - IPv4 no port: 127.0.0.1
+               - IPv6 complete: [::1]:1234
+               - IPv6 no port: [::1]
+               - IPv6 no braces (implies no port): ::1
+
+              If you want to translate address and port to numbers use
+              @.resolve.
+            ]]
+            params {
+                numeric_address: string; [[The numeric address to parse, such 
as '127.0.0.1:1234' or '[::1]:1234']]
+            }
+            return: own(Efl.Net.Ip.Address); [[The new IP address object or 
NULL if it failed to parse]]
+        }
+
+        resolve @class {
+            [[Asynchronously resolve host and port names.
+
+              This will resolve the host and port names, returning the
+              results asynchronously in a Future.
+
+              It's based on getaddrinfo() and will receive extra flags
+              to change its behavior.
+
+              Ports can also be named, for example http resolves to
+              80. Your system database is used (see getaddrinfo()).
+
+              You may try @.parse if you have numeric values for host
+              and port.
+            ]]
+            params {
+                address: string @nonull; [[The address such as 
enlightenment.org:http or enlightenment.org (port=0)]]
+                family: int @optional; [[Preferred family. AF_UNSPEC or 0 for 
both, otherwise one of AF_INET or AF_INET6]]
+                flags: int @optional; [[Flags to use with getaddrinfo(). If 0, 
default flags are used (AI_V4MAPPED | AI_ADDRCONFIG, if these exist in your 
system).]]
+            }
+            return: future<const(Efl.Net.Ip.Address.Resolve_Results)>; [[The 
resolve results. It contains a list of Efl.Net.Ip.Address, they will be 
automatically deleted unless you keep a reference to it.]]
+        }
+
+        @property string {
+            [[Returns the numeric address formatted as a string.
+
+              The format will be:
+
+               - IPv4 with port > 0: 127.0.0.1:1234
+               - IPv4 with port == 0: 127.0.0.1
+               - IPv6 with port > 0: [::1]:1234
+               - IPv6 with port == 0: [::1]
+            ]]
+            get { }
+            values {
+                str: string @nonull;
+            }
+        }
+
+        @property family {
+            [[The address family, one of AF_INET6 or AF_INET.
+
+              May only be set once, afterwards the object is not changing.
+            ]]
+            values {
+                family: int; [[AF_INET or AF_INET6]]
+            }
+        }
+
+        @property port {
+            [[The address port in Host/Native endianess.
+
+              May only be set once, afterwards the object is not changing.
+
+              Must be set after @.family.
+            ]]
+            values {
+                port: uint16; [[Port number in Host/Native endianess]]
+            }
+        }
+
+        @property address {
+            [[The bytes representing the address.
+
+              May only be set once, afterwards the object is not changing.
+
+              Must be set after @.family.
+            ]]
+            values {
+                address: const(ptr(Eina.Slice)) @nonull;
+            }
+        }
+
+        @property sockaddr {
+            [[The <netinet/in.h>-compatible 'struct sockaddr'.
+
+              May only be set once, afterwards the object is not changing.
+            ]]
+            values {
+                sockaddr: const(void_ptr) @nonull;
+            }
+        }
+
+        ipv4_class_a_check @const {
+            [[Check if IPv4 and is CLASS-A]]
+            return: bool(false); [[$true if is a CLASS-A IPv4 address]]
+        }
+
+        ipv4_class_b_check @const {
+            [[Check if IPv4 and is CLASS-B]]
+            return: bool(false); [[$true if is a CLASS-B IPv4 address]]
+        }
+
+        ipv4_class_c_check @const {
+            [[Check if IPv4 and is CLASS-C]]
+            return: bool(false); [[$true if is a CLASS-C IPv4 address]]
+        }
+
+        ipv4_class_d_check @const {
+            [[Check if IPv4 and is CLASS-D]]
+            return: bool(false); [[$true if is a CLASS-D IPv4 address]]
+        }
+
+        ipv6_v4mapped_check @const {
+            [[Check if IPv6 is mapping an IPv4.
+
+              If the IPv6 server is not IPv6-only, then it will take
+              IPv4 addresses and map them inside IPv6. This checks if
+              the given address is an IPv4 mapped inside IPv6.
+            ]]
+            return: bool(false); [[$true if is the IPv6 is IPv4 mapped inside 
IPv6 address]]
+        }
+
+        ipv6_v4compat_check @const {
+            [[Check if IPv6 is compatible with IPv4.
+
+              This happens if the first 12 bytes of IPv6 are 0.
+            ]]
+            return: bool(false); [[$true if is a IPv6 address is IPv4 
compatible.]]
+        }
+
+        ipv6_local_link_check @const {
+            [[Check if IPv6 is link-local.]]
+            return: bool(false); [[$true if is a IPv6 address is link-local]]
+        }
+
+        ipv6_local_site_check @const {
+            [[Check if IPv6 is site-local.]]
+            return: bool(false); [[$true if is a IPv6 address is site-local]]
+        }
+
+        multicast_check @const {
+            [[Check if multicast]]
+            return: bool(false); [[$true if is a multicast address]]
+        }
+
+        loopback_check @const {
+            [[Check if loopback "127.0.0.1" (IPv4) or "::1" (IPv6)]]
+            return: bool(false); [[$true if is a multicast address]]
+        }
+
+        any_check @const {
+            [[Check if "0.0.0.0" (IPv4) or "::" (IPv6)]]
+            return: bool(false); [[$true if address means "any"]]
+        }
+    }
+
+    implements {
+        Efl.Object.finalize;
+    }
+}
diff --git a/src/tests/ecore_con/ecore_con_suite.c 
b/src/tests/ecore_con/ecore_con_suite.c
index a3ef616..21585ae 100644
--- a/src/tests/ecore_con/ecore_con_suite.c
+++ b/src/tests/ecore_con/ecore_con_suite.c
@@ -9,6 +9,7 @@ static const Efl_Test_Case etc[] = {
   { "Ecore_Con", ecore_con_test_ecore_con },
   { "Ecore_Con_Url", ecore_con_test_ecore_con_url },
   { "Ecore_Con_Eet", ecore_con_test_ecore_con_eet },
+  { "Efl_Net_Ip_Address", ecore_con_test_efl_net_ip_address },
   { NULL, NULL }
 };
 
diff --git a/src/tests/ecore_con/ecore_con_suite.h 
b/src/tests/ecore_con/ecore_con_suite.h
index 5d18647..133b68a 100644
--- a/src/tests/ecore_con/ecore_con_suite.h
+++ b/src/tests/ecore_con/ecore_con_suite.h
@@ -6,5 +6,6 @@
 void ecore_con_test_ecore_con(TCase *tc);
 void ecore_con_test_ecore_con_url(TCase *tc);
 void ecore_con_test_ecore_con_eet(TCase *tc);
+void ecore_con_test_efl_net_ip_address(TCase *tc);
 
 #endif /* _ECORE_CON_SUITE_H */
diff --git a/src/tests/ecore_con/ecore_con_test_efl_net_ip_address.c 
b/src/tests/ecore_con/ecore_con_test_efl_net_ip_address.c
new file mode 100644
index 0000000..d46e31b
--- /dev/null
+++ b/src/tests/ecore_con/ecore_con_test_efl_net_ip_address.c
@@ -0,0 +1,1154 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#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
+
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#include "ecore_con_suite.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 void
+_timeout(void *data, const Efl_Event *event EINA_UNUSED)
+{
+   Eina_Bool *did = data;
+   *did = EINA_TRUE;
+   ck_abort_msg("timed out!");
+}
+
+#define LOOP_WITH_TIMEOUT(t) \
+  do \
+    { \
+       Eina_Bool _did_timeout = EINA_FALSE; \
+       Efl_Future *_timeout_future = efl_loop_timeout(ecore_main_loop_get(), 
t, &_did_timeout); \
+       efl_future_then(_timeout_future, _timeout, NULL, NULL, &_did_timeout); \
+       mark_point(); \
+       ecore_main_loop_begin(); \
+       if (!_did_timeout) efl_future_cancel(_timeout_future); \
+       else ck_abort_msg("Timed out!"); \
+    } \
+  while (0)
+
+
+struct resolve_ctx {
+   Eina_Array *results;
+   Eina_Stringshare *canonical_name;
+   Eina_Stringshare *request_address;
+   Efl_Future *future;
+   Eina_Error err;
+};
+
+static void
+_resolve_cleanup(struct resolve_ctx *ctx)
+{
+   Eina_Array_Iterator it;
+   unsigned int i;
+   const Efl_Net_Ip_Address *o;
+
+   mark_point();
+
+   if (ctx->results)
+     {
+        EINA_ARRAY_ITER_NEXT(ctx->results, i, o, it)
+          efl_unref(o);
+        eina_array_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)
+     {
+        efl_future_cancel(ctx->future);
+        ctx->future = NULL;
+     }
+}
+
+static Eina_Bool
+_resolve_found(const struct resolve_ctx *ctx, const char *string)
+{
+   Eina_Array_Iterator it;
+   unsigned int i;
+   const Efl_Net_Ip_Address *o;
+
+   mark_point();
+
+   EINA_ARRAY_ITER_NEXT(ctx->results, i, o, it)
+     {
+        if (strcmp(string, efl_net_ip_address_string_get(o)) == 0)
+          return EINA_TRUE;
+     }
+
+   return EINA_FALSE;
+}
+
+static void
+_resolve_done(void *data, const Efl_Event *event)
+{
+   struct resolve_ctx *ctx = data;
+   Efl_Future_Event_Success *f = event->info;
+   Efl_Net_Ip_Address_Resolve_Results *r = f->value;
+   Eina_Array_Iterator it;
+   unsigned int i;
+   Efl_Net_Ip_Address *o;
+
+   ck_assert_ptr_eq(ctx->results, NULL);
+   ctx->results = eina_array_new(32);
+
+   ctx->canonical_name = eina_stringshare_ref(r->canonical_name);
+   ctx->request_address = eina_stringshare_ref(r->request_address);
+
+   EINA_ARRAY_ITER_NEXT(r->results, i, o, it)
+     eina_array_push(ctx->results, efl_ref(o));
+
+   ctx->future = NULL;
+   ecore_main_loop_quit();
+
+   mark_point();
+}
+
+static void
+_resolve_failed(void *data, const Efl_Event *event)
+{
+   struct resolve_ctx *ctx = data;
+   Efl_Future_Event_Failure *f = event->info;
+
+   mark_point();
+
+   ctx->err = f->error;
+   ctx->future = NULL;
+   ecore_main_loop_quit();
+
+   mark_point();
+}
+
+static void
+_resolve(struct resolve_ctx *ctx, const char *address, int family, int flags)
+{
+   ctx->future = efl_net_ip_address_resolve(EFL_NET_IP_ADDRESS_CLASS,
+                                            address, family, flags);
+   ck_assert_ptr_ne(ctx->future, NULL);
+   efl_future_then(ctx->future, _resolve_done, _resolve_failed, NULL, ctx);
+
+   LOOP_WITH_TIMEOUT(5);
+}
+
+/* IPv4 *****************************************************************/
+
+static void
+_ipv4_check(Eo *o, const struct sockaddr_in *addr)
+{
+   Eina_Slice slice = { .mem = &addr->sin_addr, .len = sizeof(addr->sin_addr) 
};
+   const 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), ntohs(addr->sin_port));
+
+   rs = efl_net_ip_address_get(o);
+   ck_assert_ptr_ne(rs, NULL);
+   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", 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);
+}
+
+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;
+
+   ecore_con_init();
+
+   addr.sin_port = htons(12345);
+   addr.sin_addr.s_addr = htonl(0xabcdefafU);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET),
+               efl_net_ip_address_port_set(efl_added, ntohs(addr.sin_port)),
+               efl_net_ip_address_set(efl_added, &slice));
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   addr.sin_port = htons(8081);
+   addr.sin_addr.s_addr = htonl(0);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET),
+               efl_net_ip_address_port_set(efl_added, ntohs(addr.sin_port)),
+               efl_net_ip_address_set(efl_added, &slice));
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   addr.sin_port = htons(0);
+   addr.sin_addr.s_addr = htonl(0x12345678);
+   o = efl_add(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_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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;
+
+   ecore_con_init();
+
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: 
pd->addr.sa_family == 0 is true");
+   o = efl_add(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(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(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(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(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(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_del(o);
+   TRAP_ERRORS_FINISH(1);
+
+   addr.sin_port = htons(12345);
+   addr.sin_addr.s_addr = htonl(0xabcdefafU);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET),
+               efl_net_ip_address_port_set(efl_added, 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, 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: address == NULL");
+   efl_net_ip_address_set(o, NULL);
+   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 = htonl(0x12345678);
+   slice.len = sizeof(addr.sin_addr.s_addr);
+   efl_net_ip_address_set(o, &slice);
+   TRAP_ERRORS_FINISH(1);
+
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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;
+
+   ecore_con_init();
+
+   addr.sin_port = htons(12345);
+   addr.sin_addr.s_addr = htonl(0xabcdefafU);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin_port),
+                                 slice);
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   addr.sin_port = htons(8081);
+   addr.sin_addr.s_addr = htonl(0);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin_port),
+                                 slice);
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   addr.sin_port = htons(0);
+   addr.sin_addr.s_addr = htonl(0x12345678);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin_port),
+                                 slice);
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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;
+
+   ecore_con_init();
+
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: address.len != 4 
&& address.len != 16 is true");
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 1234,
+                                 wrong_slice);
+   ck_assert_ptr_eq(o, NULL);
+   TRAP_ERRORS_FINISH(1);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_create_sockaddr_ok)
+{
+   struct sockaddr_in addr = {
+     .sin_family = AF_INET,
+   };
+   Eo *o;
+
+   ecore_con_init();
+
+   addr.sin_port = htons(12345);
+   addr.sin_addr.s_addr = htonl(0xabcdefafU);
+   o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, &addr);
+   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   addr.sin_port = htons(0);
+   addr.sin_addr.s_addr = htonl(0);
+   o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, &addr);
+   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
+   _ipv4_check(o, &addr);
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_create_sockaddr_fail)
+{
+   struct sockaddr_in addr = {
+     .sin_family = 1234,
+   };
+   Eo *o;
+
+   ecore_con_init();
+
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: sockaddr == 
NULL");
+   o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, 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(EFL_NET_IP_ADDRESS_CLASS, &addr);
+   ck_assert_ptr_eq(o, NULL);
+   TRAP_ERRORS_FINISH(1);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_parse_ok)
+{
+   Eo *o;
+
+   ecore_con_init();
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "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_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "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_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "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_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "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_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_parse_fail)
+{
+   Eo *o;
+
+   ecore_con_init();
+
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: numeric_address 
== NULL");
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, 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(EFL_NET_IP_ADDRESS_CLASS, "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(EFL_NET_IP_ADDRESS_CLASS, "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(EFL_NET_IP_ADDRESS_CLASS, "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(EFL_NET_IP_ADDRESS_CLASS, "127.0.0.1:http");
+   ck_assert_ptr_eq(o, NULL);
+   TRAP_ERRORS_FINISH(0); /* no error messages! */
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_resolve_ok)
+{
+   struct resolve_ctx ctx = { };
+
+   ecore_con_init();
+
+   _resolve(&ctx, "localhost:http", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "127.0.0.1", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "127.0.0.1:http", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "127.0.0.1:80", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost:80", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost:http", AF_INET, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]:80"), EINA_FALSE);
+   _resolve_cleanup(&ctx);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv4_resolve_fail)
+{
+   struct resolve_ctx ctx = { };
+
+   ecore_con_init();
+
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, "safety check failed: address == NULL");
+   ctx.future = efl_net_ip_address_resolve(EFL_NET_IP_ADDRESS_CLASS,
+                                           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(EFL_NET_IP_ADDRESS_CLASS,
+                                           "localhost", 1234, 0);
+   ck_assert_ptr_eq(ctx.future, NULL);
+   TRAP_ERRORS_FINISH(1);
+
+   _resolve(&ctx, "xxlocalhost:xxhttp", 0, 0);
+   ck_assert_int_eq(ctx.err, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "[::1]:http", AF_INET, 0);
+   ck_assert_int_eq(ctx.err, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
+   _resolve_cleanup(&ctx);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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
+   };
+
+   ecore_con_init();
+
+   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 = htonl(itr->addr),
+        };
+        Eo *o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, 
&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_del(o);
+     }
+
+   ecore_con_shutdown();
+}
+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) };
+   const 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), ntohs(addr->sin6_port));
+
+   rs = efl_net_ip_address_get(o);
+   ck_assert_ptr_ne(rs, NULL);
+   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", 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);
+}
+
+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;
+
+   ecore_con_init();
+
+   addr.sin6_port = htons(12345);
+   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET6),
+               efl_net_ip_address_port_set(efl_added, ntohs(addr.sin6_port)),
+               efl_net_ip_address_set(efl_added, &slice));
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   addr.sin6_port = htons(8081);
+   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET6),
+               efl_net_ip_address_port_set(efl_added, ntohs(addr.sin6_port)),
+               efl_net_ip_address_set(efl_added, &slice));
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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;
+
+   ecore_con_init();
+
+   /* 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(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_del(o);
+   TRAP_ERRORS_FINISH(1);
+
+   addr.sin6_port = htons(12345);
+   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 1);
+   o = efl_add(EFL_NET_IP_ADDRESS_CLASS, NULL,
+               efl_net_ip_address_family_set(efl_added, AF_INET6),
+               efl_net_ip_address_port_set(efl_added, 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, 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_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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;
+
+   ecore_con_init();
+
+   addr.sin6_port = htons(12365);
+   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin6_port),
+                                 slice);
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   addr.sin6_port = htons(8081);
+   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin6_port),
+                                 slice);
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   addr.sin6_port = htons(0);
+   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 1);
+   o = efl_net_ip_address_create(EFL_NET_IP_ADDRESS_CLASS,
+                                 ntohs(addr.sin6_port),
+                                 slice);
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv6_create_sockaddr_ok)
+{
+   struct sockaddr_in6 addr = {
+     .sin6_family = AF_INET6,
+   };
+   Eo *o;
+
+   ecore_con_init();
+
+   addr.sin6_port = htons(12345);
+   _ipv6_set(&addr, 1, 2, 3, 4, 5, 6, 7, 8);
+   o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, &addr);
+   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   addr.sin6_port = htons(0);
+   _ipv6_set(&addr, 0, 0, 0, 0, 0, 0, 0, 0);
+   o = efl_net_ip_address_create_sockaddr(EFL_NET_IP_ADDRESS_CLASS, &addr);
+   ck_assert_ptr_ne(&addr, efl_net_ip_address_sockaddr_get(o));
+   _ipv6_check(o, &addr);
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv6_parse_ok)
+{
+   Eo *o;
+
+   ecore_con_init();
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::1]:12345");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]:12345");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::1]:0");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::1]");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "::1");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::1]");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::]:12345");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]:12345");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::]:0");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::]");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "::");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[::]");
+   efl_del(o);
+
+   /* IPv4 Mapped */
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, 
"[::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_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, 
"[::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_del(o);
+
+   /* IPv4 Compatible */
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, 
"[::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_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[::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_del(o);
+
+   /* Link Local */
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[fe80::1]:12345");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fe80::1]:12345");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[fe80::1]");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fe80::1]");
+   efl_del(o);
+
+   /* Site Local */
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[fc00::1]:12345");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fc00::1]:12345");
+   efl_del(o);
+
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "[fc00::1]");
+   ck_assert_ptr_ne(o, NULL);
+   ck_assert_str_eq(efl_net_ip_address_string_get(o), "[fc00::1]");
+   efl_del(o);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv6_parse_fail)
+{
+   Eo *o;
+
+   ecore_con_init();
+
+   /* generic error (null ptr) checked in ipv4_parse_fail */
+
+   /* incomplete numbers */
+   TRAP_ERRORS_BEGIN(eina_safety, ERR, NULL);
+   o = efl_net_ip_address_parse(EFL_NET_IP_ADDRESS_CLASS, "::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(EFL_NET_IP_ADDRESS_CLASS, "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(EFL_NET_IP_ADDRESS_CLASS, "[::1]:http");
+   ck_assert_ptr_eq(o, NULL);
+   TRAP_ERRORS_FINISH(0); /* no error messages! */
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv6_resolve_ok)
+{
+   struct resolve_ctx ctx = { };
+
+   ecore_con_init();
+
+   _resolve(&ctx, "localhost:http", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "::1", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+#if defined(AI_V4MAPPED) && (AI_V4MAPPED > 0)
+   _resolve(&ctx, "127.0.0.1", AF_INET6, AI_V4MAPPED);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::ffff:127.0.0.1]"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+#endif
+
+   _resolve(&ctx, "[::1]:http", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "[::1]:80", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost:80", 0, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "[::1]:80"), EINA_TRUE);
+   _resolve_cleanup(&ctx);
+
+   _resolve(&ctx, "localhost:http", AF_INET6, 0);
+   ck_assert_int_eq(ctx.err, 0);
+   ck_assert_int_eq(_resolve_found(&ctx, "127.0.0.1:80"), EINA_FALSE);
+   _resolve_cleanup(&ctx);
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+START_TEST(ecore_test_efl_net_ip_address_ipv6_resolve_fail)
+{
+   struct resolve_ctx ctx = { };
+
+   ecore_con_init();
+
+   /* 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 */
+   ck_assert_int_eq(ctx.err, EFL_NET_ERROR_COULDNT_RESOLVE_HOST);
+   _resolve_cleanup(&ctx);
+#endif
+
+   ecore_con_shutdown();
+}
+END_TEST
+
+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
+   };
+
+   ecore_con_init();
+
+   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(EFL_NET_IP_ADDRESS_CLASS, 
&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_del(o);
+     }
+
+   ecore_con_shutdown();
+}
+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);
+   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);
+}

-- 


Reply via email to