#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if_arp.h>
#include <ifaddrs.h>
#include <arpa/inet.h>

#include "dev.h"
#include "str.h"
#include "sock.h"
#include "die.h"
#include "link.h"
#include "built_in.h"

int __device_ifindex(const char *ifname)
{
	int ret, sock, index;
	struct ifreq ifr;

	if (!strncmp("any", ifname, strlen("any")))
		return 0;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ret = ioctl(sock, SIOCGIFINDEX, &ifr);
	if (ret)
		index = -1;
	else
		index = ifr.ifr_ifindex;

	close(sock);

	return index;
}

int device_ifindex(const char *ifname)
{
	int index = __device_ifindex(ifname);

	if (unlikely(index < 0))
		panic("Cannot get ifindex from device!\n");

	return index;
}

int device_type(const char *ifname)
{
	int ret, sock, type;
	struct ifreq ifr;

	if (!strncmp("any", ifname, strlen("any")))
		return ARPHRD_ETHER;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ret = ioctl(sock, SIOCGIFHWADDR, &ifr);
	if (unlikely(ret))
		panic("Cannot get iftype from device!\n");

	/* dev->type */
	type = ifr.ifr_hwaddr.sa_family;
	close(sock);

	return type;
}

static int __device_address6(const char *ifname, struct sockaddr_storage *ss)
{
	int ret, family, found = -EINVAL;
	struct ifaddrs *ifaddr, *ifa;

	ret = getifaddrs(&ifaddr);
	if (unlikely(ret < 0))
		panic("Cannot get device addresses for IPv6!\n");

	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
		family = ifa->ifa_addr->sa_family;
		if (family != AF_INET6)
			continue;
		if (strcmp(ifa->ifa_name, ifname))
			continue;

		memcpy(ss, ifa->ifa_addr, sizeof(*ss));
		found = 0;
		break;
	}

	freeifaddrs(ifaddr);
	return found;
}

int device_address(const char *ifname, int af, struct sockaddr_storage *ss)
{
	int ret, sock;
	struct ifreq ifr;

	if (unlikely(!ss))
		return -EINVAL;
	if (!strncmp("any", ifname, strlen("any")))
		return -EINVAL;
	if (af == AF_INET6)
		return __device_address6(ifname, ss);

	sock = af_socket(af);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ifr.ifr_addr.sa_family = af;

	ret = ioctl(sock, SIOCGIFADDR, &ifr);
	if (likely(!ret))
		memcpy(ss, &ifr.ifr_addr, sizeof(ifr.ifr_addr));

	close(sock);
	return ret;
}

int device_hw_address(const char *ifname, uint8_t *addr, size_t len)
{
	int ret, sock;
	struct ifreq ifr;

	if (!addr)
		return -EINVAL;
	if (len < IFHWADDRLEN)
		return -ENOSPC;
	if (!strncmp("any", ifname, strlen("any")))
		return -EINVAL;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ret = ioctl(sock, SIOCGIFHWADDR, &ifr);
	if (!ret)
		memcpy(addr, &ifr.ifr_hwaddr.sa_data[0], IFHWADDRLEN);

	close(sock);
	return ret;
}

size_t device_mtu(const char *ifname)
{
	size_t mtu = 0;
	int ret, sock;
	struct ifreq ifr;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ret = ioctl(sock, SIOCGIFMTU, &ifr);
	if (likely(!ret))
		mtu = ifr.ifr_mtu;

	close(sock);
	return mtu;
}

short device_get_flags(const char *ifname)
{
	short flags = 0;
	int ret, sock;
	struct ifreq ifr;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);

	ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
	if (likely(!ret))
		flags = ifr.ifr_flags;

	close(sock);
	return flags;
}

void device_set_flags(const char *ifname, const short flags)
{
	int ret, sock;
	struct ifreq ifr;

	sock = af_socket(AF_INET);

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	ifr.ifr_flags = flags;

	ret = ioctl(sock, SIOCSIFFLAGS, &ifr);
	if (unlikely(ret < 0))
		panic("Cannot set NIC flags (%s)!\n", strerror(errno));

	close(sock);
}

int device_up_and_running(const char *ifname)
{
	if (!ifname)
		return -EINVAL;
	if (!strncmp("any", ifname, strlen("any")))
		return 1;

	return (device_get_flags(ifname) &
		(IFF_UP | IFF_RUNNING)) ==
		(IFF_UP | IFF_RUNNING);
}

uint32_t device_bitrate(const char *ifname)
{
	uint32_t scopper;

	scopper = ethtool_bitrate(ifname);

	return scopper ? : wireless_bitrate(ifname);
}

short device_enter_promiscuous_mode(const char *ifname)
{
	short ifflags;

	if (!strncmp("any", ifname, strlen("any")))
		return 0;

	ifflags = device_get_flags(ifname);
	device_set_flags(ifname, ifflags | IFF_PROMISC);

	return ifflags;
}

void device_leave_promiscuous_mode(const char *ifname, short oldflags)
{
	if (!strncmp("any", ifname, strlen("any")))
		return;

	device_set_flags(ifname, oldflags);
}

