/*
   *  Copyright (c) 2007, 2008, Andrea Bittau <a.bittau@cs.ucl.ac.uk>
   *
   *  OS dependent API for cygwin. It relies on an external
   *  DLL to do the actual wifi stuff
   *
   *  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 <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <windows.h>

#include "osdep.h"
#include "network.h"
#include "cygwin.h"

#ifdef HAVE_AIRPCAP
#include "aircrack_ng_airpcap.h"
#endif

#define xstr(s) str(s)
#define str(s) #s

#define DLL_EXTENSION ".dll"

struct priv_cygwin
{
	pthread_t pc_reader;
	volatile int pc_running;
	int pc_pipe[2]; /* reader -> parent */
	int pc_channel;
	int pc_frequency;
	struct wif * pc_wi;
	int pc_did_init;

	int isAirpcap;
	int useDll;

	int (*pc_init)(char * param);
	int (*pc_set_chan)(int chan);
	int (*pc_set_freq)(int freq);
	int (*pc_inject)(void * buf, int len, struct tx_info * ti);
	int (*pc_sniff)(void * buf, int len, struct rx_info * ri);
	int (*pc_get_mac)(void * mac);
	int (*pc_set_mac)(void * mac);
	void (*pc_close)(void);
};

/**
 * strstr() function case insensitive
 * @param String C string to be scanned
 * @param Pattern C string containing the sequence of characters to match
 * @return Pointer to the first occurrence of Pattern in String, or a null
 * pointer if there Pattern is not part of String.
 */
char * stristr(const char * String, const char * Pattern)
{
	char *pptr, *sptr, *start;
	unsigned slen, plen;

	for (start = (char *) String,
		pptr = (char *) Pattern,
		slen = strlen(String),
		plen = strlen(Pattern);

		 /* while string length not shorter than pattern length */

		 slen >= plen;

		 start++, slen--)
	{
		/* find start of pattern in string */
		while (toupper((int) *start) != toupper((int) *Pattern))
		{
			start++;
			slen--;

			/* if pattern longer than string */

			if (slen < plen) return (NULL);
		}

		sptr = start;
		pptr = (char *) Pattern;

		while (toupper((int) *sptr) == toupper((int) *pptr))
		{
			sptr++;
			pptr++;

			/* if end of pattern then pattern was found */

			if ('\0' == *pptr) return (start);
		}
	}
	return (NULL);
}

/**
 * Get the different functions for to interact with the device:
 * - setting monitor mode
 * - changing channel
 * - capturing data
 * - injecting packets
 * @param iface The interface name
 */
static int do_cygwin_open(struct wif * wi, char * iface)
{
	struct priv_cygwin * priv = wi_priv(wi);
	void * lib;
	char * file;
	char * parm;
	int rc = -1;

	if (!iface) return -1;
	if (*iface == 0) return -1;

	priv->useDll = 0;

	if (stristr(iface, DLL_EXTENSION)) priv->useDll = 1;

	if (priv->useDll)
	{
		file = strdup(iface);
		if (!file) return -1;

		parm = strchr(file, '|');
		if (parm) *parm++ = 0;

		/* load lib */
		lib = dlopen(file, RTLD_LAZY);
		if (!lib) goto errdll;

#if defined(__GNUC__) || defined(__llvm__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
		priv->pc_init = dlsym(lib, xstr(CYGWIN_DLL_INIT));
		priv->pc_set_chan = dlsym(lib, xstr(CYGWIN_DLL_SET_CHAN));
		priv->pc_set_freq = dlsym(lib, xstr(CYGWIN_DLL_SET_FREQ));
		priv->pc_get_mac = dlsym(lib, xstr(CYGWIN_DLL_GET_MAC));
		priv->pc_set_mac = dlsym(lib, xstr(CYGWIN_DLL_SET_MAC));
		priv->pc_close = dlsym(lib, xstr(CYGWIN_DLL_CLOSE));
		priv->pc_inject = dlsym(lib, xstr(CYGWIN_DLL_INJECT));
		priv->pc_sniff = dlsym(lib, xstr(CYGWIN_DLL_SNIFF));
#if defined(__GNUC__) || defined(__llvm__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif

		if (!(priv->pc_init && priv->pc_set_chan && priv->pc_get_mac
			  && priv->pc_inject
			  && priv->pc_sniff
			  && priv->pc_close))
			goto errdll;

		/* init lib */
		if ((rc = priv->pc_init(parm))) goto errdll;
		priv->pc_did_init = 1;

		rc = 0;

	errdll:
		free(file);
	}
	else
	{
#ifdef HAVE_AIRPCAP
		// Check if it's an Airpcap device
		priv->isAirpcap = isAirpcapDevice(iface);

		if (priv->isAirpcap)
		{
			// Get functions
			priv->pc_init = airpcap_init;
			priv->pc_set_chan = airpcap_set_chan;
			priv->pc_get_mac = airpcap_get_mac;
			priv->pc_set_mac = airpcap_set_mac;
			priv->pc_close = airpcap_close;
			priv->pc_inject = airpcap_inject;
			priv->pc_sniff = airpcap_sniff;

			rc = 0;
		}

#endif
	}

	if (rc == 0)
	{
		// Don't forget to initialize
		if (!priv->useDll)
		{
			rc = priv->pc_init(iface);

			if (rc == 0)
				priv->pc_did_init = 1;
			else
				fprintf(stderr, "Error initializing <%s>\n", iface);
		}
	}
	else
	{
		// Show an error message if the adapter is not supported
		fprintf(stderr, "Adapter <%s> not supported\n", iface);
	}

	return rc;
}

