/*
 * scapitest.c
 *
 * Expected SUCCESSes:	2 + 10 + 1 for all tests.
 *
 * Returns:
 *	Number of FAILUREs.
 *
 *
 * ASSUMES  No key management functions return non-zero success codes.
 *
 * XXX	Split into individual modules?
 * XXX	Error/fringe conditions should be tested.
 *
 *
 * Test of sc_random.						SUCCESSes: 2.
 *	REQUIRES a human to spot check for obvious non-randomness...
 *
 * Test of sc_generate_keyed_hash and sc_check_keyed_hash.	SUCCESSes: 10.
 *
 * Test of sc_encrypt and sc_decrypt.				SUCCESSes: 1.
 */

static char *rcsid = "$Id: scapitest.c 8676 2008-01-17 23:11:17Z ispringer $";	/* */


#include <config.h>

#include <stdio.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include "asn1.h"
#include "snmp_api.h"
#include "keytools.h"
#include "tools.h"
#include "scapi.h"
#include "transform_oids.h"
#include "callback.h"

#include <stdlib.h>

extern char     *optarg;
extern int      optind, optopt, opterr;

#define DEBUG	/* */



/*
 * Globals, &c...
 */
char *local_progname;

#define USAGE	"Usage: %s [-h][-acHr]"
#define OPTIONLIST	"achHr"

int	doalltests	= 0,
	docrypt		= 0,
	dokeyedhash	= 0,
	dorandom	= 0;

#define	ALLOPTIONS	(doalltests + docrypt + dokeyedhash + dorandom)



#define LOCAL_MAXBUF	(1024 * 8)
#define NL		"\n"

#define OUTPUT(o)	fprintf(stdout, "\n\n%s\n\n", o);

#define SUCCESS(s)					\
{							\
	if (!failcount)					\
		fprintf(stdout, "\nSUCCESS: %s\n", s);	\
}

#define FAILED(e, f)					\
{							\
	if (e != SNMPERR_SUCCESS) {			\
		fprintf(stdout, "\nFAILED: %s\n", f);	\
		failcount += 1;				\
	}						\
}


#define BIGSTRING							\
    "   A port may be a pleasant retreat for any mind grown weary of"	\
    "the struggle for existence.  The vast expanse of sky, the"		\
    "mobile architecture of the clouds, the chameleon coloration"	\
    "of the sea, the beacons flashing on the shore, together make"	\
    "a prism which is marvellously calculated to entertain but not"	\
    "fatigue the eye.  The lofty ships with their complex webs of"	\
    "rigging, swayed to and fro by the swell in harmonious dance,"	\
    "all help to maintain a taste for rhythm and beauty in the"		\
    "mind.  And above all there is a mysterious, aristrocratic kind"	\
    "of pleasure to be had, for those who have lost all curiosity"	\
    "or ambition, as they strech on the belvedere or lean over the"	\
    "mole to watch the arrivals and departures of other men, those"	\
    "who still have sufficient strength of purpose in them, the"	\
    "urge to travel or enrich themselves."				\
    "	-- Baudelaire"							\
    "	   From _The_Poems_in_Prose_, \"The Port\" (XLI)."

#define BIGSECRET	"Shhhh... Don't tell *anyone* about this.  Not a soul."
#define BKWDSECRET	".luos a toN  .siht tuoba *enoyna* llet t'noD ...hhhhS"

#define MLCOUNT_MAX	6	/* MAC Length Count Maximum. */



/*
 * Prototypes.
 */
void	usage(FILE *ofp);

int	test_docrypt(void);
int	test_dokeyedhash(void);
int	test_dorandom(void);




int
main(int argc, char **argv)
{
	int		 rval		= SNMPERR_SUCCESS,
			 failcount	= 0;
	char		 ch;

	local_progname = argv[0];

	/*
	 * Parse.
	 */
	while ( (ch = getopt(argc, argv, OPTIONLIST)) != EOF )
	{
		switch(ch) {
		case 'a':	doalltests = 1;		break;
		case 'c':	docrypt = 1;		break;
		case 'H':	dokeyedhash = 1;	break;
		case 'r':	dorandom = 1;		break;
		case 'h':
			rval = 0;
		default:
			usage(stdout);
			exit(rval);
		}

		argc -= 1; argv += 1;
		optind = 1;
	}  /* endwhile getopt */

	if ((argc > 1)) {
		usage(stdout);
		exit(1000);

	} else if ( ALLOPTIONS != 1 ) {
		usage(stdout);
		exit(1000);
	}


	/*
	 * Test stuff.
	 */
	rval = sc_init();
	FAILED(rval, "sc_init().");


	if (docrypt || doalltests) {
		failcount += test_docrypt();
	}
	if (dokeyedhash || doalltests) {
		failcount += test_dokeyedhash();
	}
	if (dorandom || doalltests) {
		failcount += test_dorandom();
	}


	/*
	 * Cleanup.
	 */
	rval = sc_shutdown(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SHUTDOWN,
                           NULL, NULL);
	FAILED(rval, "sc_shutdown().");

	return failcount;

} /* end main() */





