/*
**  Copyright (c) 2005-2008 Sendmail, Inc. and its suppliers.
**    All rights reserved.
**
**  Copyright (c) 2009, 2011-2013, The Trusted Domain Project.
**    All rights reserved.
*/

#include "build-config.h"

/* system includes */
#include <sys/types.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sysexits.h>

/* libopendkim includes */
#include "../dkim.h"
#include "t-testdata.h"

#define	DEFMSGSIZE	1024
#define	DEFTESTINT	5
#define	BODYBUFRSZ	8192
#define	MAXHEADER	4096

#ifndef MIN
# define MIN(x,y)	((x) < (y) ? (x) : (y))
#endif /* ! MIN */

char *progname;

/*
**  CANON_CODE -- convert a canonicalization name to its code
**
**  Parameters:
**  	name -- name to convert
**
**  Return value:
**  	dkim_canon_t
*/

static dkim_canon_t
canon_code(char *name)
{
	if (name == NULL)
		return (dkim_canon_t) DKIM_CANON_UNKNOWN;
	else if (strcasecmp(name, "simple") == 0)
		return (dkim_canon_t) DKIM_CANON_SIMPLE;
	else if (strcasecmp(name, "relaxed") == 0)
		return (dkim_canon_t) DKIM_CANON_RELAXED;
	else
		return (dkim_canon_t) DKIM_CANON_UNKNOWN;
}

/*
**  CANON_NAME -- convert a canonicalization code to its name
**
**  Parameters:
**  	code -- code to convert
**
**  Return value:
**  	Pointer to name string.
*/

static const char *
canon_name(dkim_canon_t code)
{
	switch (code)
	{
	  case DKIM_CANON_SIMPLE:
		return "simple";

	  case DKIM_CANON_RELAXED:
		return "relaxed";

	  case DKIM_CANON_UNKNOWN:
	  default:
		return "unknown";
	}
}

/*
**  ALG_CODE -- convert an algorithm name to its code
**
**  Parameters:
**  	name -- name to convert
**
**  Return value:
**  	dkim_alg_t
*/

static dkim_alg_t
alg_code(char *name)
{
	if (name == NULL)
		return (dkim_alg_t) DKIM_SIGN_UNKNOWN;
	else if (strcasecmp(name, "rsa-sha1") == 0)
		return (dkim_alg_t) DKIM_SIGN_RSASHA1;
	else if (strcasecmp(name, "rsa-sha256") == 0)
		return (dkim_alg_t) DKIM_SIGN_RSASHA256;
	else if (strcasecmp(name, "ed25519-sha256") == 0)
		return (dkim_alg_t) DKIM_SIGN_ED25519SHA256;
	else
		return (dkim_alg_t) DKIM_SIGN_UNKNOWN;
}

/*
**  ALG_NAME -- convert an algorithm code to its name
**
**  Parameters:
**  	code -- code to convert
**
**  Return value:
**  	Pointer to name string.
*/

static const char *
alg_name(dkim_alg_t code)
{
	switch (code)
	{
	  case DKIM_SIGN_DEFAULT:
		return "default";

	  case DKIM_SIGN_RSASHA1:
		return "rsa-sha1";

	  case DKIM_SIGN_RSASHA256:
		return "rsa-sha256";

	  case DKIM_SIGN_ED25519SHA256:
		  return "ed25519-sha256";

	  case DKIM_SIGN_UNKNOWN:
	  default:
		return "unknown";
	}
}

/*
**  USAGE -- print usage message
**
**  Parameters:
**  	None.
**
**  Return value:
**  	EX_USAGE
*/

static int
usage(void)
{
	fprintf(stderr, "%s: usage: %s [options]\n"
	        "\t-b bodycanon\tbody canonicalization to use\n"
	        "\t-h hdrcanon \theader canonicalization to use\n"
	        "\t-m bytes    \tmessage size in bytes\n"
	        "\t-s signalg  \tsigning algorithm to use\n"
	        "\t-t seconds  \ttest time in seconds\n",
	        progname, progname);

	return EX_USAGE;
}

/*
**  MAIN -- program mainline
**
**  Parameters:
**  	The usual.
**
**  Return value:
**  	Exit status.
*/