/**
 * Change channel
 * @param chan Channel
 * @return 0 if successful, -1 if it failed
 */
static int cygwin_set_channel(struct wif * wi, int chan)
{
	struct priv_cygwin * priv = wi_priv(wi);

	if (priv->pc_set_chan(chan) == -1) return -1;

	priv->pc_channel = chan;
	return 0;
}

/**
 * Change frequency
 * @param freq Frequency
 * @return 0 if successful, -1 if it failed
 */
static int cygwin_set_freq(struct wif * wi, int freq)
{
	struct priv_cygwin * priv = wi_priv(wi);

	if (!priv->pc_set_freq || priv->pc_set_freq(freq) == -1) return -1;

	priv->pc_frequency = freq;
	return 0;
}

/**
 * Capture a packet
 * @param buf Buffer for the packet (has to be already allocated)
 * @param len Length of the buffer
 * @param ri Receive information structure
 * @return -1 in case of failure or the number of bytes received
 */
static int cygwin_read_packet(struct priv_cygwin * priv,
							  void * buf,
							  int len,
							  struct rx_info * ri)
{
	int rd;

	memset(ri, 0, sizeof(*ri));

	rd = priv->pc_sniff(buf, len, ri);
	if (rd == -1) return -1;

	if (!ri->ri_channel) ri->ri_channel = wi_get_channel(priv->pc_wi);

	return rd;
}

/**
 * Send a packet
 * @param h80211 The packet itself
 * @param len Length of the packet
 * @param ti Transmit information
 * @return -1 if failure or the number of bytes sent
 */
static int cygwin_write(struct wif * wi,
						struct timespec * ts,
						int dlt,
						unsigned char * h80211,
						int len,
						struct tx_info * ti)
{
	struct priv_cygwin * priv = wi_priv(wi);
	int rc;

	(void) ts;
	(void) dlt;

	if ((rc = priv->pc_inject(h80211, len, ti)) == -1) return -1;

	return rc;
}

/**
 * Get device channel
 * @return channel
 */
static int cygwin_get_channel(struct wif * wi)
{
	struct priv_cygwin * pc = wi_priv(wi);

	return pc->pc_channel;
}

static int cygwin_get_freq(struct wif * wi)
{
	struct priv_cygwin * pc = wi_priv(wi);

	return pc->pc_frequency;
}

int cygwin_read_reader(int fd, int plen, void * dst, int len)
{
	/* packet */
	if (len > plen) len = plen;
	if (net_read_exact(fd, dst, len) == -1) return -1;
	plen -= len;

	/* consume packet */
	while (plen)
	{
		char lame[1024];
		int rd = sizeof(lame);

		if (rd > plen) rd = plen;

		if (net_read_exact(fd, lame, rd) == -1) return -1;

		plen -= rd;

		assert(plen >= 0);
	}

	return len;
}

static int cygwin_read(struct wif * wi,
					   struct timespec * ts,
					   int * dlt,
					   unsigned char * h80211,
					   int len,
					   struct rx_info * ri)
{
	struct priv_cygwin * pc = wi_priv(wi);
	struct rx_info tmp;
	int plen;

	(void) ts;
	(void) dlt;

	if (pc->pc_running == -1) return -1;

	if (!ri) ri = &tmp;

	/* length */
	if (net_read_exact(pc->pc_pipe[0], &plen, sizeof(plen)) == -1) return -1;

	/* ri */
	if (net_read_exact(pc->pc_pipe[0], ri, sizeof(*ri)) == -1) return -1;
	plen -= sizeof(*ri);
	assert(plen > 0);

	return cygwin_read_reader(pc->pc_pipe[0], plen, h80211, len);
}

/**
 * Free allocated data
 */
