/*
  *  Copyright (c) 2007, 2008, Andrea Bittau <a.bittau@cs.ucl.ac.uk>
  *
  *  OS dependent API for NetBSD.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <net/bpf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_media.h>
#include <sys/ioctl.h>
#include <net/if_dl.h>
#include <net80211/ieee80211.h>
#include <net80211/ieee80211_crypto.h>
#include <net80211/ieee80211_ioctl.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_proto.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>
#include <assert.h>
#include <ifaddrs.h>

#include "osdep.h"

#ifndef LINKTYPE_IEEE802_11
#define LINKTYPE_IEEE802_11 105
#endif

#ifndef IEEE80211_RADIOTAP_F_FCS
#define IEEE80211_RADIOTAP_F_FCS 0x10 /* Frame includes FCS */
#endif

#ifndef IEEE80211_IOC_CHANNEL
#define IEEE80211_IOC_CHANNEL 0
#endif

#ifndef le32toh
#define le32toh(x) htole32(x)
#endif

struct priv_nbsd
{
	/* iface */
	int pn_fd;

	/* rx */
	int pn_nocrc;

	/* tx */
	unsigned char pn_buf[4096];
	unsigned char * pn_next;
	int pn_totlen;

	/* setchan */
	int pn_s;
	struct ifreq pn_ifr;
	struct ieee80211chanreq pn_ireq;
	int pn_chan;
};

static void get_radiotap_info(struct priv_nbsd * pn,
							  struct ieee80211_radiotap_header * rth,
							  int * plen,
							  struct rx_info * ri)
{
	uint32_t present;
	uint8_t rflags = 0;
	int i;
	unsigned char * body = (unsigned char *) (rth + 1);
	int dbm_power = 0, db_power = 0;

	/* reset control info */
	if (ri) memset(ri, 0, sizeof(*ri));

	/* get info */
	present = le32toh(rth->it_present);
	for (i = IEEE80211_RADIOTAP_TSFT; i <= IEEE80211_RADIOTAP_EXT; i++)
	{
		if (!(present & (1 << i))) continue;

		switch (i)
		{
			case IEEE80211_RADIOTAP_TSFT:
				body += sizeof(uint64_t);
				break;

			case IEEE80211_RADIOTAP_FLAGS:
				rflags = *((uint8_t *) body);
			/* fall through */
			case IEEE80211_RADIOTAP_RATE:
				body += sizeof(uint8_t);
				break;

			case IEEE80211_RADIOTAP_CHANNEL:
				if (ri)
				{
					ri->ri_channel = 1;
				}
				body += sizeof(uint16_t) * 2;
				break;

			case IEEE80211_RADIOTAP_FHSS:
				body += sizeof(uint16_t);
				break;

			case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
				dbm_power = *body++;
				break;

			case IEEE80211_RADIOTAP_DBM_ANTNOISE:
				dbm_power -= *body++;
				break;

			case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
				db_power = *body++;
				break;

			case IEEE80211_RADIOTAP_DB_ANTNOISE:
				db_power -= *body++;
				break;

			default:
				i = IEEE80211_RADIOTAP_EXT + 1;
				break;
		}
	}

	/* set power */
	if (ri)
	{
		if (dbm_power)
			ri->ri_power = dbm_power;
		else
			ri->ri_power = db_power;
	}

	/* XXX cache; drivers won't change this per-packet */
	/* check if FCS/CRC is included in packet */
	if (pn->pn_nocrc || (rflags & IEEE80211_RADIOTAP_F_FCS))
	{
		*plen -= IEEE80211_CRC_LEN;
		pn->pn_nocrc = 1;
	}
}

