/*
 * Copyright (C) 2012-2016 Sean Buckheister
 * Copyright (C) 2016 Nikos Mavrogiannopoulos
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * This file is part of GnuTLS.
 *
 * GnuTLS 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 3 of the License, or
 * (at your option) any later version.
 *
 * GnuTLS 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 GnuTLS.  If not, see <https://www.gnu.org/licenses/>.
 */

/*
 * DTLS stress test utility
 *
 * **** Available parameters ****
 *
 *	-nb		 enable nonblocking operations on sessions
 *	-batch	      read test identifiers from stdin and run them
 *	-d		  increase debug level by one
 *	-r		  replay messages (very crude replay mechanism)
 *	-d <n>	      set debug level to <n>
 *	-die		don't start new tests after the first detected failure
 *	-timeout <n>	set handshake timeout to <n> seconds. Tests that don't make progress
 *			    within twice this time will be forcibly killed. (default: 120)
 *	-retransmit <n>     set retransmit timeout to <n> milliseconds (default: 100)
 *	-j <n>	      run up to <n> tests in parallel
 *	-full	       use full handshake with mutual certificate authentication
 *	-resume	     use resumed handshake
 *	-shello <perm>      run only one test, with the server hello flight permuted as <perm>
 *	-sfinished <perm>   run only one test, with the server finished flight permuted as <perm>
 *	-cfinished <perm>   run only one test, with the client finished flight permuted as <perm>
 *	<packet name>       run only one test, drop <packet name> three times
 *			    valid values for <packet name> are:
 *				SHello, SCertificate, SKeyExchange, SCertificateRequest, SHelloDone,
 *				CCertificate, CKeyExchange, CCertificateVerify, CChangeCipherSpec,
 *				CFinished, SChangeCipherSpec, SFinished
 *			    using *Certificate* without -full will yield unexpected results
 *
 * 
 * **** Permutation handling ****
 *
 * Flight length for -sfinished is 2, for -shello and -cfinished they are 5 with -full, 3 otherwise.
 * Permutations are given with base 0 and specify the order in which reordered packets are transmitted.
 * For example, -full -shello 42130 will transmit server hello flight packets in the order
 * SHelloDone, SKeyExchange, SCertificate, SCertificateRequest, SHello
 *
 * When -resume is specified the -sfinished flight length is 3 (same as shello), cfinished is 2.
 * The -resume option has to be combined with sfinished or cfinished.
 *
 * **** Output format ****
 *
 * Every line printed for any given test is prefixed by a unique id for that test. See run_test_by_id for
 * exact composition. Errors encountered during execution are printed, with one status line after test
 * completen. The format for this line is as follows:
 *
 * <id> <status> SHello(<shperm>), SFinished(<sfinperm>), CFinished(<cfinperm>) :- <drops>
 *
 * The format for error lines is <id> <role>| <text>, with <role> being the role of the child process
 * that encountered the error, and <text> being obvious.
 *
 * <id> is the unique id for the test, it can be used as input to -batch.
 * <status> can be ++ for a successful test, -- for a failure, TT for a deadlock timeout killed test,
 *     or !! for a test has died due to some unforeseen circumstances like syscall failures.
 * <shperm>, <sfinperm>, <cfinperm> show the permutation for the respective flights used.
 *    They can be used as input to -shello, -sfinished, and -cfinished, respectively.
 * <drops> is a comma separated list of <packet name>, one for every packet dropped thrice
 *
 *
 * **** Exit status ****
 *
 * 0    all tests have passed
 * 1    some tests have failed
 * 4    the master processed has encountered unexpected errors
 * 8    error parsing command line
 */

#include "config.h"
#include <gnutls/gnutls.h>
#include <gnutls/openpgp.h>
#include <gnutls/dtls.h>
#include <unistd.h>
#include "../utils.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <assert.h>
#include <sys/wait.h>

#if _POSIX_TIMERS && (_POSIX_TIMERS - 200112L) >= 0

// {{{ types

#define log(fmt, ...) \
	if (debug)    \
	fprintf(stdout, "%i %s| " fmt, run_id, role_name, ##__VA_ARGS__)

typedef struct {
	int count;
} filter_packet_state_t;

typedef struct {
	const char *name;
	gnutls_datum_t packets[5];
	int *order;
	int count;
} filter_permute_state_t;

typedef void (*filter_fn)(gnutls_transport_ptr_t, const unsigned char *,
			  size_t);

typedef int (*match_fn)(const unsigned char *, size_t);

enum role {
	SERVER,
	CLIENT
};

// }}}

// {{{ static data

static int permutations2[2][2] = { { 0, 1 }, { 1, 0 } };

static const char *permutation_names2[] = { "01", "10", 0 };

static int permutations3[6][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 },
				   { 1, 2, 0 }, { 2, 0, 1 }, { 2, 1, 0 } };

static const char *permutation_names3[] = { "012", "021", "102", "120",
					    "201", "210", 0 };

