/*
  *  Copyright (c) 2007, 2008, Andrea Bittau <a.bittau@cs.ucl.ac.uk>
  *
  *  OS dependent API for using card via network.
  *
  *  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/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/select.h>
#include <errno.h>

#include "aircrack-ng/support/communications.h"
#include "osdep.h"
#include "network.h"

#define QUEUE_MAX 666

struct netqueue
{
	unsigned char q_buf[2048];
	int q_len;

	struct netqueue * q_next;
	struct netqueue * q_prev;
};

struct priv_net
{
	int pn_s;
	struct netqueue pn_queue;
	struct netqueue pn_queue_free;
	int pn_queue_len;
};

EXPORT int net_send(int s, int command, void * arg, int len)
{
	struct net_hdr * pnh;
	char * pktbuf;
	size_t pktlen;

	// Validate command value
	assert(command >= NET_RC && command <= HIGHEST_NET_COMMAND);
	if (command < NET_RC || command > HIGHEST_NET_COMMAND)
	{
		return -1;
	}

	if (arg == NULL) return -1;

	pktlen = sizeof(struct net_hdr) + len;

	pktbuf = (char *) calloc(sizeof(char), pktlen);
	if (pktbuf == NULL)
	{
		perror("calloc");
		goto net_send_error;
	}

	pnh = (struct net_hdr *) pktbuf;
	pnh->nh_type = command;
	pnh->nh_len = htonl(len);

	memcpy(pktbuf + sizeof(struct net_hdr), arg, len);

	for (;;)
	{
		ssize_t rc = send(s, pktbuf, pktlen, 0);

		if ((size_t) rc == pktlen) break;

		if (rc == EAGAIN || rc == EWOULDBLOCK || rc == EINTR) continue;

		if (rc == ECONNRESET)
			printf("Connection reset while sending packet!\n");

		goto net_send_error;
	}

	free(pktbuf);
	return 0;

net_send_error:
	free(pktbuf);
	return -1;
}

EXPORT int net_read_exact(int s, void * arg, int len)
{
	ssize_t rc;
	int rlen = 0;
	char * buf = (char *) arg;
	while (rlen < len)
	{
		rc = recv(s, buf, (len - rlen), 0);

		if (rc < 1)
		{
			if (rc == -1 && (errno == EAGAIN || errno == EINTR))
			{
				usleep(100);
				continue;
			}

			return -1;
		}

		buf += rc;
		rlen += rc;
	}

	return 0;
}

EXPORT int net_get(int s, void * arg, int * len)
{
	struct net_hdr nh;
	int plen;

	if (net_read_exact(s, &nh, sizeof(nh)) == -1)
	{
		return -1;
	}

	plen = ntohl(nh.nh_len);
	assert(plen <= *len && plen >= 0);

	*len = plen;
	if ((*len) && (net_read_exact(s, arg, *len) == -1))
	{
		return -1;
	}

	return nh.nh_type;
}

static void queue_del(struct netqueue * q)
{
	q->q_prev->q_next = q->q_next;
	q->q_next->q_prev = q->q_prev;
}

static void queue_add(struct netqueue * head, struct netqueue * q)
{
	struct netqueue * pos = head->q_prev;

	q->q_prev = pos;
	q->q_next = pos->q_next;
	q->q_next->q_prev = q;
	pos->q_next = q;
}

#if 0
static int queue_len(struct netqueue *head)
{
	struct netqueue *q = head->q_next;
	int i = 0;

	while (q != head) {
		i++;
		q = q->q_next;
	}

	return i;
}
#endif

static struct netqueue * queue_get_slot(struct priv_net * pn)
{
	struct netqueue * q = pn->pn_queue_free.q_next;

	if (q != &pn->pn_queue_free)
	{
		queue_del(q);
		return q;
	}

	if (pn->pn_queue_len++ > QUEUE_MAX) return NULL;

	return malloc(sizeof(*q));
}

static void net_enque(struct priv_net * pn, void * buf, int len)
{
	struct netqueue * q;

	q = queue_get_slot(pn);
	if (!q) return;

	q->q_len = len;
	assert((int) sizeof(q->q_buf) >= q->q_len);
	memcpy(q->q_buf, buf, q->q_len);
	queue_add(&pn->pn_queue, q);
}

static int net_get_nopacket(struct priv_net * pn, void * arg, int * len)
{
	unsigned char buf[2048];
	int l = sizeof(buf);
	int c = 0;

	while (1)
	{
		l = sizeof(buf);
		c = net_get(pn->pn_s, buf, &l);
		if (c < 0) return c;

		if (c != NET_PACKET && c > 0) break;

		if (c > 0) net_enque(pn, buf, l);
	}

	assert(l <= *len);
	memcpy(arg, buf, l);
	*len = l;

	return c;
}

static int net_cmd(struct priv_net * pn, int command, void * arg, int alen)
{
	uint32_t rc = 0;
	int len;
	int cmd;

	if (net_send(pn->pn_s, command, arg, alen) == -1)
	{
		return -1;
	}

	len = sizeof(rc);
	cmd = net_get_nopacket(pn, &rc, &len);
	if (cmd == -1)
	{
		return -1;
	}
	assert(cmd == NET_RC); //-V547
	assert(len == sizeof(rc));

	return ntohl(rc);
}

static int queue_get(struct priv_net * pn, void * buf, int len)
{
	struct netqueue * head = &pn->pn_queue;
	struct netqueue * q = head->q_next;

	if (q == head) return 0;

#ifdef NDEBUG
	(void) len;
#endif

	assert(q->q_len <= len);
	memcpy(buf, q->q_buf, q->q_len);

	queue_del(q);
	queue_add(&pn->pn_queue_free, q);

	return q->q_len;
}

static int net_read(struct wif * wi,
					struct timespec * ts,
					int * dlt,
					unsigned char * h80211,
					int len,
					struct rx_info * ri)
{
	struct priv_net * pn = wi_priv(wi);
	uint32_t buf[512] = {0}; // 512 * 4 = 2048
	unsigned char * bufc = (unsigned char *) buf;
	int cmd;
	int sz = sizeof(*ri);
	int l;
	int ret;

	/* try queue */
	l = queue_get(pn, buf, sizeof(buf));
	if (!l)
	{
		/* try reading form net */
		l = sizeof(buf);
		cmd = net_get(pn->pn_s, buf, &l);

		if (cmd == -1) return -1;
		if (cmd == NET_RC)
		{
			ret = ntohl((buf[0]));
			return ret;
		}
		assert(cmd == NET_PACKET);
	}

	/* XXX */
	if (ri)
	{
		// re-assemble 64-bit integer
		uint64_t hi = buf[0];
		ri->ri_mactime = __be64_to_cpu(((hi) << 32U) | buf[1]);
		ri->ri_power = __be32_to_cpu(buf[2]);
		ri->ri_noise = __be32_to_cpu(buf[3]);
		ri->ri_channel = __be32_to_cpu(buf[4]);
		ri->ri_freq = __be32_to_cpu(buf[5]);
		ri->ri_rate = __be32_to_cpu(buf[6]);
		ri->ri_antenna = __be32_to_cpu(buf[7]);
	}
	l -= sz;
	assert(l > 0);
	if (l > len) l = len;
	memcpy(h80211, &bufc[sz], l);

	if (dlt)
	{
		*dlt = LINKTYPE_IEEE802_11;
	}

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

	return l;
}