static void do_free(struct wif * wi)
{
	struct priv_cygwin * pc = wi_priv(wi);
	int tries = 3;

	/* wait for reader */
	if (pc->pc_running == 1)
	{
		pc->pc_running = 0;

		while ((pc->pc_running != -1) && tries--) sleep(1);
	}

	if (pc->pc_pipe[0])
	{
		close(pc->pc_pipe[0]);
		close(pc->pc_pipe[1]);
	}

	if (pc->pc_did_init) pc->pc_close();

	assert(wi->wi_priv);
	free(wi->wi_priv);
	wi->wi_priv = 0;

	free(wi);
}

/**
 * Close the device and free data
 */
static void cygwin_close(struct wif * wi) { do_free(wi); }

/**
 * Get the file descriptor for the device
 */
static int cygwin_fd(struct wif * wi)
{
	struct priv_cygwin * pc = wi_priv(wi);

	if (pc->pc_running == -1) return -1;

	return pc->pc_pipe[0];
}

/**
 * Get MAC Address of the device
 * @param mac It will contain the mac address
 * @return 0 if successful
 */
static int cygwin_get_mac(struct wif * wi, unsigned char * mac)
{
	struct priv_cygwin * pc = wi_priv(wi);

	return pc->pc_get_mac(mac);
}
/**
 * Set MAC Address of the device
 * @param mac MAC Address
 * @return 0 if successful
 */
static int cygwin_set_mac(struct wif * wi, unsigned char * mac)
{
	struct priv_cygwin * pc = wi_priv(wi);

	return pc->pc_set_mac(mac);
}

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

	return 0;
}

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

	return 1000000;
}

/**
 * Set (injection) rate of the device
 * @param rate Rate to be used
 * @return 0 (successful)
 */
static int cygwin_set_rate(struct wif * wi, int rate)
{
	if (wi || rate)
	{
	} /* XXX unused */

	return 0;
}

static void * cygwin_reader(void * arg)
{
	struct priv_cygwin * priv = arg;
	unsigned char buf[2048];
	int len;
	struct rx_info ri;

	while (priv->pc_running)
	{
		/* read one packet */

		/* a potential problem: the cygwin_read_packet will never return
		 * if there no packet sniffered, so the thread cannot be closed
		 * correctly.
		 */
		len = cygwin_read_packet(priv, buf, sizeof(buf), &ri);
		if (len == -1) break;

		/* len */
		len += sizeof(ri);
		if (write(priv->pc_pipe[1], &len, sizeof(len)) != sizeof(len)) break;
		len -= sizeof(ri);

		/* ri */
		if (write(priv->pc_pipe[1], &ri, sizeof(ri)) != sizeof(ri)) break;

		/* packet */
		if (write(priv->pc_pipe[1], buf, len) != len) break;
	}

	priv->pc_running = -1;
	return NULL;
}

static struct wif * cygwin_open(char * iface)
{
	struct wif * wi;
	struct priv_cygwin * priv;

	/* setup wi struct */
	wi = wi_alloc(sizeof(*priv));
	if (!wi) return NULL;
	wi->wi_read = cygwin_read;
	wi->wi_write = cygwin_write;
	wi->wi_set_channel = cygwin_set_channel;
	wi->wi_get_channel = cygwin_get_channel;
	wi->wi_set_freq = cygwin_set_freq;
	wi->wi_get_freq = cygwin_get_freq;
	wi->wi_close = cygwin_close;
	wi->wi_fd = cygwin_fd;
	wi->wi_get_mac = cygwin_get_mac;
	wi->wi_set_mac = cygwin_set_mac;
	wi->wi_get_rate = cygwin_get_rate;
	wi->wi_set_rate = cygwin_set_rate;
	wi->wi_get_monitor = cygwin_get_monitor;

	/* setup iface */
	if (do_cygwin_open(wi, iface) == -1) goto err;

	/* setup private state */
	priv = wi_priv(wi);
	priv->pc_wi = wi;

	/* setup reader */
	if (pipe(priv->pc_pipe) == -1) goto err;
	priv->pc_running = 2;
	if (pthread_create(&priv->pc_reader, NULL, cygwin_reader, priv)) goto err;
	priv->pc_running = 1;

	return wi;

err:
	do_free(wi);
	return NULL;
}

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

/**
 * Return remaining battery time in seconds.
 * @return Battery time in seconds or 0 if no battery (or connected to power)
 */
EXPORT int get_battery_state(void)
{
	SYSTEM_POWER_STATUS powerStatus;
	int batteryTime = 0;

	if (GetSystemPowerStatus(&powerStatus) == TRUE)
	{

		if (powerStatus.ACLineStatus == 0)
			batteryTime = (int) powerStatus.BatteryLifeTime;
	}
	return batteryTime;
}