int
main(int argc, char **argv)
{
	DKIM_STAT status;
	u_int signcnt = 0;
	int c;
	int w;
	int rate;
	size_t msgsize = DEFMSGSIZE;
	size_t msgrem;
	size_t wsz;
	char *p;
	DKIM *dkim;
	DKIM_LIB *lib;
	dkim_sigkey_t key;
	int keybits;
	const char *selector;
	unsigned int seed;
	unsigned char hdr[MAXHEADER + 1];
	unsigned char body[BODYBUFRSZ];
	time_t start = DEFTESTINT;
	time_t testint = DEFTESTINT;
	dkim_canon_t hcanon = DKIM_CANON_RELAXED;
	dkim_canon_t bcanon = DKIM_CANON_SIMPLE;
	dkim_alg_t signalg = DKIM_SIGN_UNKNOWN;

	progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;

	while ((c = getopt(argc, argv, "b:h:m:s:t:")) != -1)
	{
		switch (c)
		{
		  case 'b':
			bcanon = canon_code(optarg);
			if (bcanon == (dkim_canon_t) -1)
			{
				fprintf(stderr,
				        "%s: unknown canonicalization '%s'\n",
				        progname, optarg);
				return EX_USAGE;
			}
			break;

		  case 'h':
			hcanon = canon_code(optarg);
			if (hcanon == (dkim_canon_t) -1)
			{
				fprintf(stderr,
				        "%s: unknown canonicalization '%s'\n",
				        progname, optarg);
				return EX_USAGE;
			}
			break;

		  case 'm':
			msgsize = strtoul(optarg, &p, 10);
			if (*p != '\0')
			{
				fprintf(stderr, "%s: invalid size '%s'\n",
				        progname, optarg);
				return EX_USAGE;
			}
			break;

		  case 's':
			signalg = alg_code(optarg);
			if (signalg == (dkim_alg_t) -1)
			{
				fprintf(stderr,
				        "%s: unknown signing algorithm '%s'\n",
				        progname, optarg);
				return EX_USAGE;
			}
			break;

		  case 't':
			testint = strtoul(optarg, &p, 10);
			if (*p != '\0')
			{
				fprintf(stderr, "%s: invalid seconds '%s'\n",
				        progname, optarg);
				return EX_USAGE;
			}
			break;

		  default:
			return usage();
		}
	}

	/* instantiate the library */
	lib = dkim_init(NULL, NULL);

	if (signalg == DKIM_SIGN_UNKNOWN)
		signalg = DKIM_SIGN_RSASHA256;
	else if (signalg == DKIM_SIGN_ED25519SHA256 &&
	         (!dkim_libfeature(lib, DKIM_FEATURE_ED25519) ||
	          !dkim_libfeature(lib, DKIM_FEATURE_SHA256)))
	{
		fprintf(stdout, "### algorithm %s not available SKIPPED\n",
		        alg_name(signalg));
		dkim_close(lib);
		return 0;
	}

	if (signalg == DKIM_SIGN_ED25519SHA256)
	{
		key = RFC8463_ED25519KEY;
		keybits = 256;
		selector = SELECTORRFC8463;
	}
	else
	{
		key = KEY;
		keybits = 1024;
		selector = SELECTOR;
	}

	seed = time(NULL);
	srandom(seed);

	fprintf(stdout,
	        "*** SIGNING SPEED TEST: %d-bit %s/%s with %s, body size %u for %lds, random seed %u\n",
	        keybits, canon_name(hcanon), canon_name(bcanon), alg_name(signalg),
	        (unsigned int) msgsize, (long) testint, seed);

	/* prepare a random body buffer */
	/* c is reused as getopt()'s int (with -1 sentinel) elsewhere in
	 * this function, so cast at the size_t comparisons rather than
	 * retyping the local. */
	for (c = 0, w = 0; (size_t) c < sizeof body; c++)
	{
		if (w >= 75 && (size_t) c < sizeof body - 2)
		{
			body[c++] = '\r';
			body[c++] = '\n';
			w = 0;
		}

		body[c] = (unsigned char)((random() % 95) + 32);
		w++;
	}

	(void) time(&start);

	while (time(NULL) < start + testint)
	{
		dkim = dkim_sign(lib, JOBID, NULL, key, selector, DOMAIN,
		                 hcanon, bcanon, signalg, -1L, &status);
		assert(status == DKIM_STAT_OK);

		status = dkim_header(dkim, HEADER02, strlen(HEADER02));

		status = dkim_header(dkim, HEADER03, strlen(HEADER03));

		status = dkim_header(dkim, HEADER04, strlen(HEADER04));

		status = dkim_header(dkim, HEADER05, strlen(HEADER05));

		status = dkim_header(dkim, HEADER06, strlen(HEADER06));

		status = dkim_header(dkim, HEADER07, strlen(HEADER07));

		status = dkim_header(dkim, HEADER08, strlen(HEADER08));

		status = dkim_header(dkim, HEADER09, strlen(HEADER09));

		assert(dkim_eoh(dkim) == DKIM_STAT_OK);

		msgrem = msgsize;

		while (msgrem > 0)
		{
			wsz = MIN(msgrem, sizeof body);

			status = dkim_body(dkim, body, wsz);

			msgrem -= wsz;
		}

		(void) dkim_body(dkim, CRLF, 2);

		assert(dkim_eom(dkim, NULL) == DKIM_STAT_OK);

		memset(hdr, '\0', sizeof hdr);
		status = dkim_getsighdr(dkim, hdr, sizeof hdr,
		                        strlen(DKIM_SIGNHEADER) + 2);

		status = dkim_free(dkim);

		signcnt++;
	}

	dkim_close(lib);

	rate = signcnt / testint;

	fprintf(stdout, "*** %u messages signed (%d msgs/sec)\n",
	        signcnt, rate);

	return 0;
}