static int net_get_mac(struct wif * wi, unsigned char * mac)
{
	struct priv_net * pn = wi_priv(wi);
	uint32_t buf[2]; // only need 6 bytes, this provides 8
	int cmd;
	int sz = 6;

	if (net_send(pn->pn_s, NET_GET_MAC, NULL, 0) == -1) return -1;

	cmd = net_get_nopacket(pn, buf, &sz);
	if (cmd == -1) return -1;
	if (cmd == NET_RC) return ntohl(buf[0]); //-V547
	assert(cmd == NET_MAC);
	assert(sz == 6);

	memcpy(mac, buf, 6); //-V512

	return 0;
}

static int net_write(struct wif * wi,
					 struct timespec * ts,
					 int dlt,
					 unsigned char * h80211,
					 int len,
					 struct tx_info * ti)
{
	struct priv_net * pn = wi_priv(wi);
	int sz = sizeof(*ti);
	unsigned char buf[2048];
	unsigned char * ptr = buf;

	(void) ts;
	(void) dlt;

	/* XXX */
	if (ti)
		memcpy(ptr, ti, sz); //-V512
	else
		memset(ptr, 0, sizeof(*ti)); //-V512

	ptr += sz;
	memcpy(ptr, h80211, len);
	sz += len;

	return net_cmd(pn, NET_WRITE, buf, sz);
}