static int permutations5[120][5] = {
	{ 0, 1, 2, 3, 4 }, { 0, 2, 1, 3, 4 }, { 1, 0, 2, 3, 4 },
	{ 1, 2, 0, 3, 4 }, { 2, 0, 1, 3, 4 }, { 2, 1, 0, 3, 4 },
	{ 0, 1, 3, 2, 4 }, { 0, 2, 3, 1, 4 }, { 1, 0, 3, 2, 4 },
	{ 1, 2, 3, 0, 4 }, { 2, 0, 3, 1, 4 }, { 2, 1, 3, 0, 4 },
	{ 0, 3, 1, 2, 4 }, { 0, 3, 2, 1, 4 }, { 1, 3, 0, 2, 4 },
	{ 1, 3, 2, 0, 4 }, { 2, 3, 0, 1, 4 }, { 2, 3, 1, 0, 4 },
	{ 3, 0, 1, 2, 4 }, { 3, 0, 2, 1, 4 }, { 3, 1, 0, 2, 4 },
	{ 3, 1, 2, 0, 4 }, { 3, 2, 0, 1, 4 }, { 3, 2, 1, 0, 4 },
	{ 0, 1, 2, 4, 3 }, { 0, 2, 1, 4, 3 }, { 1, 0, 2, 4, 3 },
	{ 1, 2, 0, 4, 3 }, { 2, 0, 1, 4, 3 }, { 2, 1, 0, 4, 3 },
	{ 0, 1, 3, 4, 2 }, { 0, 2, 3, 4, 1 }, { 1, 0, 3, 4, 2 },
	{ 1, 2, 3, 4, 0 }, { 2, 0, 3, 4, 1 }, { 2, 1, 3, 4, 0 },
	{ 0, 3, 1, 4, 2 }, { 0, 3, 2, 4, 1 }, { 1, 3, 0, 4, 2 },
	{ 1, 3, 2, 4, 0 }, { 2, 3, 0, 4, 1 }, { 2, 3, 1, 4, 0 },
	{ 3, 0, 1, 4, 2 }, { 3, 0, 2, 4, 1 }, { 3, 1, 0, 4, 2 },
	{ 3, 1, 2, 4, 0 }, { 3, 2, 0, 4, 1 }, { 3, 2, 1, 4, 0 },
	{ 0, 1, 4, 2, 3 }, { 0, 2, 4, 1, 3 }, { 1, 0, 4, 2, 3 },
	{ 1, 2, 4, 0, 3 }, { 2, 0, 4, 1, 3 }, { 2, 1, 4, 0, 3 },
	{ 0, 1, 4, 3, 2 }, { 0, 2, 4, 3, 1 }, { 1, 0, 4, 3, 2 },
	{ 1, 2, 4, 3, 0 }, { 2, 0, 4, 3, 1 }, { 2, 1, 4, 3, 0 },
	{ 0, 3, 4, 1, 2 }, { 0, 3, 4, 2, 1 }, { 1, 3, 4, 0, 2 },
	{ 1, 3, 4, 2, 0 }, { 2, 3, 4, 0, 1 }, { 2, 3, 4, 1, 0 },
	{ 3, 0, 4, 1, 2 }, { 3, 0, 4, 2, 1 }, { 3, 1, 4, 0, 2 },
	{ 3, 1, 4, 2, 0 }, { 3, 2, 4, 0, 1 }, { 3, 2, 4, 1, 0 },
	{ 0, 4, 1, 2, 3 }, { 0, 4, 2, 1, 3 }, { 1, 4, 0, 2, 3 },
	{ 1, 4, 2, 0, 3 }, { 2, 4, 0, 1, 3 }, { 2, 4, 1, 0, 3 },
	{ 0, 4, 1, 3, 2 }, { 0, 4, 2, 3, 1 }, { 1, 4, 0, 3, 2 },
	{ 1, 4, 2, 3, 0 }, { 2, 4, 0, 3, 1 }, { 2, 4, 1, 3, 0 },
	{ 0, 4, 3, 1, 2 }, { 0, 4, 3, 2, 1 }, { 1, 4, 3, 0, 2 },
	{ 1, 4, 3, 2, 0 }, { 2, 4, 3, 0, 1 }, { 2, 4, 3, 1, 0 },
	{ 3, 4, 0, 1, 2 }, { 3, 4, 0, 2, 1 }, { 3, 4, 1, 0, 2 },
	{ 3, 4, 1, 2, 0 }, { 3, 4, 2, 0, 1 }, { 3, 4, 2, 1, 0 },
	{ 4, 0, 1, 2, 3 }, { 4, 0, 2, 1, 3 }, { 4, 1, 0, 2, 3 },
	{ 4, 1, 2, 0, 3 }, { 4, 2, 0, 1, 3 }, { 4, 2, 1, 0, 3 },
	{ 4, 0, 1, 3, 2 }, { 4, 0, 2, 3, 1 }, { 4, 1, 0, 3, 2 },
	{ 4, 1, 2, 3, 0 }, { 4, 2, 0, 3, 1 }, { 4, 2, 1, 3, 0 },
	{ 4, 0, 3, 1, 2 }, { 4, 0, 3, 2, 1 }, { 4, 1, 3, 0, 2 },
	{ 4, 1, 3, 2, 0 }, { 4, 2, 3, 0, 1 }, { 4, 2, 3, 1, 0 },
	{ 4, 3, 0, 1, 2 }, { 4, 3, 0, 2, 1 }, { 4, 3, 1, 0, 2 },
	{ 4, 3, 1, 2, 0 }, { 4, 3, 2, 0, 1 }, { 4, 3, 2, 1, 0 }
};

static const char *permutation_names5[] = {
	"01234", "02134", "10234", "12034", "20134", "21034", "01324", "02314",
	"10324", "12304", "20314", "21304", "03124", "03214", "13024", "13204",
	"23014", "23104", "30124", "30214", "31024", "31204", "32014", "32104",
	"01243", "02143", "10243", "12043", "20143", "21043", "01342", "02341",
	"10342", "12340", "20341", "21340", "03142", "03241", "13042", "13240",
	"23041", "23140", "30142", "30241", "31042", "31240", "32041", "32140",
	"01423", "02413", "10423", "12403", "20413", "21403", "01432", "02431",
	"10432", "12430", "20431", "21430", "03412", "03421", "13402", "13420",
	"23401", "23410", "30412", "30421", "31402", "31420", "32401", "32410",
	"04123", "04213", "14023", "14203", "24013", "24103", "04132", "04231",
	"14032", "14230", "24031", "24130", "04312", "04321", "14302", "14320",
	"24301", "24310", "34012", "34021", "34102", "34120", "34201", "34210",
	"40123", "40213", "41023", "41203", "42013", "42103", "40132", "40231",
	"41032", "41230", "42031", "42130", "40312", "40321", "41302", "41320",
	"42301", "42310", "43012", "43021", "43102", "43120", "43201", "43210",
	0
};

static const char *filter_names[8] = { "SHello",
				       "SKeyExchange",
				       "SHelloDone",
				       "CKeyExchange",
				       "CChangeCipherSpec",
				       "CFinished",
				       "SChangeCipherSpec",
				       "SFinished" };

static const char *filter_names_resume[] = { "SHello", "SChangeCipherSpec",
					     "SFinished", "CChangeCipherSpec",
					     "CFinished" };

static const char *filter_names_full[12] = { "SHello",
					     "SCertificate",
					     "SKeyExchange",
					     "SCertificateRequest",
					     "SHelloDone",
					     "CCertificate",
					     "CKeyExchange",
					     "CCertificateVerify",
					     "CChangeCipherSpec",
					     "CFinished",
					     "SChangeCipherSpec",
					     "SFinished" };

#include "cert-common.h"

// }}}

// {{{ other global state

enum role role;

#define role_name (role == SERVER ? "server" : "client")

int debug;
int nonblock;
int replay;
int full;
int resume;
int timeout_seconds;
int retransmit_milliseconds;
int run_to_end;