void
usage(FILE *ofp)
{
	fprintf(ofp,

	USAGE								
	""								NL
	"	-a		All tests."				NL
	"	-c		Test of sc_encrypt()/sc_decrypt()."	NL
	"	-h		Help."					NL
	"	-H              Test sc_{generate,check}_keyed_hash()." NL
	"	-r              Test sc_random()." 			NL
	""								NL
		, local_progname);

}  /* end usage() */




#ifdef EXAMPLE
/*******************************************************************-o-******
 * test_dosomething
 *
 * Test template.
 *
 * Returns:
 *	Number of failures.
 */
int
test_dosomething(void)
{
	int		rval = SNMPERR_SUCCESS,
			failcount = 0;

EM0(1, "UNIMPLEMENTED");	/* EM(1); /* */

test_dosomething_quit:
	return failcount;

}  /* end test_dosomething() */
#endif /* EXAMPLE */





/*******************************************************************-o-******
 * test_dorandom
 *
 * One large request, one set of short requests.
 *
 * Returns:
 *	Number of failures.
 *
 * XXX	probably should split up into individual options.
 */
int
test_dorandom(void)
{
	int	rval		= SNMPERR_SUCCESS,
		failcount	= 0,
		origrequest	= (1024 * 2),
		origrequest_short = 19,
		nbytes		= origrequest,
		shortcount	= 7,
		i;
	char	buf[LOCAL_MAXBUF];

	OUTPUT("Random test -- large request:");

	rval = sc_random(buf, &nbytes);
	FAILED(rval, "sc_random().");

	if (nbytes != origrequest) {
		FAILED(SNMPERR_GENERR,
		    "sc_random() returned different than requested.");
	}

	dump_chunk("scapitest", NULL, buf, nbytes);

	SUCCESS("Random test -- large request.");


	OUTPUT("Random test -- short requests:");
	origrequest_short = 16;

	for (i = 0; i < shortcount; i++ )
	{
		nbytes = origrequest_short;
		rval = sc_random(buf, &nbytes);
		FAILED(rval, "sc_random().");

		if (nbytes != origrequest_short) {
			FAILED(	SNMPERR_GENERR,
				"sc_random() returned different "
				"than requested.");
		}

		dump_chunk("scapitest", NULL, buf, nbytes);
	}  /* endfor */

	SUCCESS("Random test -- short requests.");


	return failcount;

}  /* end test_dorandom() */



/*******************************************************************-o-******
 * test_dokeyedhash
 *
 * Returns:
 *	Number of failures.
 *
 *
 * Test keyed hashes with a variety of MAC length requests.
 *
 *
 * NOTE Both tests intentionally use the same secret
 *
 * FIX	Get input or output from some other package which hashes...
 * XXX	Could cut this in half with a little indirection...
 */
