diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am index 10285c6f0c..3c62337d2f 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 88fc2bd66a..eeaf839ff3 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 5ac7a1df33..f0f4557b14 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 0000000000..8c817c44b7 --- /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 +#include +#include +#include +#include + +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 089c88cbea..a5ed29d739 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 0000000000..0109748cb8 --- /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 +#endif + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#ifdef HAVE_EVIL +# include +#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 0000000000..9398c29472 --- /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; [[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 .]] + } + 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; [[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 -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 a3ef616064..21585ae93e 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 5d18647696..133b68ab2d 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 0000000000..d46e31bc4e --- /dev/null +++ b/src/tests/ecore_con/ecore_con_test_efl_net_ip_address.c @@ -0,0 +1,1154 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include + +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#ifdef HAVE_EVIL +# include +#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); +}