int run_id;

// }}}

// {{{ logging and error handling

static void logfn(int level, const char *s)
{
	if (debug) {
		fprintf(stdout, "%i %s|<%i> %s", run_id, role_name, level, s);
	}
}

static void auditfn(gnutls_session_t session, const char *s)
{
	if (debug) {
		fprintf(stdout, "%i %s| %s", run_id, role_name, s);
	}
}

static void drop(const char *packet)
{
	if (debug) {
		log("dropping %s\n", packet);
	}
}

static int _process_error(int loc, int code, int die)
{
	if (code < 0 && (die || code != GNUTLS_E_AGAIN)) {
		fprintf(stdout, "%i <%s tls> line %i: %s", run_id, role_name,
			loc, gnutls_strerror(code));
		if (gnutls_error_is_fatal(code) || die) {
			fprintf(stdout, " (fatal)\n");
			exit(1);
		} else {
			fprintf(stdout, "\n");
		}
	}
	return code;
}

#define die_on_error(code) _process_error(__LINE__, code, 1)
#define process_error(code) _process_error(__LINE__, code, 0)

static void _process_error_or_timeout(int loc, int err, time_t tdiff)
{
	if (err < 0) {
		if (err != GNUTLS_E_TIMEDOUT || tdiff >= 60) {
			_process_error(loc, err, 0);
		} else {
			log("line %i: {spurious timeout} (fatal)", loc);
			exit(1);
		}
	}
}

#define process_error_or_timeout(code, tdiff) \
	_process_error_or_timeout(__LINE__, code, tdiff)

static void rperror(const char *name)
{
	fprintf(stdout, "%i %s| %s\n", run_id, role_name, name);
}

// }}}

// {{{ init, shared, and teardown code and data for packet stream filters

filter_packet_state_t state_packet_ServerHello = { 0 };
filter_packet_state_t state_packet_ServerCertificate = { 0 };
filter_packet_state_t state_packet_ServerKeyExchange = { 0 };
filter_packet_state_t state_packet_ServerCertificateRequest = { 0 };
filter_packet_state_t state_packet_ServerHelloDone = { 0 };
filter_packet_state_t state_packet_ClientCertificate = { 0 };
filter_packet_state_t state_packet_ClientKeyExchange = { 0 };
filter_packet_state_t state_packet_ClientCertificateVerify = { 0 };
filter_packet_state_t state_packet_ClientChangeCipherSpec = { 0 };
filter_packet_state_t state_packet_ClientFinished = { 0 };
filter_packet_state_t state_packet_ClientFinishedResume = { 0 };
filter_packet_state_t state_packet_ServerChangeCipherSpec = { 0 };
filter_packet_state_t state_packet_ServerFinished = { 0 };
filter_packet_state_t state_packet_ServerFinishedResume = { 0 };

