/*-
 * Copyright (c) 2007 Seccuris Inc.
 * All rights reserved.
 *
 * This sofware was developed by Robert N. M. Watson under contract to
 * Seccuris Inc.
 *
 * 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.
 *
 * $FreeBSD$
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "../../src/sys/net/bpf.h"

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define	USE_BUFFER_PAGES	256	/* How many to use. */
#define	ALLOC_BUFFER_PAGES	2048	/* How many to allocate. */

static int
bpf_ackzbuf(int fd, void *buf, u_int buflen)
{
	struct bpf_zbuf bz;

	bzero(&bz, sizeof(bz));
	bz.bz_bufa = buf;
	bz.bz_bufb = (void *)0xffffffff;
	bz.bz_buflen = buflen;
	if (ioctl(fd, BIOCACKZBUF, &bz) < 0)
		return (-1);
	return (0);
}

static int
bpf_getzbuf(int fd, char **bufa, char **bufb, size_t *buflen)
{
	struct bpf_zbuf bz;

	bzero(&bz, sizeof(bz));
	if (ioctl(fd, BIOCGETZBUF, &bz) < 0)
		return (-1);
	*bufa = bz.bz_bufa;
	*bufb = bz.bz_bufb;
	*buflen = bz.bz_buflen;
	return (0);
}

static int
bpf_setzbuf(int fd, char *bufa, char *bufb, size_t buflen)
{
	struct bpf_zbuf bz;

	bzero(&bz, sizeof(bz));
	bz.bz_bufa = bufa;
	bz.bz_bufb = bufb;
	bz.bz_buflen = buflen;
	if (ioctl(fd, BIOCSETZBUF, &bz) < 0)
		return (-1);
	return (0);
}

static int
bpf_open(void)
{
	char path[PATH_MAX];
	int fd, i;

	for (i = 0; i < 256; i++) {
		sprintf(path, "/dev/bpf%d", i);
		fd = open(path, O_RDWR);
		if ((fd < 0) && (errno != EBUSY))
			err(-1, "%s", path);
		if (fd >= 0)
			break;
	}
	if (i == 256)
		errx(-1, "BPF not available");
	return (fd);
}

/*
 * Given a set of arguments, verify that either it succeeds or gets the right
 * error.  Abort program only if unable to set back to the original state.
 */
static void
bpf_test_setzbuf(char *desc, char *bufa, char *bufb, size_t buflen,
    int error)
{
	char *bufa_check, *bufb_check;
	size_t buflen_check;
	int fd, ret;
	u_int mode;

	fd = bpf_open();
	if (fd < 0)
		err(-1, "bpf_test_setzbuf(%s): bpf_open", desc);

	mode = BPF_BUFMODE_ZBUF;
	if (ioctl(fd, BIOCSETBUFMODE, &mode) < 0)
		err(-1,
		    "bpf_test_setzbuf(%s): ioctl(BIOCSETBUFMODE, "
		    "BPF_BUFMODE_ZBUF)", desc);

	ret = bpf_setzbuf(fd, bufa, bufb, buflen);
	if (ret == 0 && error != 0) {
		warnx("bpf_test_setzbuf(%s): bpf_setzbuf(0x%x, 0x%x, %d)"
		    " should fail with %d (%s) but returned 0", desc,
		    (uintptr_t)bufa, (uintptr_t)bufb, buflen, error,
		    strerror(error));
		goto out;
	}
	if (ret < 0 && error != errno) {
		warnx("bpf_test_setzbuf(%s): bpf_setzbuf(0x%x, 0x%x, %d)"
		    " should fail with %d (%s) but failed with %d (%s) "
		    "instead", desc, (uintptr_t)bufa, (uintptr_t)bufb,
		    buflen, error, strerror(error), errno, strerror(errno));
		goto out;
	}

	ret = bpf_getzbuf(fd, &bufa_check, &bufb_check, &buflen_check);
	if (ret < 0)
		err(-1, "bpf_test_setzbuf(%s): bpf_getzbuf() to confirm "
		    "results failed", desc);
	if (error == 0) {
		if (bufa_check != bufa || bufb_check != bufb ||
		    buflen != buflen_check)
			errx(-1, "bpf_test_setzbuf(%s): getzbuf returned "
			    "(0x%x, 0x%x, %d)", desc, (uintptr_t)bufa_check,
			    (uintptr_t)bufb_check, buflen_check);
	} else {
		if (bufa_check != NULL || bufb_check != NULL ||
		    buflen_check != 0)
			errx(-1, "bpf_test_setzbuf(%s): getzbuf returned "
			    "(0x%x, 0x%x, %d)", desc, (uintptr_t)bufa_check,
			    (uintptr_t)bufb_check, buflen_check);
	}
out:
	close(fd);
}

