/**
 * \file
 * Modern posix networking code
 *
 * Author:
 *	Rodrigo Kumpera (kumpera@gmail.com)
 *
 * (C) 2015 Xamarin
 */

#include <config.h>
#include <glib.h>

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_GETIFADDRS
#include <ifaddrs.h>
#endif
#ifdef HAVE_QP2GETIFADDRS
/* Bizarrely, IBM i implements this, but AIX doesn't, so on i, it has a different name... */
#include <as400_types.h>
#include <as400_protos.h>
/* Defines to just reuse ifaddrs code */
#define ifaddrs ifaddrs_pase
#define freeifaddrs Qp2freeifaddrs
#define getifaddrs Qp2getifaddrs
#endif

#include <mono/utils/networking.h>
#include <mono/utils/mono-threads-coop.h>

#if HAVE_SIOCGIFCONF || HAVE_GETIFADDRS

static void*
get_address_from_sockaddr (struct sockaddr *sa)
{
	switch (sa->sa_family) {
	case AF_INET:
		return &((struct sockaddr_in*)sa)->sin_addr;
#ifdef HAVE_STRUCT_SOCKADDR_IN6
	case AF_INET6:
		return &((struct sockaddr_in6*)sa)->sin6_addr;
#endif
	}
	return NULL;
}

#endif

#ifdef HAVE_GETADDRINFO