static unsigned char *
get_80211(struct priv_nbsd * pn, int * plen, struct rx_info * ri)
{
	struct bpf_hdr * bpfh;
	struct ieee80211_radiotap_header * rth;
	void * ptr;
	unsigned char ** data;
	int * totlen;

	data = &pn->pn_next;
	totlen = &pn->pn_totlen;
	assert(*totlen);

	/* bpf hdr */
	bpfh = (struct bpf_hdr *) (*data);
	assert(bpfh->bh_caplen == bpfh->bh_datalen); /* XXX */
	*totlen -= bpfh->bh_hdrlen;

	/* check if more packets */
	if ((int) bpfh->bh_caplen < *totlen)
	{
		int tot = bpfh->bh_hdrlen + bpfh->bh_caplen;
		int offset = BPF_WORDALIGN(tot);

		*data = (unsigned char *) bpfh + offset;
		*totlen -= offset - tot; /* take into account align bytes */
	}
	else if ((int) bpfh->bh_caplen > *totlen)
		abort();

	*plen = bpfh->bh_caplen;
	*totlen -= bpfh->bh_caplen;
	assert(*totlen >= 0);

	/* radiotap */
	rth = (struct ieee80211_radiotap_header *) ((char *) bpfh
												+ bpfh->bh_hdrlen);
	get_radiotap_info(pn, rth, plen, ri);
	*plen -= rth->it_len;
	assert(*plen > 0);

	/* data */
	ptr = (char *) rth + rth->it_len;

	return ptr;
}

static int nbsd_get_channel(struct wif * wi)
{
	struct priv_nbsd * pn = wi_priv(wi);
	struct ieee80211chanreq channel;

	memset(&channel, 0, sizeof(channel));
	strlcpy(channel.i_name, wi_get_ifname(wi), sizeof(channel.i_name));

	if (ioctl(pn->pn_s, SIOCG80211CHANNEL, (caddr_t) &channel) < 0) return -1;

	return channel.i_channel;
}

static int nbsd_set_channel(struct wif * wi, int chan)
{
	struct priv_nbsd * pn = wi_priv(wi);
	struct ieee80211chanreq channel;

	memset(&channel, 0, sizeof(channel));
	strlcpy(channel.i_name, wi_get_ifname(wi), sizeof(channel.i_name));
	channel.i_channel = chan;
	if (ioctl(pn->pn_s, SIOCS80211CHANNEL, (caddr_t) &channel) < 0) return -1;
	pn->pn_chan = chan;

	return 0;
}

static int nbsd_read(struct wif * wi,
					 struct timespec * ts,
					 int * dlt,
					 unsigned char * h80211,
					 int len,
					 struct rx_info * ri)
{
	struct priv_nbsd * pn = wi_priv(wi);
	unsigned char * wh;
	int plen;

	assert(len > 0);

	/* need to read more */
	if (pn->pn_totlen == 0)
	{
		pn->pn_totlen = read(pn->pn_fd, pn->pn_buf, sizeof(pn->pn_buf));
		if (pn->pn_totlen == -1)
		{
			pn->pn_totlen = 0;
			return -1;
		}
		pn->pn_next = pn->pn_buf;
	}

	/* read 802.11 packet */
	wh = get_80211(pn, &plen, ri);
	if (plen > len) plen = len;
	assert(plen > 0);
	memcpy(h80211, wh, plen);

	if (dlt)
	{
		*dlt = LINKTYPE_IEEE802_11;
	}

	if (ts)
	{
		clock_gettime(CLOCK_REALTIME, ts);
	}

	if (ri && !ri->ri_channel) ri->ri_channel = wi_get_channel(wi);

	return plen;
}

static int nbsd_write(struct wif * wi,
					  struct timespec * ts,
					  int dlt,
					  unsigned char * h80211,
					  int len,
					  struct tx_info * ti)
{
	struct priv_nbsd * pn = wi_priv(wi);
	int rc;

	(void) ts;
	(void) dlt;

	/* XXX make use of ti */
	if (ti)
	{
	}

	rc = write(pn->pn_fd, h80211, len);
	if (rc == -1) return rc;

	return 0;
}

