/*-
 * Copyright (c) 2004 Robert N. M. Watson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * [id for your version control system, if any]
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/bpf.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ether.h"

static struct bpf_insn matchall_instructions[] = {
	/* BPF_RET, , , 1600 */
	{ 0x6, 0, 0, 0x00000640 }
};
static const int	matchall_instructions_count =
			    sizeof(matchall_instructions) /
			    sizeof(struct bpf_insn);

#define	BPF_THREAD_NONE		-1	/* Nothing at all. */
#define	BPF_THREAD_CREATED	0	/* Created, not yet active. */
#define	BPF_THREAD_RUNNING	1	/* Up and running. */
#define	BPF_THREAD_DOSTOP	2	/* Stop requested. */
#define	BPF_THREAD_STOPPING	3	/* Thread signalled stopping. */

static int		 bpf_fd = -1;
static pthread_t	 bpf_thread;
static pthread_mutex_t	 bpf_mutex;
static pthread_cond_t	 bpf_cond;
static int		 bpf_thread_state = BPF_THREAD_NONE;

#define	BUFSIZE	BPF_MAXBUFSIZE
static u_char		 bpf_buffer[BUFSIZE];
static u_char		*bpf_bp;	/* Buffer begin pointer. */
static u_char		*bpf_ep;	/* Buffer end pointer. */

static void
bpf_read(void)
{
	struct bpf_hdr *hdr;
	ssize_t len;

	len = read(bpf_fd, bpf_buffer, BUFSIZE);
	if (len < 0) {
		perror("bpf_read read");
		return;
	}

	bpf_bp = bpf_buffer;
	bpf_ep = bpf_buffer + len;
	while (bpf_bp < bpf_ep) {
		hdr = (struct bpf_hdr *)bpf_bp;

		/*
		 * Check for packets truncated during capture.
		 */
		if (hdr->bh_caplen != hdr->bh_datalen) {
			printf("Truncated packet\n");
			bpf_bp += BPF_WORDALIGN(hdr->bh_caplen +
			    hdr->bh_hdrlen);
			continue;
		}

		ether_input(((u_char *)hdr) + hdr->bh_hdrlen, hdr->bh_caplen);

		bpf_bp += BPF_WORDALIGN(hdr->bh_caplen + hdr->bh_hdrlen);
	}
}

static void *
bpf_worker(void *arg)
{

	bpf_thread_state = BPF_THREAD_RUNNING;
	assert(pthread_cond_signal(&bpf_cond) == 0);

	while (1) {
		bpf_read();
		if (bpf_thread_state == BPF_THREAD_DOSTOP)
			break;
	}

	bpf_thread_state = BPF_THREAD_STOPPING;
	assert(pthread_cond_signal(&bpf_cond) == 0);

	return (NULL);
}

int
bpf_output(u_char *packet, u_int packetlen)
{

	return (write(bpf_fd, packet, packetlen));
}

