/*
 * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		James Woodcock
 */


#include "config.h"
#include "pcap-int.h"

#include <OS.h>

#include <sys/socket.h>
#include <sys/sockio.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/*
 * Private data for capturing on Haiku sockets.
 */
struct pcap_haiku {
	struct pcap_stat	stat;
	char	*device;	/* device name */
};


bool
prepare_request(struct ifreq& request, const char* name)
{
	if (strlen(name) >= IF_NAMESIZE)
		return false;

	strcpy(request.ifr_name, name);
	return true;
}


static int
pcap_read_haiku(pcap_t* handle, int maxPackets, pcap_handler callback,
	u_char* userdata)
{
	// Receive a single packet

	struct pcap_haiku* handlep = (struct pcap_haiku*)handle->priv;
	u_char* buffer = (u_char*)handle->buffer + handle->offset;
	struct sockaddr_dl from;
	ssize_t bytesReceived;
	do {
		if (handle->break_loop) {
			// Clear the break loop flag, and return -2 to indicate our
			// reasoning
			handle->break_loop = 0;
			return -2;
		}

		socklen_t fromLength = sizeof(from);
		bytesReceived = recvfrom(handle->fd, buffer, handle->bufsize, MSG_TRUNC,
			(struct sockaddr*)&from, &fromLength);
	} while (bytesReceived < 0 && errno == B_INTERRUPTED);

	if (bytesReceived < 0) {
		if (errno == B_WOULD_BLOCK) {
			// there is no packet for us
			return 0;
		}

		snprintf(handle->errbuf, sizeof(handle->errbuf),
			"recvfrom: %s", strerror(errno));
		return -1;
	}

	int32 captureLength = bytesReceived;
	if (captureLength > handle->snapshot)
		captureLength = handle->snapshot;

	// run the packet filter
	if (handle->fcode.bf_insns) {
		if (pcap_filter(handle->fcode.bf_insns, buffer, bytesReceived,
				captureLength) == 0) {
			// packet got rejected
			return 0;
		}
	}

	// fill in pcap_header
	pcap_pkthdr header;
	header.caplen = captureLength;
	header.len = bytesReceived;
	header.ts.tv_usec = system_time() % 1000000;
	header.ts.tv_sec = system_time() / 1000000;
	// TODO: get timing from packet!!!

	/* Call the user supplied callback function */
	callback(userdata, &header, buffer);
	return 1;
}


static int
pcap_inject_haiku(pcap_t *handle, const void *buffer, int size)
{
	// we don't support injecting packets yet
	// TODO: use the AF_LINK protocol (we need another socket for this) to
	// inject the packets
	strlcpy(handle->errbuf, "Sending packets isn't supported yet",
		PCAP_ERRBUF_SIZE);
	return -1;
}


static int
pcap_stats_haiku(pcap_t *handle, struct pcap_stat *stats)
{
	struct pcap_haiku* handlep = (struct pcap_haiku*)handle->priv;
	ifreq request;
	int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (socket < 0) {
		return -1;
	}
	prepare_request(request, handlep->device);
	if (ioctl(socket, SIOCGIFSTATS, &request, sizeof(struct ifreq)) < 0) {
		snprintf(handle->errbuf, PCAP_ERRBUF_SIZE, "pcap_stats: %s",
			strerror(errno));
		close(socket);
		return -1;
	}

	close(socket);
	handlep->stat.ps_recv += request.ifr_stats.receive.packets;
	handlep->stat.ps_drop += request.ifr_stats.receive.dropped;
	*stats = handlep->stat;
	return 0;
}


static int
pcap_activate_haiku(pcap_t *handle)
{
	struct pcap_haiku* handlep = (struct pcap_haiku*)handle->priv;

	const char* device = handle->opt.device;

	handle->read_op = pcap_read_haiku;
	handle->setfilter_op = install_bpf_program; /* no kernel filtering */
	handle->inject_op = pcap_inject_haiku;
	handle->stats_op = pcap_stats_haiku;

	// use default hooks where possible
	handle->getnonblock_op = pcap_getnonblock_fd;
	handle->setnonblock_op = pcap_setnonblock_fd;

	handlep->device	= strdup(device);
	if (handlep->device == NULL) {
		pcap_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE,
			errno, "strdup");
		return PCAP_ERROR;
	}

	handle->bufsize = 65536;
	// TODO: should be determined by interface MTU

	// allocate buffer for monitoring the device
	handle->buffer = (u_char*)malloc(handle->bufsize);
	if (handle->buffer == NULL) {
		pcap_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE,
			errno, "buffer malloc");
		return PCAP_ERROR;
	}

	handle->offset = 0;
	handle->linktype = DLT_EN10MB;
	// TODO: check interface type!

	return 0;
}


//	#pragma mark - pcap API


extern "C" pcap_t *
pcap_create_interface(const char *device, char *errorBuffer)
{
	// TODO: handle promiscuous mode!

	// we need a socket to talk to the networking stack
	int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (socket < 0) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE,
			"The networking stack doesn't seem to be available.\n");
		return NULL;
	}

	struct ifreq request;
	if (!prepare_request(request, device)) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE,
			"Interface name \"%s\" is too long.", device);
		close(socket);
		return NULL;
	}

	// check if the interface exist
	if (ioctl(socket, SIOCGIFINDEX, &request, sizeof(request)) < 0) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE,
			"Interface \"%s\" does not exist.\n", device);
		close(socket);
		return NULL;
	}

	close(socket);
	// no longer needed after this point

	// get link level interface for this interface

	socket = ::socket(AF_LINK, SOCK_DGRAM, 0);
	if (socket < 0) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE, "No link level: %s\n",
			strerror(errno));
		return NULL;
	}

	// start monitoring
	if (ioctl(socket, SIOCSPACKETCAP, &request, sizeof(struct ifreq)) < 0) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE, "Cannot start monitoring: %s\n",
			strerror(errno));
		close(socket);
		return NULL;
	}

	pcap_t* handle = PCAP_CREATE_COMMON(errorBuffer, struct pcap_haiku);
	if (handle == NULL) {
		snprintf(errorBuffer, PCAP_ERRBUF_SIZE, "malloc: %s", strerror(errno));
		close(socket);
		return NULL;
	}

	handle->selectable_fd = socket;
	handle->fd = socket;

	handle->activate_op = pcap_activate_haiku;

	return handle;
}

static int
can_be_bound(const char *name)
{
	return 1;
}

static int
get_if_flags(const char *name, bpf_u_int32 *flags, char *errbuf)
{
	/* TODO */
	if (*flags & PCAP_IF_LOOPBACK) {
		/*
		 * Loopback devices aren't wireless, and "connected"/
		 * "disconnected" doesn't apply to them.
		 */
		*flags |= PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
		return (0);
	}
	return (0);
}

extern "C" int
pcap_platform_finddevs(pcap_if_list_t* _allDevices, char* errorBuffer)
{
	return pcap_findalldevs_interfaces(_allDevices, errorBuffer, can_be_bound,
		get_if_flags);
}