static void do_free(struct wif * wi)
{
	assert(wi->wi_priv);
	free(wi->wi_priv);
	wi->wi_priv = 0;
	free(wi);
}

static void nbsd_close(struct wif * wi)
{
	struct priv_nbsd * pn = wi_priv(wi);

	close(pn->pn_fd);
	close(pn->pn_s);
	do_free(wi);
}

static int do_nbsd_open(struct wif * wi, char * iface)
{
	int i;
	char buf[64];
	int fd = -1;
	struct ifreq ifr;
	unsigned int dlt = DLT_IEEE802_11_RADIO;
	int s;
	unsigned int flags;
	struct ifmediareq ifmr;
	int * mwords;
	struct priv_nbsd * pn = wi_priv(wi);
	unsigned int size = sizeof(pn->pn_buf);

	/* basic sanity check */
	if (strlen(iface) >= sizeof(ifr.ifr_name)) return -1;

	/* open wifi */
	s = socket(PF_INET, SOCK_DGRAM, 0);
	if (s == -1) return -1;
	pn->pn_s = s;

	/* set iface up and promisc */
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, iface, IFNAMSIZ);
	if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) goto close_sock;

	flags = ifr.ifr_flags;
	flags |= IFF_UP | IFF_PROMISC;
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, iface, IFNAMSIZ);
	ifr.ifr_flags = flags & 0xffff;
	if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) goto close_sock;

	/* monitor mode */
	memset(&ifmr, 0, sizeof(ifmr));
	strncpy(ifmr.ifm_name, iface, IFNAMSIZ);
	if (ioctl(s, SIOCGIFMEDIA, &ifmr) == -1) goto close_sock;

	assert(ifmr.ifm_count != 0);

	mwords = (int *) malloc(ifmr.ifm_count * sizeof(int));
	if (!mwords) goto close_sock;
	ifmr.ifm_ulist = mwords;
	if (ioctl(s, SIOCGIFMEDIA, &ifmr) == -1)
	{
		free(mwords);
		goto close_sock;
	}
	free(mwords);

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, iface, IFNAMSIZ);
	ifr.ifr_media = ifmr.ifm_current | IFM_IEEE80211_MONITOR;
	if (ioctl(s, SIOCSIFMEDIA, &ifr) == -1) goto close_sock;

	/* setup ifreq for chan that may be used in future */
	strncpy(pn->pn_ireq.i_name, iface, IFNAMSIZ);

	/* same for ifreq [mac addr] */
	strncpy(pn->pn_ifr.ifr_name, iface, IFNAMSIZ);

	/* open bpf */
	for (i = 0; i < 256; i++)
	{
		snprintf(buf, sizeof(buf), "/dev/bpf%d", i);

		fd = open(buf, O_RDWR);
		if (fd < 0)
		{
			if (errno != EBUSY) return -1;
			continue;
		}
		else
			break;
	}

	if (fd < 0) goto close_sock;

	if (ioctl(fd, BIOCSBLEN, &size) < 0) goto close_bpf;

	strncpy(ifr.ifr_name, iface, IFNAMSIZ);

	if (ioctl(fd, BIOCSETIF, &ifr) < 0) goto close_bpf;

	if (ioctl(fd, BIOCSDLT, &dlt) < 0) goto close_bpf;

	if (ioctl(fd, BIOCPROMISC, NULL) < 0) goto close_bpf;

	dlt = 1;
	if (ioctl(fd, BIOCIMMEDIATE, &dlt) == -1) goto close_bpf;

	return fd;

close_sock:
	close(s);
	return -1;
close_bpf:
	close(fd);
	goto close_sock;
}

static int nbsd_fd(struct wif * wi)
{
	struct priv_nbsd * pn = wi_priv(wi);

	return pn->pn_fd;
}