int
test_dokeyedhash(void)
{
	int		 rval		= SNMPERR_SUCCESS,
			 failcount	= 0,
			 bigstring_len	= strlen(BIGSTRING),
			 secret_len	= strlen(BIGSECRET),
			 properlength,
			 mlcount	= 0,	/* MAC Length count.   */
			 hblen;			/* Hash Buffer length. */

	u_int		 hashbuf_len[MLCOUNT_MAX] = {
				LOCAL_MAXBUF,
				BYTESIZE(SNMP_TRANS_AUTHLEN_HMACSHA1),
				BYTESIZE(SNMP_TRANS_AUTHLEN_HMACMD5),
				BYTESIZE(SNMP_TRANS_AUTHLEN_HMAC96),
				7,
				0, };

	u_char		 hashbuf[LOCAL_MAXBUF];
	char		*s;

test_dokeyedhash_again:

	OUTPUT("Keyed hash test using MD5 --");

	memset(hashbuf, 0, LOCAL_MAXBUF);
	hblen		 = hashbuf_len[mlcount];
	properlength	 = BYTESIZE(SNMP_TRANS_AUTHLEN_HMACMD5);

	rval = sc_generate_keyed_hash(
		usmHMACMD5AuthProtocol, USM_LENGTH_OID_TRANSFORM,
		BIGSECRET, secret_len,
		BIGSTRING, bigstring_len,
		hashbuf, &hblen);
	FAILED(rval, "sc_generate_keyed_hash().");

	if ( hashbuf_len[mlcount] > properlength ) {
		if ( hblen != properlength ) {
			FAILED(	SNMPERR_GENERR,
				"Wrong MD5 hash length returned.  (1)");
		}

	} else if ( hblen != hashbuf_len[mlcount] ) {
		FAILED(SNMPERR_GENERR, "Wrong MD5 hash length returned.  (2)");
	}

	rval = sc_check_keyed_hash(
		usmHMACMD5AuthProtocol, USM_LENGTH_OID_TRANSFORM,
		BIGSECRET, secret_len,
		BIGSTRING, bigstring_len,
		hashbuf, hblen);
	FAILED(rval, "sc_check_keyed_hash().");

	binary_to_hex(hashbuf, hblen, &s);
	fprintf(stdout, "hash buffer (len=%d, request=%d):   %s\n",
						hblen, hashbuf_len[mlcount], s);
	SNMP_FREE(s);

	SUCCESS("Keyed hash test using MD5.");



	OUTPUT("Keyed hash test using SHA1 --");

	memset(hashbuf, 0, LOCAL_MAXBUF);
	hblen		 = hashbuf_len[mlcount];
	properlength	 = BYTESIZE(SNMP_TRANS_AUTHLEN_HMACSHA1);

	rval = sc_generate_keyed_hash(
		usmHMACSHA1AuthProtocol, USM_LENGTH_OID_TRANSFORM,
		BIGSECRET, secret_len,
		BIGSTRING, bigstring_len,
		hashbuf, &hblen);
	FAILED(rval, "sc_generate_keyed_hash().");

	if ( hashbuf_len[mlcount] > properlength ) {
		if ( hblen != properlength ) {
			FAILED(	SNMPERR_GENERR,
				"Wrong SHA1 hash length returned.  (1)");
		}

	} else if (hblen != hashbuf_len[mlcount]) {
		FAILED(SNMPERR_GENERR, "Wrong SHA1 hash length returned.  (2)");
	}

	rval = sc_check_keyed_hash(
		usmHMACSHA1AuthProtocol, USM_LENGTH_OID_TRANSFORM,
		BIGSECRET, secret_len,
		BIGSTRING, bigstring_len,
		hashbuf, hblen);
	FAILED(rval, "sc_check_keyed_hash().");

	binary_to_hex(hashbuf, hblen, &s);
	fprintf(stdout, "hash buffer (len=%d, request=%d):   %s\n",
						hblen, hashbuf_len[mlcount], s);
	SNMP_FREE(s);

	SUCCESS("Keyed hash test using SHA1.");



	/*
	 * Run the basic hash tests but vary the size MAC requests.
	 */
	if ( hashbuf_len[++mlcount] != 0 ) {
		goto test_dokeyedhash_again;
	}


	return failcount;

}  /* end test_dokeyedhash() */





/*******************************************************************-o-******
 * test_docrypt
 *
 * Returns:
 *	Number of failures.
 */
int
test_docrypt(void)
{
	int		 rval = SNMPERR_SUCCESS,
			 failcount = 0,
			 bigstring_len	 = strlen(BIGSTRING),
			 secret_len	 = BYTESIZE(SNMP_TRANS_PRIVLEN_1DES),
			 iv_len		 = BYTESIZE(SNMP_TRANS_PRIVLEN_1DES_IV);

	u_int		 buf_len = LOCAL_MAXBUF,
			 cryptbuf_len = LOCAL_MAXBUF;

	char		 buf[LOCAL_MAXBUF],
			 cryptbuf[LOCAL_MAXBUF],
			 secret[LOCAL_MAXBUF],
			 iv[LOCAL_MAXBUF];

	OUTPUT("Test 1DES-CBC --");


	memset(buf, 0, LOCAL_MAXBUF);

	memcpy(secret,	BIGSECRET,	secret_len);
	memcpy(iv,	BKWDSECRET,	iv_len);


	rval = sc_encrypt(
		usmDESPrivProtocol, USM_LENGTH_OID_TRANSFORM,
		secret, secret_len,
		iv, iv_len,
		BIGSTRING, bigstring_len,
		cryptbuf, &cryptbuf_len);
	FAILED(rval, "sc_encrypt().");

	rval = sc_decrypt(
		usmDESPrivProtocol, USM_LENGTH_OID_TRANSFORM,
		secret, secret_len,
		iv, iv_len,
		cryptbuf, cryptbuf_len,
		buf, &buf_len);
	FAILED(rval, "sc_decrypt().");

	if (buf_len != bigstring_len) {
		FAILED(SNMPERR_GENERR, "Decrypted buffer is the wrong length.");
	}
	if ( memcmp(buf, BIGSTRING, bigstring_len) ) {
		FAILED(	SNMPERR_GENERR,
			"Decrypted buffer is not equal to original plaintext.");
	}


	SUCCESS("Test 1DES-CBC --");

	return failcount;

}  /* end test_docrypt() */