static int net_set_channel(struct wif * wi, int chan)
{
	uint32_t c = htonl(chan);

	return net_cmd(wi_priv(wi), NET_SET_CHAN, &c, sizeof(c));
}

static int net_get_channel(struct wif * wi)
{
	struct priv_net * pn = wi_priv(wi);

	return net_cmd(pn, NET_GET_CHAN, NULL, 0);
}

static int net_set_rate(struct wif * wi, int rate)
{
	uint32_t c = htonl(rate);

	return net_cmd(wi_priv(wi), NET_SET_RATE, &c, sizeof(c));
}

static int net_get_rate(struct wif * wi)
{
	struct priv_net * pn = wi_priv(wi);

	return net_cmd(pn, NET_GET_RATE, NULL, 0);
}

static int net_get_monitor(struct wif * wi)
{
	return net_cmd(wi_priv(wi), NET_GET_MONITOR, NULL, 0);
}

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

static void net_close(struct wif * wi)
{
	struct priv_net * pn = wi_priv(wi);

	close(pn->pn_s);
	do_net_free(wi);
}

static int handshake(int s)
{
	if (s)
	{
	} /* XXX unused */
	/* XXX do a handshake */
	return 0;
}

static int do_net_open(char * iface)
{
	int s, port;
	char ip[16];
	struct sockaddr_in s_in;

	port = get_ip_port(iface, ip, sizeof(ip) - 1);
	if (port == -1) return -1;

	memset(&s_in, 0, sizeof(struct sockaddr_in));
	s_in.sin_family = PF_INET;
	s_in.sin_port = htons(port);
	if (!inet_aton(ip, &s_in.sin_addr)) return -1;

	if ((s = socket(s_in.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
		return -1;

	printf("Connecting to %s port %d...\n", ip, port);

	if (connect(s, (struct sockaddr *) &s_in, sizeof(s_in)) == -1)
	{
		close(s);

		printf("Failed to connect\n");

		return -1;
	}

	if (handshake(s) == -1)
	{
		close(s);

		printf("Failed to connect - handshake failed\n");

		return -1;
	}

	printf("Connection successful\n");

	return s;
}

static int net_fd(struct wif * wi)
{
	struct priv_net * pn = wi_priv(wi);

	return pn->pn_s;
}

EXPORT struct wif * net_open(char * iface)
{
	struct wif * wi;
	struct priv_net * pn;
	int s;

	/* setup wi struct */
	wi = wi_alloc(sizeof(*pn));
	if (!wi) return NULL;
	wi->wi_read = net_read;
	wi->wi_write = net_write;
	wi->wi_set_channel = net_set_channel;
	wi->wi_get_channel = net_get_channel;
	wi->wi_set_rate = net_set_rate;
	wi->wi_get_rate = net_get_rate;
	wi->wi_close = net_close;
	wi->wi_fd = net_fd;
	wi->wi_get_mac = net_get_mac;
	wi->wi_get_monitor = net_get_monitor;

	/* setup iface */
	s = do_net_open(iface);
	if (s == -1)
	{
		do_net_free(wi);
		return NULL;
	}

	/* setup private state */
	pn = wi_priv(wi);
	pn->pn_s = s;
	pn->pn_queue.q_next = pn->pn_queue.q_prev = &pn->pn_queue;
	pn->pn_queue_free.q_next = pn->pn_queue_free.q_prev = &pn->pn_queue_free;

	return wi;
}