static int nbsd_get_mac(struct wif * wi, unsigned char * mac)
{
	struct ifaddrs *ifa, *p;
	char * name = wi_get_ifname(wi);
	int rc = -1;
	struct sockaddr_dl * sdp;

	if (getifaddrs(&ifa) == -1) return -1;

	p = ifa;
	while (p)
	{
		if (p->ifa_addr->sa_family == AF_LINK && strcmp(name, p->ifa_name) == 0)
		{

			sdp = (struct sockaddr_dl *) p->ifa_addr;
			memcpy(mac, sdp->sdl_data + sdp->sdl_nlen, 6);
			rc = 0;
			break;
		}

		p = p->ifa_next;
	}
	freeifaddrs(ifa);

	return rc;
}

static int nbsd_get_monitor(struct wif * wi)
{
	if (wi)
	{
	} /* XXX unused */

	/* XXX */
	return 0;
}

static int nbsd_get_rate(struct wif * wi)
{
	if (wi)
	{
	} /* XXX unused */

	/* XXX */
	return 1000000;
}

static int nbsd_set_rate(struct wif * wi, int rate)
{
	if (wi || rate)
	{
	} /* XXX unused */

	/* XXX */
	return 0;
}

static int nbsd_set_mac(struct wif * wi, unsigned char * mac)
{
	struct priv_nbsd * pn = wi_priv(wi);
	struct ifreq * ifr = &pn->pn_ifr;

	ifr->ifr_addr.sa_family = AF_LINK;
	ifr->ifr_addr.sa_len = 6;
	memcpy(ifr->ifr_addr.sa_data, mac, 6);

	return ioctl(pn->pn_s, SIOCSIFADDR, ifr);
}

static struct wif * nbsd_open(char * iface)
{
	struct wif * wi;
	struct priv_nbsd * pn;
	int fd;

	/* setup wi struct */
	wi = wi_alloc(sizeof(*pn));
	if (!wi) return NULL;
	wi->wi_read = nbsd_read;
	wi->wi_write = nbsd_write;
	wi->wi_set_channel = nbsd_set_channel;
	wi->wi_get_channel = nbsd_get_channel;
	wi->wi_close = nbsd_close;
	wi->wi_fd = nbsd_fd;
	wi->wi_get_mac = nbsd_get_mac;
	wi->wi_set_mac = nbsd_set_mac;
	wi->wi_get_rate = nbsd_get_rate;
	wi->wi_set_rate = nbsd_set_rate;
	wi->wi_get_monitor = nbsd_get_monitor;

	/* setup iface */
	fd = do_nbsd_open(wi, iface);
	if (fd == -1)
	{
		do_free(wi);
		return NULL;
	}

	/* setup private state */
	pn = wi_priv(wi);
	pn->pn_fd = fd;

	return wi;
}

struct wif * wi_open_osdep(char * iface) { return nbsd_open(iface); }

EXPORT int get_battery_state(void)
{
#if defined(__FreeBSD__)
	int value;
	size_t len;

	len = 1;
	value = 0;
	sysctlbyname("hw.acpi.acline", &value, &len, NULL, 0);
	if (value == 0)
	{
		sysctlbyname("hw.acpi.battery.time", &value, &len, NULL, 0);
		value = value * 60;
	}
	else
	{
		value = 0;
	}

	return (value);
#elif defined(_BSD_SOURCE)
	struct apm_power_info api;
	int apmfd;
	if ((apmfd = open("/dev/apm", O_RDONLY)) < 0) return 0;
	if (ioctl(apmfd, APM_IOC_GETPOWER, &api) < 0)
	{
		close(apmfd);
		return 0;
	}
	close(apmfd);
	if (api.battery_state == APM_BATT_UNKNOWN
		|| api.battery_state == APM_BATTERY_ABSENT
		|| api.battery_state == APM_BATT_CHARGING
		|| api.ac_state == APM_AC_ON)
	{
		return 0;
	}
	return ((int) (api.minutes_left)) * 60;
#else
	return 0;
#endif
}
