 /*
  *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */

#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 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, 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(ri && !ri->ri_channel)
            ri->ri_channel = wi_get_channel(wi);

	return plen;
}

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

	/* 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);
}

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
}