static void
bpf_test_ackzbuf(void *buf, u_int buflen)
{
	int fd, mode;

	fd = bpf_open();
	if (fd < 0)
		err(-1, "bpf_test_ackzbuf: bpf_open");
	mode = BPF_BUFMODE_ZBUF;
	if (ioctl(fd, BIOCSETBUFMODE, &mode) < 0)
		err(-1, "bpf_test_ackzbuf: ioctl(BIOCSETBUFMODE, "
		    "BPF_BUFMODE_ZBUF)");
	bpf_ackzbuf(fd, buf, buflen);
	close(fd);
}

int
main(int argc, char *argv[])
{
	size_t buflen_real, pagesize;
	char *bufa_real, *bufb_real;

	pagesize = getpagesize();
	buflen_real = pagesize * USE_BUFFER_PAGES;

	bufa_real = mmap(NULL, pagesize * ALLOC_BUFFER_PAGES,
	    PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
	if (bufa_real == MAP_FAILED)
		err(-1, "mmap");

	bufb_real = mmap(NULL, pagesize * ALLOC_BUFFER_PAGES,
	    PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
	if (bufb_real == MAP_FAILED)
		err(-1, "mmap");

#if 0
	/*
	 * Query and make sure zbufs are not configured up front.
	 */
	if (bpf_getzbuf(fd, &bufa, &bufb, &buflen) < 0)
		err(-1, "bpf_getzbuf");
#endif

	/*
	 * Make sure that attempts to set just one buffer are rejected.
	 */
	bpf_test_setzbuf("just_bufa", bufa_real, NULL, buflen_real, EINVAL);
	bpf_test_setzbuf("just_bufb", NULL, bufb_real, buflen_real, EINVAL);

	/*
	 * Set the aligned buffers with proper sizes.
	 */
	bpf_test_setzbuf("align_good", bufa_real, bufb_real, buflen_real, 0);

	/*
	 * Try some non-aligned values.
	 */
	bpf_test_setzbuf("nonalign_good", bufa_real, bufb_real, pagesize, 0);
	bpf_test_setzbuf("nonalign_bufa", bufa_real + 4, bufb_real, pagesize,
	    EINVAL);
	bpf_test_setzbuf("nonalign_bufb", bufa_real, bufb_real + 4, pagesize,
	    EINVAL);

	/*
	 * Try an invalid size.
	 */
	bpf_test_setzbuf("isize_good", bufa_real, bufb_real, pagesize, 0);
	bpf_test_setzbuf("isize_buflen", bufa_real, bufb_real, pagesize + 4,
	    EINVAL);

	/*
	 * Try a ridiculously large size (based on a priori knowledge of the
	 * size limit).
	 */
	bpf_test_setzbuf("size_toobig", bufa_real, bufb_real,
	    ALLOC_BUFFER_PAGES * pagesize, EINVAL);

	/*
	 * Confirm that page protection requirements are being properly
	 * implemented.
	 */
	if (munmap(bufa_real, pagesize * ALLOC_BUFFER_PAGES) < 0)
		err(-1, "munmap(bufa_real)");

	bufa_real = mmap(NULL, pagesize * ALLOC_BUFFER_PAGES, PROT_READ,
	    MAP_ANON, -1, 0);
	if (bufa_real == MAP_FAILED)
		err(-1, "mmap");

	bpf_test_setzbuf("prot_readonly", bufa_real, bufb_real, pagesize,
	    EFAULT);

#if 0
	/*
	 * At least i386 equated write access with read access, so this
	 * test fails by virtue of reporting successful read despite in
	 * principle having only write access.
	 */
	if (munmap(bufa_real, pagesize * ALLOC_BUFFER_PAGES) < 0)
		err(-1, "munmap(bufa_real)");

	bufa_real = mmap(NULL, pagesize * ALLOC_BUFFER_PAGES, PROT_WRITE,
	    MAP_ANON, -1, 0);
	if (bufa_real == MAP_FAILED)
		err(-1, "mmap");

	bpf_test_setzbuf("prot_writeonly", bufa_real, bufb_real, pagesize,
	    EFAULT);
#endif

	if (munmap(bufa_real, pagesize * ALLOC_BUFFER_PAGES) < 0)
		err(-1, "munmap(bufa_real)");

	bufa_real = mmap(NULL, pagesize * ALLOC_BUFFER_PAGES, 0, MAP_ANON,
	    -1, 0);
	if (bufa_real == MAP_FAILED)
		err(-1, "mmap");

	bpf_test_setzbuf("prot_none", bufa_real, bufb_real, pagesize,
	    EFAULT);

	/*
	 * Test other functions.
	 */
	bpf_test_ackzbuf(NULL, 0);

	return (0);
}
