/**
 * @file net/if.c  Network interface code
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <re_types.h>
#include <re_fmt.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_net.h>


#define DEBUG_MODULE "netif"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


/** Interface address entry */
struct ifentry {
	int af;        /**< Address family */
	char *ifname;  /**< Interface name */
	struct sa *ip; /**< IP address     */
	size_t sz;     /**< Size of buffer */
	bool found;    /**< Found flag     */
};


static bool if_getname_handler(const char *ifname, const struct sa *sa,
			       void *arg)
{
	struct ifentry *ife = arg;

	if (ife->af != sa_af(sa))
		return false;

	if (sa_cmp(sa, ife->ip, SA_ADDR)) {
		str_ncpy(ife->ifname, ifname, ife->sz);
		ife->found = true;
		return true;
	}

	return false;
}


/**
 * Get the name of the interface for a given IP address
 *
 * @param ifname Buffer for returned network interface name
 * @param sz     Size of buffer
 * @param af     Address Family
 * @param ip     Given IP address
 *
 * @return 0 if success, otherwise errorcode
 */
int net_if_getname(char *ifname, size_t sz, int af, const struct sa *ip)
{
	struct ifentry ife;
	int err;

	if (!ifname || !sz || !ip)
		return EINVAL;

	ife.af     = af;
	ife.ifname = ifname;
	ife.ip     = (struct sa *)ip;
	ife.sz     = sz;
	ife.found  = false;

	err = net_if_list(if_getname_handler, &ife);

	return ife.found ? err : ENODEV;
}


static bool if_getaddr_handler(const char *ifname,
			       const struct sa *sa, void *arg)
{
	struct ifentry *ife = arg;

	/* Match name of interface? */
	if (str_isset(ife->ifname) && 0 != str_casecmp(ife->ifname, ifname))
		return false;

	if (!sa_isset(sa, SA_ADDR))
		return false;

#if 1
	/* skip loopback and link-local IP */
	if (sa_is_loopback(sa) || sa_is_linklocal(sa))
		return false;
#endif

	/* Match address family */
	if (ife->af != sa_af(sa))
		return false;

	/* Match - copy address */
	sa_cpy(ife->ip, sa);
	ife->found = true;

	return ife->found;
}


/**
 * Get IP address for a given network interface
 *
 * @param ifname  Network interface name (optional)
 * @param af      Address Family
 * @param ip      Returned IP address
 *
 * @return 0 if success, otherwise errorcode
 *
 * @deprecated Works for IPv4 only
 */
int net_if_getaddr(const char *ifname, int af, struct sa *ip)
{
	struct ifentry ife;
	int err;

	if (!ip)
		return EINVAL;

	ife.af     = af;
	ife.ifname = (char *)ifname;
	ife.ip     = ip;
	ife.sz     = 0;
	ife.found  = false;

#ifdef HAVE_GETIFADDRS
	err = net_getifaddrs(if_getaddr_handler, &ife);
#else
	err = net_if_list(if_getaddr_handler, &ife);
#endif

	return ife.found ? err : ENODEV;
}


static bool if_debug_handler(const char *ifname, const struct sa *sa,
			     void *arg)
{
	struct re_printf *pf = arg;

	(void)re_hprintf(pf, " %10s:  %j\n", ifname, sa);

	return false;
}


/**
 * Debug network interfaces
 *
 * @param pf     Print handler for debug output
 * @param unused Unused parameter
 *
 * @return 0 if success, otherwise errorcode
 */
int net_if_debug(struct re_printf *pf, void *unused)
{
	int err;

	(void)unused;

	err = re_hprintf(pf, "net interfaces:\n");

#ifdef HAVE_GETIFADDRS
	err |= net_getifaddrs(if_debug_handler, pf);
#else
	err |= net_if_list(if_debug_handler, pf);
#endif

	return err;
}


static bool linklocal_handler(const char *ifname, const struct sa *sa,
			      void *arg)
{
	void **argv = arg;
	int af = *(int *)argv[1];

	if (argv[0] && 0 != str_casecmp(argv[0], ifname))
		return false;

	if (af != AF_UNSPEC && af != sa_af(sa))
		return false;

	if (sa_is_linklocal(sa)) {
		*((struct sa *)argv[2]) = *sa;
		return true;
	}

	return false;
}


/**
 * Get the Link-local address for a specific network interface
 *
 * @param ifname Name of the interface
 * @param af     Address family
 * @param ip     Returned link-local address
 *
 * @return 0 if success, otherwise errorcode
 */
int net_if_getlinklocal(const char *ifname, int af, struct sa *ip)
{
	struct sa addr;
	void *argv[3];
	int err;

	if (!ip)
		return EINVAL;

	sa_init(&addr, sa_af(ip));

	argv[0] = (void *)ifname;
	argv[1] = &af;
	argv[2] = &addr;

	err = net_if_apply(linklocal_handler, argv);
	if (err)
		return err;

	if (!sa_isset(&addr, SA_ADDR))
		return ENOENT;

	*ip = addr;

	return 0;
}