static filter_permute_state_t state_permute_ServerHello = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ServerHelloFull = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ServerFinished = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ServerFinishedResume = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ClientFinished = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ClientFinishedResume = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};
static filter_permute_state_t state_permute_ClientFinishedFull = {
	0,
	{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
	0,
	0
};

filter_fn filter_chain[32];
int filter_current_idx;

static void filter_permute_state_free_buffer(filter_permute_state_t *state)
{
	unsigned int i;

	for (i = 0; i < sizeof(state->packets) / sizeof(state->packets[0]);
	     i++) {
		free(state->packets[i].data);
		state->packets[i].data = NULL;
	}
}

static void filter_clear_state(void)
{
	filter_current_idx = 0;

	filter_permute_state_free_buffer(&state_permute_ServerHello);
	filter_permute_state_free_buffer(&state_permute_ServerHelloFull);
	filter_permute_state_free_buffer(&state_permute_ServerFinished);
	filter_permute_state_free_buffer(&state_permute_ServerFinishedResume);
	filter_permute_state_free_buffer(&state_permute_ClientFinished);
	filter_permute_state_free_buffer(&state_permute_ClientFinishedResume);
	filter_permute_state_free_buffer(&state_permute_ClientFinishedFull);

	memset(&state_packet_ServerHello, 0, sizeof(state_packet_ServerHello));
	memset(&state_packet_ServerCertificate, 0,
	       sizeof(state_packet_ServerCertificate));
	memset(&state_packet_ServerKeyExchange, 0,
	       sizeof(state_packet_ServerKeyExchange));
	memset(&state_packet_ServerCertificateRequest, 0,
	       sizeof(state_packet_ServerCertificateRequest));
	memset(&state_packet_ServerHelloDone, 0,
	       sizeof(state_packet_ServerHelloDone));
	memset(&state_packet_ClientCertificate, 0,
	       sizeof(state_packet_ClientCertificate));
	memset(&state_packet_ClientKeyExchange, 0,
	       sizeof(state_packet_ClientKeyExchange));
	memset(&state_packet_ClientCertificateVerify, 0,
	       sizeof(state_packet_ClientCertificateVerify));
	memset(&state_packet_ClientChangeCipherSpec, 0,
	       sizeof(state_packet_ClientChangeCipherSpec));
	memset(&state_packet_ClientFinished, 0,
	       sizeof(state_packet_ClientFinished));
	memset(&state_packet_ClientFinishedResume, 0,
	       sizeof(state_packet_ClientFinishedResume));
	memset(&state_packet_ServerChangeCipherSpec, 0,
	       sizeof(state_packet_ServerChangeCipherSpec));
	memset(&state_packet_ServerFinished, 0,
	       sizeof(state_packet_ServerFinished));
	memset(&state_packet_ServerFinishedResume, 0,
	       sizeof(state_packet_ServerFinishedResume));
	memset(&state_permute_ServerHello, 0,
	       sizeof(state_permute_ServerHello));
	memset(&state_permute_ServerHelloFull, 0,
	       sizeof(state_permute_ServerHelloFull));
	memset(&state_permute_ServerFinished, 0,
	       sizeof(state_permute_ServerFinished));
	memset(&state_permute_ClientFinished, 0,
	       sizeof(state_permute_ClientFinished));
	memset(&state_permute_ClientFinishedResume, 0,
	       sizeof(state_permute_ClientFinishedResume));
	memset(&state_permute_ClientFinishedFull, 0,
	       sizeof(state_permute_ClientFinishedFull));

	state_permute_ServerHello.name = "ServerHello";
	state_permute_ServerHelloFull.name = "ServerHelloFull";
	state_permute_ServerFinished.name = "ServerFinished";
	state_permute_ServerFinishedResume.name = "ServerFinishedResume";
	state_permute_ClientFinished.name = "ClientFinished";
	state_permute_ClientFinishedResume.name = "ClientFinishedResume";
	state_permute_ClientFinishedFull.name = "ClientFinishedFull";
}

/* replay buffer */
static int rbuffer[5 * 1024];
unsigned rbuffer_size = 0;

static void filter_run_next(gnutls_transport_ptr_t fd,
			    const unsigned char *buffer, size_t len)
{
	int ret = 0;
	filter_fn fn = filter_chain[filter_current_idx];
	filter_current_idx++;
	if (fn) {
		fn(fd, buffer, len);
	} else {
		ret = send((int)(intptr_t)fd, buffer, len, 0);
	}
	filter_current_idx--;

	if (ret > 0 && replay != 0) {
		if (rbuffer_size == 0 && len < sizeof(rbuffer)) {
			memcpy(rbuffer, buffer, len);
			rbuffer_size = len;
		} else if (rbuffer_size != 0) {
			send((int)(intptr_t)fd, rbuffer, rbuffer_size, 0);
			if (len < sizeof(rbuffer) && len > rbuffer_size) {
				memcpy(rbuffer, buffer, len);
				rbuffer_size = len;
			}
		}
	}
}

// }}}

// {{{ packet match functions

static int match_ServerHello(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 2;
}

static int match_ServerCertificate(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 11;
}

static int match_ServerKeyExchange(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 12;
}

static int match_ServerCertificateRequest(const unsigned char *buffer,
					  size_t len)
{
	return role == SERVER && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 13;
}

static int match_ServerHelloDone(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 14;
}

static int match_ClientCertificate(const unsigned char *buffer, size_t len)
{
	return role == CLIENT && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 11;
}

static int match_ClientKeyExchange(const unsigned char *buffer, size_t len)
{
	return role == CLIENT && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 16;
}

static int match_ClientCertificateVerify(const unsigned char *buffer,
					 size_t len)
{
	return role == CLIENT && len >= 13 + 1 && buffer[0] == 22 &&
	       buffer[13] == 15;
}

static int match_ClientChangeCipherSpec(const unsigned char *buffer, size_t len)
{
	return role == CLIENT && len >= 13 && buffer[0] == 20;
}

static int match_ClientFinished(const unsigned char *buffer, size_t len)
{
	return role == CLIENT && len >= 13 && buffer[0] == 22 && buffer[4] == 1;
}

static int match_ServerChangeCipherSpec(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 && buffer[0] == 20;
}

static int match_ServerFinished(const unsigned char *buffer, size_t len)
{
	return role == SERVER && len >= 13 && buffer[0] == 22 && buffer[4] == 1;
}

// }}}

// {{{ packet drop filters

#define FILTER_DROP_COUNT 3
#define DECLARE_FILTER(packet)                                             \
	static void filter_packet_##packet(gnutls_transport_ptr_t fd,      \
					   const unsigned char *buffer,    \
					   size_t len)                     \
	{                                                                  \
		if (match_##packet(buffer, len) &&                         \
		    (state_packet_##packet).count++ < FILTER_DROP_COUNT) { \
			drop(#packet);                                     \
		} else {                                                   \
			filter_run_next(fd, buffer, len);                  \
		}                                                          \
	}

DECLARE_FILTER(ServerHello)
DECLARE_FILTER(ServerCertificate)
DECLARE_FILTER(ServerKeyExchange)
DECLARE_FILTER(ServerCertificateRequest)
DECLARE_FILTER(ServerHelloDone)
DECLARE_FILTER(ClientCertificate)
DECLARE_FILTER(ClientKeyExchange)
DECLARE_FILTER(ClientCertificateVerify)
DECLARE_FILTER(ClientChangeCipherSpec)
DECLARE_FILTER(ClientFinished)
DECLARE_FILTER(ServerChangeCipherSpec)
DECLARE_FILTER(ServerFinished)
// }}}
// {{{ flight permutation filters
static void filter_permute_state_run(filter_permute_state_t *state,
				     int packetCount, gnutls_transport_ptr_t fd,
				     const unsigned char *buffer, size_t len)
{
	unsigned char *data;
	int packet = state->order[state->count];

	if (debug > 2)
		log("running permutation for %s/%d/%d\n", state->name,
		    packetCount, state->count);

	data = malloc(len);
	assert(data);
	memcpy(data, buffer, len);
	state->packets[packet].data = data;
	state->packets[packet].size = len;
	state->count++;

	if (state->count == packetCount) {
		for (packet = 0; packet < packetCount; packet++) {
			filter_run_next(fd, state->packets[packet].data,
					state->packets[packet].size);
		}
		filter_permute_state_free_buffer(state);
		state->count = 0;
	}
}

#define DECLARE_PERMUTE(flight)                                             \
	static void filter_permute_##flight(gnutls_transport_ptr_t fd,      \
					    const unsigned char *buffer,    \
					    size_t len)                     \
	{                                                                   \
		int count = sizeof(permute_match_##flight) /                \
			    sizeof(permute_match_##flight[0]);              \
		int i;                                                      \
		for (i = 0; i < count; i++) {                               \
			if (permute_match_##flight[i](buffer, len)) {       \
				filter_permute_state_run(                   \
					&state_permute_##flight, count, fd, \
					buffer, len);                       \
				return;                                     \
			}                                                   \
		}                                                           \
		filter_run_next(fd, buffer, len);                           \
	}

static match_fn permute_match_ServerHello[] = { match_ServerHello,
						match_ServerKeyExchange,
						match_ServerHelloDone };

static match_fn permute_match_ServerHelloFull[] = {
	match_ServerHello, match_ServerCertificate, match_ServerKeyExchange,
	match_ServerCertificateRequest, match_ServerHelloDone
};

static match_fn permute_match_ServerFinished[] = { match_ServerChangeCipherSpec,
						   match_ServerFinished };

static match_fn permute_match_ServerFinishedResume[] = {
	match_ServerHello, match_ServerChangeCipherSpec, match_ServerFinished
};

static match_fn permute_match_ClientFinished[] = { match_ClientKeyExchange,
						   match_ClientChangeCipherSpec,
						   match_ClientFinished };

static match_fn permute_match_ClientFinishedResume[] = {
	match_ClientChangeCipherSpec, match_ClientFinished
};

static match_fn permute_match_ClientFinishedFull[] = {
	match_ClientCertificate, match_ClientKeyExchange,
	match_ClientCertificateVerify, match_ClientChangeCipherSpec,
	match_ClientFinished
};

DECLARE_PERMUTE(ServerHello)
DECLARE_PERMUTE(ServerHelloFull)
DECLARE_PERMUTE(ServerFinishedResume)
DECLARE_PERMUTE(ServerFinished)
DECLARE_PERMUTE(ClientFinished)
DECLARE_PERMUTE(ClientFinishedResume)
DECLARE_PERMUTE(ClientFinishedFull)
// }}}
// {{{ emergency deadlock resolution time bomb
timer_t killtimer_tid = 0;

static void killtimer_set(void)
{
	struct sigevent sig;
	struct itimerspec tout = { { 0, 0 }, { 2 * timeout_seconds, 0 } };

	if (killtimer_tid != 0) {
		timer_delete(killtimer_tid);
	}

	memset(&sig, 0, sizeof(sig));
	sig.sigev_notify = SIGEV_SIGNAL;
	sig.sigev_signo = 15;
	if (timer_create(CLOCK_MONOTONIC, &sig, &killtimer_tid) < 0) {
		rperror("timer_create");
		exit(3);
	}

	timer_settime(killtimer_tid, 0, &tout, 0);
}

// }}}

// {{{ actual gnutls operations

gnutls_certificate_credentials_t cred;
gnutls_session_t session;

static ssize_t writefn(gnutls_transport_ptr_t fd, const void *buffer,
		       size_t len)
{
	filter_run_next(fd, (const unsigned char *)buffer, len);
	return len;
}

static void await(int fd, int timeout)
{
	if (nonblock) {
		struct pollfd p = { fd, POLLIN, 0 };
		if (poll(&p, 1, timeout) < 0 && errno != EAGAIN &&
		    errno != EINTR) {
			rperror("poll");
			exit(3);
		}
	}
}

static void cred_init(void)
{
	assert(gnutls_certificate_allocate_credentials(&cred) >= 0);

	gnutls_certificate_set_x509_key_mem(cred, &cli_ca3_cert, &cli_ca3_key,
					    GNUTLS_X509_FMT_PEM);
}

static void session_init(int sock, int server)
{
	gnutls_init(&session, GNUTLS_DATAGRAM |
				      (server ? GNUTLS_SERVER : GNUTLS_CLIENT) |
				      GNUTLS_NONBLOCK * nonblock);
	gnutls_priority_set_direct(session, "NORMAL:+ECDHE-RSA:+ANON-ECDH", 0);
	gnutls_transport_set_int(session, sock);

	if (full) {
		gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
		if (server) {
			gnutls_certificate_server_set_request(
				session, GNUTLS_CERT_REQUIRE);
		}
	} else if (server) {
		gnutls_anon_server_credentials_t acred;
		assert(gnutls_anon_allocate_server_credentials(&acred) >= 0);
		gnutls_credentials_set(session, GNUTLS_CRD_ANON, acred);
	} else {
		gnutls_anon_client_credentials_t acred;
		assert(gnutls_anon_allocate_client_credentials(&acred) >= 0);
		gnutls_credentials_set(session, GNUTLS_CRD_ANON, acred);
	}

	gnutls_dtls_set_mtu(session, 1400);
	gnutls_dtls_set_timeouts(session, retransmit_milliseconds,
				 timeout_seconds * 1000);
}

static void client(int sock)
{
	int err = 0;
	time_t started = time(0);
	const char *line = "foobar!";
	char buffer[8192];
	int len, ret;
	gnutls_datum_t data = { NULL, 0 };

	session_init(sock, 0);

	killtimer_set();

	if (resume) {
		do {
			err = process_error(gnutls_handshake(session));
			if (err != 0) {
				int t = gnutls_dtls_get_timeout(session);
				await(sock, t ? t : 100);
			}
		} while (err != 0);
		process_error_or_timeout(err, time(0) - started);

		ret = gnutls_session_get_data2(session, &data);
		if (ret < 0) {
			exit(1);
		}
		gnutls_deinit(session);

		session_init(sock, 0);
		gnutls_session_set_data(session, data.data, data.size);
		gnutls_free(data.data);
		data.data = NULL;

		if (debug) {
			fprintf(stdout, "%i %s| initial handshake complete\n",
				run_id, role_name);
		}
	}

	gnutls_transport_set_push_function(session, writefn);

	killtimer_set();
	do {
		err = process_error(gnutls_handshake(session));
		if (err != 0) {
			int t = gnutls_dtls_get_timeout(session);
			await(sock, t ? t : 100);
		}
	} while (err != 0);
	process_error_or_timeout(err, time(0) - started);

	if (debug) {
		fprintf(stdout, "%i %s| handshake complete\n", run_id,
			role_name);
	}

	if (resume) {
		killtimer_set();

		do {
			await(sock, -1);
			len = process_error(gnutls_record_recv(session, buffer,
							       sizeof(buffer)));
		} while (len < 0);

		log("received data\n");

		die_on_error(gnutls_record_send(session, buffer, len));

		log("sent data\n");
		exit(0);

	} else {
		killtimer_set();
		die_on_error(gnutls_record_send(session, line, strlen(line)));

		log("sent data\n");

		do {
			await(sock, -1);
			len = process_error(gnutls_record_recv(session, buffer,
							       sizeof(buffer)));
		} while (len < 0);

		log("received data\n");

		if (len > 0 && strncmp(line, buffer, len) == 0) {
			exit(0);
		} else {
			exit(1);
		}
	}
}

static gnutls_datum_t saved_data = { NULL, 0 };

static gnutls_datum_t db_fetch(void *dbf, gnutls_datum_t key)
{
	gnutls_datum_t t = { NULL, 0 };
	t.data = malloc(saved_data.size);
	if (t.data == NULL)
		return t;
	memcpy(t.data, saved_data.data, saved_data.size);
	t.size = saved_data.size;

	return t;
}

static int db_delete(void *dbf, gnutls_datum_t key)
{
	return 0;
}

static int db_store(void *dbf, gnutls_datum_t key, gnutls_datum_t data)
{
	saved_data.data = malloc(data.size);
	if (saved_data.data == NULL)
		return -1;
	memcpy(saved_data.data, data.data, data.size);
	saved_data.size = data.size;
	return 0;
}

static void server(int sock)
{
	int err;
	const char *line = "server foobar!";
	time_t started = time(0);
	char buffer[8192];
	int len;

	session_init(sock, 1);

	await(sock, -1);

	killtimer_set();
	if (resume) {
		gnutls_db_set_retrieve_function(session, db_fetch);
		gnutls_db_set_store_function(session, db_store);
		gnutls_db_set_remove_function(session, db_delete);
		gnutls_db_set_ptr(session, NULL);

		do {
			err = process_error(gnutls_handshake(session));
			if (err != 0) {
				int t = gnutls_dtls_get_timeout(session);
				await(sock, t ? t : 100);
			}
		} while (err != 0);
		process_error_or_timeout(err, time(0) - started);

		gnutls_deinit(session);

		session_init(sock, 1);
		gnutls_db_set_retrieve_function(session, db_fetch);
		gnutls_db_set_store_function(session, db_store);
		gnutls_db_set_remove_function(session, db_delete);
		gnutls_db_set_ptr(session, NULL);

		if (debug) {
			fprintf(stdout, "%i %s| initial handshake complete\n",
				run_id, role_name);
		}
	}

	gnutls_transport_set_push_function(session, writefn);

	await(sock, -1);

	killtimer_set();
	do {
		err = process_error(gnutls_handshake(session));
		if (err != 0) {
			int t = gnutls_dtls_get_timeout(session);
			await(sock, t ? t : 100);
		}
	} while (err != 0);
	process_error_or_timeout(err, time(0) - started);

	log("handshake complete\n");

	if (resume) {
		free(saved_data.data);
		saved_data.data = NULL;
	}

	if (resume) {
		killtimer_set();
		die_on_error(gnutls_record_send(session, line, strlen(line)));

		log("sent data\n");

		do {
			await(sock, -1);
			len = process_error(gnutls_record_recv(session, buffer,
							       sizeof(buffer)));
		} while (len < 0);

		log("received data\n");

		if (len > 0 && strncmp(line, buffer, len) == 0) {
			exit(0);
		} else {
			exit(1);
		}
	} else {
		killtimer_set();

		do {
			await(sock, -1);
			len = process_error(gnutls_record_recv(session, buffer,
							       sizeof(buffer)));
		} while (len < 0);

		log("received data\n");

		die_on_error(gnutls_record_send(session, buffer, len));

		log("sent data\n");
	}

	exit(0);
}

// }}}

// {{{ test running/handling itself

#if 0
static void udp_sockpair(int *socks)
{
	struct sockaddr_in6 sa =
	    { AF_INET6, htons(30000), 0, in6addr_loopback, 0 };
	struct sockaddr_in6 sb =
	    { AF_INET6, htons(20000), 0, in6addr_loopback, 0 };

	socks[0] = socket(AF_INET6, SOCK_DGRAM, 0);
	socks[1] = socket(AF_INET6, SOCK_DGRAM, 0);

	bind(socks[0], (struct sockaddr *)&sa, sizeof(sa));
	bind(socks[1], (struct sockaddr *)&sb, sizeof(sb));

	connect(socks[1], (struct sockaddr *)&sa, sizeof(sa));
	connect(socks[0], (struct sockaddr *)&sb, sizeof(sb));
}
#endif

static int run_test(void)
{
	int fds[2];
	int pid1, pid2;
	int status2;

	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) {
		rperror("socketpair");
		exit(2);
	}

	if (nonblock) {
		fcntl(fds[0], F_SETFL, (long)O_NONBLOCK);
		fcntl(fds[1], F_SETFL, (long)O_NONBLOCK);
	}

	if (!(pid1 = fork())) {
		role = SERVER;
		server(fds[1]); // noreturn
	} else if (pid1 < 0) {
		rperror("fork server");
		exit(2);
	}
	if (!(pid2 = fork())) {
		role = CLIENT;
		client(fds[0]); // noreturn
	} else if (pid2 < 0) {
		rperror("fork client");
		exit(2);
	}
	while (waitpid(pid2, &status2, 0) < 0 && errno == EINTR)
		;
	kill(pid1, 15);
	while (waitpid(pid1, 0, 0) < 0 && errno == EINTR)
		;

	close(fds[0]);
	close(fds[1]);

	if (!WIFSIGNALED(status2) && WEXITSTATUS(status2) != 3) {
		return !!WEXITSTATUS(status2);
	} else {
		return 3;
	}
}

static filter_fn filters[] = { filter_packet_ServerHello,
			       filter_packet_ServerKeyExchange,
			       filter_packet_ServerHelloDone,
			       filter_packet_ClientKeyExchange,
			       filter_packet_ClientChangeCipherSpec,
			       filter_packet_ClientFinished,
			       filter_packet_ServerChangeCipherSpec,
			       filter_packet_ServerFinished };

static filter_fn filters_resume[] = { filter_packet_ServerHello,
				      filter_packet_ServerChangeCipherSpec,
				      filter_packet_ServerFinished,
				      filter_packet_ClientChangeCipherSpec,
				      filter_packet_ClientFinished };

static filter_fn filters_full[] = { filter_packet_ServerHello,
				    filter_packet_ServerCertificate,
				    filter_packet_ServerKeyExchange,
				    filter_packet_ServerCertificateRequest,
				    filter_packet_ServerHelloDone,
				    filter_packet_ClientCertificate,
				    filter_packet_ClientKeyExchange,
				    filter_packet_ClientCertificateVerify,
				    filter_packet_ClientChangeCipherSpec,
				    filter_packet_ClientFinished,
				    filter_packet_ServerChangeCipherSpec,
				    filter_packet_ServerFinished };

static int run_one_test(int dropMode, int serverFinishedPermute,
			int serverHelloPermute, int clientFinishedPermute)
{
	int fnIdx = 0;
	int res, filterIdx;
	filter_fn *local_filters;
	const char **local_filter_names;
	const char **client_finished_permutation_names;
	const char **server_finished_permutation_names;
	const char **server_hello_permutation_names;
	int filter_count;

	if (full) {
		local_filters = filters_full;
		local_filter_names = filter_names_full;
		filter_count = sizeof(filters_full) / sizeof(filters_full[0]);
		client_finished_permutation_names = permutation_names5;
		server_finished_permutation_names = permutation_names2;
		server_hello_permutation_names = permutation_names5;
	} else if (resume) {
		local_filters = filters_resume;
		local_filter_names = filter_names_resume;
		filter_count =
			sizeof(filters_resume) / sizeof(filters_resume[0]);
		client_finished_permutation_names = permutation_names2;
		server_finished_permutation_names = permutation_names3;
		server_hello_permutation_names = NULL;
	} else {
		local_filters = filters;
		local_filter_names = filter_names;
		filter_count = sizeof(filters) / sizeof(filters[0]);
		client_finished_permutation_names = permutation_names3;
		server_finished_permutation_names = permutation_names2;
		server_hello_permutation_names = permutation_names3;
	}

	run_id = ((dropMode * 2 + serverFinishedPermute) * (full ? 120 : 6) +
		  serverHelloPermute) *
			 (full ? 120 : 6) +
		 clientFinishedPermute;

	filter_clear_state();

	if (full) {
		filter_chain[fnIdx++] = filter_permute_ServerHelloFull;
		state_permute_ServerHelloFull.order =
			permutations5[serverHelloPermute];

		filter_chain[fnIdx++] = filter_permute_ClientFinishedFull;
		state_permute_ClientFinishedFull.order =
			permutations5[clientFinishedPermute];

		filter_chain[fnIdx++] = filter_permute_ServerFinished;
		state_permute_ServerFinished.order =
			permutations2[serverFinishedPermute];
	} else if (resume) {
		filter_chain[fnIdx++] = filter_permute_ServerFinishedResume;
		state_permute_ServerFinishedResume.order =
			permutations3[serverFinishedPermute];

		filter_chain[fnIdx++] = filter_permute_ClientFinishedResume;
		state_permute_ClientFinishedResume.order =
			permutations2[clientFinishedPermute];
	} else {
		filter_chain[fnIdx++] = filter_permute_ServerHello;
		state_permute_ServerHello.order =
			permutations3[serverHelloPermute];

		filter_chain[fnIdx++] = filter_permute_ClientFinished;
		state_permute_ClientFinished.order =
			permutations3[clientFinishedPermute];

		filter_chain[fnIdx++] = filter_permute_ServerFinished;
		state_permute_ServerFinished.order =
			permutations2[serverFinishedPermute];
	}

	if (dropMode) {
		for (filterIdx = 0; filterIdx < filter_count; filterIdx++) {
			if (dropMode & (1 << filterIdx)) {
				filter_chain[fnIdx++] =
					local_filters[filterIdx];
			}
		}
	}
	filter_chain[fnIdx++] = NULL;

	res = run_test();

	switch (res) {
	case 0:
		fprintf(stdout, "%i ++ ", run_id);
		break;
	case 1:
		fprintf(stdout, "%i -- ", run_id);
		break;
	case 2:
		fprintf(stdout, "%i !! ", run_id);
		break;
	case 3:
		fprintf(stdout, "%i TT ", run_id);
		break;
	}

	if (!resume)
		fprintf(stdout, "SHello(%s), ",
			server_hello_permutation_names[serverHelloPermute]);
	fprintf(stdout, "SFinished(%s), ",
		server_finished_permutation_names[serverFinishedPermute]);
	fprintf(stdout, "CFinished(%s) :- ",
		client_finished_permutation_names[clientFinishedPermute]);
	if (dropMode) {
		for (filterIdx = 0; filterIdx < filter_count; filterIdx++) {
			if (dropMode & (1 << filterIdx)) {
				if (dropMode & ((1 << filterIdx) - 1)) {
					fprintf(stdout, ", ");
				}
				fprintf(stdout, "%s",
					local_filter_names[filterIdx]);
			}
		}
	}
	fprintf(stdout, "\n");

	return res;
}

static int run_test_by_id(int id)
{
	int pscale = full ? 120 : 6;
	int dropMode, serverFinishedPermute, serverHelloPermute,
		clientFinishedPermute;

	clientFinishedPermute = id % pscale;
	id /= pscale;

	serverHelloPermute = id % pscale;
	id /= pscale;

	serverFinishedPermute = id % 2;
	id /= 2;

	dropMode = id;

	return run_one_test(dropMode, serverFinishedPermute, serverHelloPermute,
			    clientFinishedPermute);
}

int *job_pids;
int job_limit;
int children = 0;

static void register_child(int pid)
{
	int idx;

	children++;
	for (idx = 0; idx < job_limit; idx++) {
		if (job_pids[idx] == 0) {
			job_pids[idx] = pid;
			return;
		}
	}
}

static int wait_children(int child_limit)
{
	int fail = 0;
	int result = 1;

	while (children > child_limit) {
		int status;
		int idx;
		int pid = waitpid(0, &status, 0);
		if (pid < 0 && errno == ECHILD) {
			break;
		}
		for (idx = 0; idx < job_limit; idx++) {
			if (job_pids[idx] == pid) {
				children--;
				if (WEXITSTATUS(status)) {
					result = 1;
					if (!run_to_end && !fail) {
						fprintf(stderr,
							"One test failed, waiting for remaining tests\n");
						fail = 1;
						child_limit = 0;
					}
				}
				job_pids[idx] = 0;
				break;
			}
		}
	}

	if (fail) {
		exit(1);
	}

	return result;
}

static int run_tests_from_id_list(int childcount)
{
	int test_id;
	int ret;
	int result = 0;

	while ((ret = fscanf(stdin, "%i\n", &test_id)) > 0) {
		int pid;
		if (test_id < 0 || test_id > 2 * (full ? 120 * 120 * (1 << 12) :
							 6 * 6 * 256)) {
			fprintf(stderr, "Invalid test id %i\n", test_id);
			break;
		}
		if (!(pid = fork())) {
			exit(run_test_by_id(test_id));
		} else if (pid < 0) {
			rperror("fork");
			result = 4;
			break;
		} else {
			register_child(pid);
			result |= wait_children(childcount);
		}
	}

	if (ret < 0 && ret != EOF) {
		fprintf(stderr, "Error reading test id list\n");
	}

	result |= wait_children(0);

	return result;
}

static int run_all_tests(int childcount)
{
	int dropMode, serverFinishedPermute, serverHelloPermute,
		clientFinishedPermute;
	int result = 0;

	for (dropMode = 0; dropMode != 1 << (full ? 12 : 8); dropMode++)
		for (serverFinishedPermute = 0; serverFinishedPermute < 2;
		     serverFinishedPermute++)
			for (serverHelloPermute = 0;
			     serverHelloPermute < (full ? 120 : 6);
			     serverHelloPermute++)
				for (clientFinishedPermute = 0;
				     clientFinishedPermute < (full ? 120 : 6);
				     clientFinishedPermute++) {
					int pid;
					if (!(pid = fork())) {
						exit(run_one_test(
							dropMode,
							serverFinishedPermute,
							serverHelloPermute,
							clientFinishedPermute));
					} else if (pid < 0) {
						rperror("fork");
						result = 4;
						break;
					} else {
						register_child(pid);
						result |= wait_children(
							childcount);
					}
				}

	result |= wait_children(0);

	return result;
}

// }}}

static int parse_permutation(const char *arg, const char *permutations[],
			     int *val)
{
	*val = 0;
	while (permutations[*val]) {
		if (strcmp(permutations[*val], arg) == 0) {
			return 1;
		} else {
			*val += 1;
		}
	}
	return 0;
}

int main(int argc, const char *argv[])
{
	int dropMode = 0;
	int serverFinishedPermute = 0;
	int serverHelloPermute = 0;
	int clientFinishedPermute = 0;
	int batch = 0;
	unsigned single = 0;
	int arg;

	nonblock = 0;
	replay = 0;
	debug = 0;
	timeout_seconds = 120;
	retransmit_milliseconds = 100;
	full = 0;
	run_to_end = 1;
	job_limit = 1;

#define NEXT_ARG(name)                                                   \
	do {                                                             \
		if (++arg >= argc) {                                     \
			fprintf(stderr, "No argument for -" #name "\n"); \
			exit(8);                                         \
		}                                                        \
	} while (0);
#define FAIL_ARG(name)                                                \
	do {                                                          \
		fprintf(stderr, "Invalid argument for -" #name "\n"); \
		exit(8);                                              \
	} while (0);

	for (arg = 1; arg < argc; arg++) {
		if (strcmp("-die", argv[arg]) == 0) {
			run_to_end = 0;
		} else if (strcmp("-batch", argv[arg]) == 0) {
			batch = 1;
		} else if (strcmp("-d", argv[arg]) == 0) {
			char *end;
			int level;

			if (arg + 1 < argc) {
				level = strtol(argv[arg + 1], &end, 10);
				if (*end == '\0') {
					debug = level;
					arg++;
				} else
					debug++;
			} else {
				debug++;
			}
		} else if (strcmp("-nb", argv[arg]) == 0) {
			nonblock = 1;
		} else if (strcmp("-r", argv[arg]) == 0) {
			replay = 1;
		} else if (strcmp("-timeout", argv[arg]) == 0) {
			char *end;
			int val;

			NEXT_ARG(timeout);
			val = strtol(argv[arg], &end, 10);
			if (*end == '\0') {
				timeout_seconds = val;
			} else {
				FAIL_ARG(timeout);
			}
		} else if (strcmp("-retransmit", argv[arg]) == 0) {
			char *end;
			int val;

			NEXT_ARG(retransmit);
			val = strtol(argv[arg], &end, 10);
			if (*end == '\0') {
				retransmit_milliseconds = val;
			} else {
				FAIL_ARG(retransmit);
			}
		} else if (strcmp("-j", argv[arg]) == 0) {
			char *end;
			int val;

			NEXT_ARG(timeout);
			val = strtol(argv[arg], &end, 10);
			if (*end == '\0') {
				job_limit = val;
			} else {
				FAIL_ARG(j);
			}
		} else if (strcmp("-full", argv[arg]) == 0) {
			if (resume) {
				fprintf(stderr,
					"You cannot combine full with resume\n");
				exit(1);
			}

			full = 1;
		} else if (strcmp("-resume", argv[arg]) == 0) {
			if (full) {
				fprintf(stderr,
					"You cannot combine full with resume\n");
				exit(1);
			}

			resume = 1;
		} else if (strcmp("-shello", argv[arg]) == 0) {
			if (resume) {
				fprintf(stderr,
					"Please use -sfinished instead of -shello\n");
				exit(1);
			}

			NEXT_ARG(shello);
			if (!parse_permutation(argv[arg],
					       full ? permutation_names5 :
						      permutation_names3,
					       &serverHelloPermute)) {
				FAIL_ARG(shell);
			}
			single++;
		} else if (strcmp("-sfinished", argv[arg]) == 0) {
			const char **pname;
			NEXT_ARG(cfinished);
			if (resume)
				pname = permutation_names3;
			else
				pname = permutation_names2;
			if (!parse_permutation(argv[arg], pname,
					       &serverFinishedPermute)) {
				FAIL_ARG(cfinished);
			}
			single++;
		} else if (strcmp("-cfinished", argv[arg]) == 0) {
			const char **pname;
			NEXT_ARG(cfinished);
			if (full)
				pname = permutation_names5;
			else if (resume)
				pname = permutation_names2;
			else
				pname = permutation_names3;
			if (!parse_permutation(argv[arg], pname,
					       &clientFinishedPermute)) {
				FAIL_ARG(cfinished);
			}
			single++;
		} else {
			int drop;
			int filter_count;
			const char **local_filter_names;

			if (full) {
				local_filter_names = filter_names_full;
				filter_count = sizeof(filters_full) /
					       sizeof(filters_full[0]);
			} else if (resume) {
				local_filter_names = filter_names_resume;
				filter_count = sizeof(filters_resume) /
					       sizeof(filters_resume[0]);
			} else {
				local_filter_names = filter_names;
				filter_count =
					sizeof(filters) / sizeof(filters[0]);
			}

			for (drop = 0; drop < filter_count; drop++) {
				if (strcmp(local_filter_names[drop],
					   argv[arg]) == 0) {
					dropMode |= (1 << drop);
					break;
				}
			}
			if (drop == filter_count) {
				fprintf(stderr, "Unknown packet %s\n",
					argv[arg]);
				exit(8);
			}
			single++;
		}
	}

	setlinebuf(stdout);
	global_init();
	cred_init();
	gnutls_global_set_log_function(logfn);
	gnutls_global_set_audit_log_function(auditfn);
	gnutls_global_set_log_level(debug);

	if (single) {
		if (debug)
			fprintf(stderr, "single test mode\n");
		return run_one_test(dropMode, serverFinishedPermute,
				    serverHelloPermute, clientFinishedPermute);
	} else {
		if (debug)
			fprintf(stderr, "multi test mode\n");

		if (resume) {
			fprintf(stderr,
				"full run not implemented yet for resumed runs\n");
			exit(5);
		}

		job_pids = calloc(sizeof(int), job_limit);
		if (batch) {
			return run_tests_from_id_list(job_limit);
		} else {
			return run_all_tests(job_limit);
		}
	}
}

// vim: foldmethod=marker

#else /* NO POSIX TIMERS */

int main(int argc, const char *argv[])
{
	exit(77);
}

#endif