int
bpf_start(const char *interface)
{
	char device[sizeof("/dev/bpf000")];
	struct bpf_program bpf;
	int error, fd, i, n;
	struct timeval tv;
	struct ifreq ifr;

	if (bpf_fd != -1) {
		fprintf(stderr, "bpf_start: already started\n");
		errno = EBUSY;
		return (-1);
	}

	n = 0;
	do {
		sprintf(device, "/dev/bpf%d", n++);
		fd = open(device, O_RDWR);
	} while (fd < 0 && errno == EBUSY && n < 1000);

	if (fd == -1) {
		error = errno;
		perror("BPF open");
		errno = error;
		return (-1);
	}

	i = BPF_MAXBUFSIZE;
	if (ioctl(fd, BIOCSBLEN, &i)) {
		error = errno;
		perror("BIOCSBLEN");
		close(fd);
		errno = error;
		return (-1);
	}

	bzero(&ifr, sizeof(ifr));
	strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
	if (ioctl(fd, BIOCSETIF, &ifr) < 0) {
		error = errno;
		perror("BIOCSETIF");
		close(fd);
		errno = error;
		return (-1);
	}

	if (ioctl(fd, BIOCGDLT, &i) < 0) {
		error = errno;
		perror("BIOCGDLT");
		close(fd);
		errno = error;
		return (-1);
	}

	if (i != DLT_EN10MB) {
		fprintf(stderr, "BIOCGDLT: Not DLT_EN10MB\n");
		close(fd);
		errno = EINVAL;
		return (-1);
	}

	i = 1;
	if (ioctl(fd, BIOCSHDRCMPLT, &i) < 0) {
		error = errno;
		perror("BIOCSHDRCMPLT");
		close(fd);
		errno = error;
		return (-1);
	}

	/*
	 * Wake up from read every 1 second to check whether the thread needs
	 * to state transition.	
	 */
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	if (ioctl(fd, BIOCSRTIMEOUT, &tv) < 0) {
		error = errno;
		perror("BIOCSRTIMEOUT");
		close(fd);
		errno = error;
		return (-1);
	}

	i = 1;
	if (ioctl(fd, BIOCIMMEDIATE, &i) < 0) {
		error = errno;
		perror("BIOCIMMEDIATE");
		close(fd);
		errno = error;
		return (-1);
	}

	i = 1;
	if (ioctl(fd, BIOCPROMISC, &i) < 0) {
		error = errno;
		perror("BIOCPROMISC");
		close(fd);
		errno = error;
		return (-1);
	}

	bpf.bf_len = matchall_instructions_count;
	bpf.bf_insns = matchall_instructions;

	if (ioctl(fd, BIOCSETF, &bpf) < 0) {
		error = errno;
		perror("BIOCSETF");
		close(fd);
		errno = error;
		return (-1);
	}

	if (pthread_mutex_init(&bpf_mutex, NULL) < 0) {
		error = errno;
		perror("pthread_mutex_init");
		close(fd);
		errno = error;
		return (-1);
	}

	if (pthread_cond_init(&bpf_cond, NULL) < 0) {
		error = errno;
		perror("pthread_cond_init");
		close(fd);
		(void)pthread_mutex_destroy(&bpf_mutex);
		errno = error;
		return (-1);
	}

	bpf_fd = fd;

	bpf_thread_state = BPF_THREAD_CREATED;
	if (pthread_create(&bpf_thread, NULL, bpf_worker, NULL) < 0) {
		error = errno;
		perror("pthread_create");
		close(fd);
		(void)pthread_cond_destroy(&bpf_cond);
		(void)pthread_mutex_destroy(&bpf_mutex);
		errno = errno;
		bpf_thread_state = BPF_THREAD_NONE;
		return (-1);
	}

	/*
	 * Wait for thread to be in a non-created state.
	 */
	assert(pthread_mutex_lock(&bpf_mutex) == 0);
	while (bpf_thread_state == BPF_THREAD_CREATED)
		assert(pthread_cond_wait(&bpf_cond, &bpf_mutex) == 0);
	assert(pthread_mutex_unlock(&bpf_mutex) == 0);

	return (0);
}

void
bpf_stop(void)
{

	if (bpf_fd == -1)
		return;

	/*
	 * Signal desire for a shutdown.
	 */
	assert(pthread_mutex_lock(&bpf_mutex) == 0);
	assert(bpf_thread_state == BPF_THREAD_RUNNING);
	bpf_thread_state = BPF_THREAD_DOSTOP;
	assert(pthread_cond_signal(&bpf_cond) == 0);
	assert(pthread_mutex_unlock(&bpf_mutex) == 0);

	/*
	 * Wait for thread to indicate it's moved into the shutdown state,
	 * then join it.
	 */
	assert(pthread_mutex_lock(&bpf_mutex) == 0);
	while (bpf_thread_state != BPF_THREAD_STOPPING)
		assert(pthread_cond_wait(&bpf_cond, &bpf_mutex) == 0);
	assert(pthread_mutex_unlock(&bpf_mutex) == 0);

	if (pthread_join(bpf_thread, NULL) < 0)
		perror("pthread_join bpf_thread");

	if (bpf_thread_state != BPF_THREAD_NONE &&
	    bpf_thread_state != BPF_THREAD_STOPPING)
		fprintf(stderr, "bpf_stop: invalid thread state %d",
		    bpf_thread_state);
	bpf_thread_state = BPF_THREAD_NONE;

	if (pthread_cond_destroy(&bpf_cond) < 0)
		perror("pthread_cond_destroy");

	if (pthread_mutex_destroy(&bpf_mutex) < 0)
		perror("pthread_mutex_destroy");

	close(bpf_fd);
	bpf_fd = -1;
}