int
mono_get_address_info (const char *hostname, int port, int flags, MonoAddressInfo **result)
{
	char service_name [16];
	struct addrinfo hints, *res = NULL, *info;
	MonoAddressEntry *cur = NULL, *prev = NULL;
	MonoAddressInfo *addr_info;
	int ret;

	memset (&hints, 0, sizeof (struct addrinfo));
	*result = NULL;

	hints.ai_family = PF_UNSPEC;
	if (flags & MONO_HINT_IPV4)
		hints.ai_family = PF_INET;
	else if (flags & MONO_HINT_IPV6)
		hints.ai_family = PF_INET6;

	hints.ai_socktype = SOCK_STREAM;

	if (flags & MONO_HINT_CANONICAL_NAME)
		hints.ai_flags = AI_CANONNAME;
	if (flags & MONO_HINT_NUMERIC_HOST)
		hints.ai_flags |= AI_NUMERICHOST;

/* Some ancient libc don't define AI_ADDRCONFIG */
#ifdef AI_ADDRCONFIG
	if (flags & MONO_HINT_CONFIGURED_ONLY)
		hints.ai_flags |= AI_ADDRCONFIG;
#endif
	sprintf (service_name, "%d", port);

	MONO_ENTER_GC_SAFE;
	ret = getaddrinfo (hostname, service_name, &hints, &info);
	MONO_EXIT_GC_SAFE;

	if (ret)
		return 1; /* FIXME propagate the error */

	res = info;
	*result = addr_info = g_new0 (MonoAddressInfo, 1);

	while (res) {
		cur = g_new0 (MonoAddressEntry, 1);
		cur->family = res->ai_family;
		cur->socktype = res->ai_socktype;
		cur->protocol = res->ai_protocol;
		if (cur->family == PF_INET) {
			cur->address_len = sizeof (struct in_addr);
			cur->address.v4 = ((struct sockaddr_in*)res->ai_addr)->sin_addr;
#ifdef HAVE_STRUCT_SOCKADDR_IN6			
		} else if (cur->family == PF_INET6) {
			cur->address_len = sizeof (struct in6_addr);
			cur->address.v6 = ((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
#endif
		} else {
			g_warning ("Cannot handle address family %d", cur->family);
			res = res->ai_next;
			g_free (cur);
			continue;
		}

		if (res->ai_canonname)
			cur->canonical_name = g_strdup (res->ai_canonname);

		if (prev)
			prev->next = cur;
		else
			addr_info->entries = cur;
			
		prev = cur;
		res = res->ai_next;
	}

	freeaddrinfo (info);
	return 0;
}

#endif

#if defined(__linux__) && defined(HAVE_GETPROTOBYNAME_R)

static int
fetch_protocol (const char *proto_name, int *cache, int *proto, int default_val)
{
	if (!*cache) {
		struct protoent protoent_buf = { 0 };
		struct protoent *pent = NULL;
		char buf[1024];

		getprotobyname_r (proto_name, &protoent_buf, buf, 1024, &pent);
		*proto = pent ? pent->p_proto : default_val;
		*cache = 1;
	}
	return *proto;
}

#elif HAVE_GETPROTOBYNAME

static int
fetch_protocol (const char *proto_name, int *cache, int *proto, int default_val)
{
	if (!*cache) {
		struct protoent *pent;

		pent = getprotobyname (proto_name);
		*proto = pent ? pent->p_proto : default_val;
		*cache = 1;
	}
	return *proto;
}

#endif

int
mono_networking_get_tcp_protocol (void)
{
	static int cache, proto;
	return fetch_protocol ("tcp", &cache, &proto, 6); //6 is SOL_TCP on linux
}

int
mono_networking_get_ip_protocol (void)
{
	static int cache, proto;
	return fetch_protocol ("ip", &cache, &proto, 0); //0 is SOL_IP on linux
}

int
mono_networking_get_ipv6_protocol (void)
{
	static int cache, proto;
	return fetch_protocol ("ipv6", &cache, &proto, 41); //41 is SOL_IPV6 on linux
}

#if defined (HAVE_SIOCGIFCONF)

#define IFCONF_BUFF_SIZE 1024
#ifndef _SIZEOF_ADDR_IFREQ
#define _SIZEOF_ADDR_IFREQ(ifr) (sizeof (struct ifreq))
#endif

#define FOREACH_IFR(IFR, IFC) \
	for (IFR = (IFC).ifc_req;	\
	ifr < (struct ifreq*)((char*)(IFC).ifc_req + (IFC).ifc_len); \
	ifr = (struct ifreq*)((char*)(IFR) + _SIZEOF_ADDR_IFREQ (*(IFR))))

void *
mono_get_local_interfaces (int family, int *interface_count)
{
	int fd;
	struct ifconf ifc;
	struct ifreq *ifr;
	int if_count = 0;
	gboolean ignore_loopback = FALSE;
	void *result = NULL;
	char *result_ptr;

	*interface_count = 0;

	if (!mono_address_size_for_family (family))
		return NULL;

	fd = socket (family, SOCK_STREAM, 0);
	if (fd == -1)
		return NULL;

	memset (&ifc, 0, sizeof (ifc));
	ifc.ifc_len = IFCONF_BUFF_SIZE;
	ifc.ifc_buf = (char *)g_malloc (IFCONF_BUFF_SIZE); /* We can't have such huge buffers on the stack. */
	if (ioctl (fd, SIOCGIFCONF, &ifc) < 0)
		goto done;

	FOREACH_IFR (ifr, ifc) {
		struct ifreq iflags;

		//only return addresses of the same type as @family
		if (ifr->ifr_addr.sa_family != family) {
			ifr->ifr_name [0] = '\0';
			continue;
		}

		strcpy (iflags.ifr_name, ifr->ifr_name);

		//ignore interfaces we can't get props for
		if (ioctl (fd, SIOCGIFFLAGS, &iflags) < 0) {
			ifr->ifr_name [0] = '\0';
			continue;
		}

		//ignore interfaces that are down
		if ((iflags.ifr_flags & IFF_UP) == 0) {
			ifr->ifr_name [0] = '\0';
			continue;
		}

		//If we have a non-loopback iface, don't return any loopback
		if ((iflags.ifr_flags & IFF_LOOPBACK) == 0) {
			ignore_loopback = TRUE;
			ifr->ifr_name [0] = 1;//1 means non-loopback
		} else {
			ifr->ifr_name [0] = 2; //2 means loopback
		}
		++if_count;
	}

	result = (char *)g_malloc (if_count * mono_address_size_for_family (family));
	result_ptr = (char *)result;
	FOREACH_IFR (ifr, ifc) {
		if (ifr->ifr_name [0] == '\0')
			continue;

		if (ignore_loopback && ifr->ifr_name [0] == 2) {
			--if_count;
			continue;
		}

		memcpy (result_ptr, get_address_from_sockaddr (&ifr->ifr_addr), mono_address_size_for_family (family));
		result_ptr += mono_address_size_for_family (family);
	}
	g_assert (result_ptr <= (char*)result + if_count * mono_address_size_for_family (family));

done:
	*interface_count = if_count;
	g_free (ifc.ifc_buf);
	close (fd);
	return result;
}

#elif defined(HAVE_GETIFADDRS) || defined(HAVE_QP2GETIFADDRS)

void *
mono_get_local_interfaces (int family, int *interface_count)
{
	struct ifaddrs *ifap = NULL, *cur;
	int if_count = 0;
	gboolean ignore_loopback = FALSE;
	void *result;
	char *result_ptr;

	*interface_count = 0;

	if (!mono_address_size_for_family (family))
		return NULL;

	if (getifaddrs (&ifap))
		return NULL;

	for (cur = ifap; cur; cur = cur->ifa_next) {
		//ignore interfaces with no address assigned
		if (!cur->ifa_addr)
			continue;

		//ignore interfaces that don't belong to @family
		if (cur->ifa_addr->sa_family != family)
			continue;

		//ignore interfaces that are down
		if ((cur->ifa_flags & IFF_UP) == 0)
			continue;

		//If we have a non-loopback iface, don't return any loopback
		if ((cur->ifa_flags & IFF_LOOPBACK) == 0)
			ignore_loopback = TRUE;

		if_count++;
	}

	result_ptr = result = g_malloc (if_count * mono_address_size_for_family (family));
	for (cur = ifap; cur; cur = cur->ifa_next) {
		if (!cur->ifa_addr)
			continue;
		if (cur->ifa_addr->sa_family != family)
			continue;
		if ((cur->ifa_flags & IFF_UP) == 0)
			continue;

		//we decrement if_count because it did not on the previous loop.
		if (ignore_loopback && (cur->ifa_flags & IFF_LOOPBACK)) {
			--if_count;
			continue;
		}

		memcpy (result_ptr, get_address_from_sockaddr (cur->ifa_addr), mono_address_size_for_family (family));
		result_ptr += mono_address_size_for_family (family);
	}
	g_assert (result_ptr <= (char*)result + if_count * mono_address_size_for_family (family));

	freeifaddrs (ifap);
	*interface_count = if_count;
	return result;
}

#endif

#ifdef HAVE_GETNAMEINFO

gboolean
mono_networking_addr_to_str (MonoAddress *address, char *buffer, socklen_t buflen)
{
	MonoSocketAddress saddr;
	socklen_t len;
	mono_socket_address_init (&saddr, &len, address->family, &address->addr, 0);

	return getnameinfo (&saddr.addr, len, buffer, buflen, NULL, 0, NI_NUMERICHOST) == 0;
}

#elif HAVE_INET_NTOP

gboolean
mono_networking_addr_to_str (MonoAddress *address, char *buffer, socklen_t buflen)
{
	return inet_ntop (address->family, &address->addr, buffer, buflen) != NULL;
}

#endif

#ifndef _WIN32
// These are already defined in networking-windows.c for Windows
void
mono_networking_init (void)
{
	//nothing really
}

void
mono_networking_shutdown (void)
{
	//nothing really
}
#endif