const char *device_type2str(uint16_t type)
{
	switch (type) {
	case ARPHRD_ETHER:
		return "ether";
	case ARPHRD_EETHER:
		return "eether";
	case ARPHRD_AX25:
		return "ax25";
	case ARPHRD_PRONET:
		return "pronet";
	case ARPHRD_CHAOS:
		return "chaos";
	case ARPHRD_IEEE802:
		return "ieee802";
	case ARPHRD_ARCNET:
		return "arcnet";
	case ARPHRD_APPLETLK:
		return "appletlk";
	case ARPHRD_DLCI:
		return "dlci";
	case ARPHRD_ATM:
		return "atm";
	case ARPHRD_METRICOM:
		return "metricom";
	case ARPHRD_IEEE1394:
		return "ieee1394";
	case ARPHRD_INFINIBAND:
		return "infiniband";
	case ARPHRD_SLIP:
		return "slip";
	case ARPHRD_CSLIP:
		return "cslip";
	case ARPHRD_SLIP6:
		return "slip6";
	case ARPHRD_CSLIP6:
		return "cslip6";
	case ARPHRD_RSRVD:
		return "RSRVD";
	case ARPHRD_ADAPT:
		return "adapt";
	case ARPHRD_ROSE:
		return "rose";
	case ARPHRD_X25:
		return "x25";
	case ARPHRD_HWX25:
		return "hwx25";
	case ARPHRD_CAN:
		return "can";
	case ARPHRD_PPP:
		return "ppp";
	case ARPHRD_HDLC:
		return "hdlc";
	case ARPHRD_LAPB:
		return "lapb";
	case ARPHRD_DDCMP:
		return "ddcmp";
	case ARPHRD_RAWHDLC:
		return "rawhdlc";
	case ARPHRD_TUNNEL:
		return "tunnel";
	case ARPHRD_TUNNEL6:
		return "tunnel6";
	case ARPHRD_FRAD:
		return "frad";
	case ARPHRD_SKIP:
		return "skip";
	case ARPHRD_LOOPBACK:
		return "loopback";
	case ARPHRD_LOCALTLK:
		return "localtlk";
	case ARPHRD_FDDI:
		return "fddi";
	case ARPHRD_BIF:
		return "bif";
	case ARPHRD_SIT:
		return "sit";
	case ARPHRD_IPDDP:
		return "ipddp";
	case ARPHRD_IPGRE:
		return "ipgre";
	case ARPHRD_PIMREG:
		return "pimreg";
	case ARPHRD_HIPPI:
		return "hippi";
	case ARPHRD_ASH:
		return "ash";
	case ARPHRD_ECONET:
		return "econet";
	case ARPHRD_IRDA:
		return "irda";
	case ARPHRD_FCPP:
		return "fcpp";
	case ARPHRD_FCAL:
		return "fcal";
	case ARPHRD_FCPL:
		return "fcpl";
	case ARPHRD_FCFABRIC:
		return "fcfb0";
	case ARPHRD_FCFABRIC + 1:
		return "fcfb1";
	case ARPHRD_FCFABRIC + 2:
		return "fcfb2";
	case ARPHRD_FCFABRIC + 3:
		return "fcfb3";
	case ARPHRD_FCFABRIC + 4:
		return "fcfb4";
	case ARPHRD_FCFABRIC + 5:
		return "fcfb5";
	case ARPHRD_FCFABRIC + 6:
		return "fcfb6";
	case ARPHRD_FCFABRIC + 7:
		return "fcfb7";
	case ARPHRD_FCFABRIC + 8:
		return "fcfb8";
	case ARPHRD_FCFABRIC + 9:
		return "fcfb9";
	case ARPHRD_FCFABRIC + 10:
		return "fcfb10";
	case ARPHRD_FCFABRIC + 11:
		return "fcfb11";
	case ARPHRD_FCFABRIC + 12:
		return "fcfb12";
	case ARPHRD_IEEE802_TR:
		return "ieee802_tr";
	case ARPHRD_IEEE80211:
		return "ieee80211";
	case ARPHRD_IEEE80211_PRISM:
		return "ieee80211_prism";
	case ARPHRD_IEEE80211_RADIOTAP:
		return "ieee80211_radiotap";
	case ARPHRD_IEEE802154:
		return "ieee802154";
	case ARPHRD_PHONET:
		return "phonet";
	case ARPHRD_PHONET_PIPE:
		return "phonet_pipe";
	case ARPHRD_CAIF:
		return "caif";
	case ARPHRD_IP6GRE:
		return "ip6gre";
	case ARPHRD_NETLINK:
		return "netlink";
	case ARPHRD_NONE:
		return "none";
	case ARPHRD_VOID:
		return "void";
	default:
		return "Unknown";
	}
}

/* Taken from iproute2 ll_addr_n2a func */
const char *device_addr2str(const unsigned char *addr, int alen, int type,
			    char *buf, int blen)
{
	int i, l;

	if (alen == 4 &&
	    (type == ARPHRD_TUNNEL || type == ARPHRD_SIT ||
	     type == ARPHRD_IPGRE))
		return inet_ntop(AF_INET, addr, buf, blen);

	if (alen == 16 && type == ARPHRD_TUNNEL6)
		return inet_ntop(AF_INET6, addr, buf, blen);

	snprintf(buf, blen, "%02x", addr[0]);
	for (i = 1, l = 2; i < alen && l < blen; i++, l += 3)
		snprintf(buf + l, blen - l, ":%02x", addr[i]);

	return buf;
}
