threadTest.cpp   [plain text]


/*
 * Multithread exerciser - beat up on CSP, TP, and CL from multiple threads.
 *
 * Written by Doug Mitchell. 
 *
 *
 * Spawn a user-spec'd number of threads, each of which does the following:
 *
 * testThread(testParams) {
 *    roll the dice;
 *    depending on dieValue {
 *		cgVerify test;
 *		cgConstruct test;
 *      sslPing() test;
 *		etc....
 *    }
 * }
 */
#include <utilLib/common.h>
#include <utilLib/cspwrap.h>	
#include <clAppUtils/clutils.h>
#include "testParams.h"
#include <security_utilities/threading.h>
#include <security_utilities/utilities.h>
#include <security_utilities/devrandom.h>
#include <pthread.h>
#include <Security/Security.h>

#include <stdio.h>
#include <stdlib.h>

/*
 * As of 3/15/2001, can't link apps which use Security.framework against BSAFE.
 */
#define BSAFE_ENABLE	0

#define NUM_LOOPS		100
#define NUM_THREADS		20

/* function for both test init and test proper */
typedef int (*testFcn)(TestParams *testParams);

/* one test */
typedef struct {
	testFcn 	testInit;
	testFcn 	testRun;
	const char	*testName;
	char		enable;
} TestDef;

/* the tests we know about */

#define CG_CONSTRUCT_ENABLE		1		/* leak free 12/19 */
#define CG_VERIFY_ENABLE		1		/* leak free 12/19 */
#define SIGN_VFY_ENABLE			1		/* leak free */
#define SYM_TEST_ENABLE			1		/* leak free */
#define TIME_ENABLE				0		/* normally off */
#define SSL_PING_ENABLE			0		/* leak free 12/19 */
#define GET_FIELDS_ENABLE		1		/* leak free */
#define GET_CACHED_FLDS_ENABLE	1		/* leak free */
#define DER_DECODE_ENABLE		0
#define ATTACH_ENABLE			1		/* leak free */
#define SEC_TRUST_ENABLE		0		/* works but leaks per 3737232 */
#define KC_STATUS_ENABLE		0		/* currently fails: see 6368768 */
#define DIGEST_CLIENT_ENABLE	1
#define MDS_LOOKUP_ENABLE		1		/* leak free */
#define CSSM_ERR_STR_ENABLE		0		/* leak free */
#define TRUST_SETTINGS_ENABLE	1
#define DB_SETTINGS_ENABLE		0		/* not thread safe */
#define COPY_ROOTS_ENABLE       1

#if		BSAFE_ENABLE
#define RSA_SIGN_ENABLE			1
#define DES_ENABLE				1
#else
#define RSA_SIGN_ENABLE			0
#define DES_ENABLE				0
#endif	/* BSAFE_ENABLE */
#define SSL_THRASH_ENABLE		0
#define CSP_RAND_ENABLE			0

/* when adding to this table be sure to update setTestEnables() as well */
TestDef testArray[] = {
	{ cgConstructInit, 		cgConstruct,	"cgConstruct", 	CG_CONSTRUCT_ENABLE },
	{ cgVerifyInit, 		cgVerify,		"cgVerify", 	CG_VERIFY_ENABLE 	},
	{ signVerifyInit, 		signVerify,		"signVerify", 	SIGN_VFY_ENABLE 	},
	{ symTestInit, 			symTest,		"symTest", 		SYM_TEST_ENABLE 	},
	{ timeInit, 			timeThread,		"timeThread", 	TIME_ENABLE 		},
	{ sslPingInit, 			sslPing,		"sslPing", 		SSL_PING_ENABLE		},
	{ getFieldsInit, 		getFields,		"getFields", 	GET_FIELDS_ENABLE	},
	{ getCachedFieldsInit,	getCachedFields,"getCachedFields",GET_CACHED_FLDS_ENABLE},
	{ attachTestInit, 		attachTest,		"attachTest", 	ATTACH_ENABLE		},
	{ sslThrashInit, 		sslThrash,		"sslThrash", 	SSL_THRASH_ENABLE	},
	{ cspRandInit,	 		cspRand,		"cspRand", 		CSP_RAND_ENABLE		},
	{ derDecodeInit,		derDecodeTest,	"derDecode",	DER_DECODE_ENABLE 	},
	{ secTrustEvalInit,		secTrustEval,	"secTrustEval",	SEC_TRUST_ENABLE 	},
	{ kcStatusInit,			kcStatus,		"kcStatus",		KC_STATUS_ENABLE	},
	{ digestClientInit,		digestClient,	"digestClient",	DIGEST_CLIENT_ENABLE},
	{ mdsLookupInit,		mdsLookup,		"mdsLookup",	MDS_LOOKUP_ENABLE	},
	{ cssmErrStrInit,		cssmErrStr,		"cssmErrStr",	CSSM_ERR_STR_ENABLE	},
	{ trustSettingsInit,	trustSettingsEval, "trustSettingsEval", TRUST_SETTINGS_ENABLE },
	{ dbOpenCloseInit,		dbOpenCloseEval, "dbOpenClose", DB_SETTINGS_ENABLE  },
    { copyRootsInit,        copyRootsTest,  "copyRoots",    COPY_ROOTS_ENABLE   },
	#if	BSAFE_ENABLE
	{ desInit,		 		desTest,		"desTest", 		DES_ENABLE			},
	{ rsaSignInit,	 		rsaSignTest,	"rsaSignTest", 	RSA_SIGN_ENABLE		}
	#endif
};
#define NUM_THREAD_TESTS	(sizeof(testArray) / sizeof(TestDef))

static void usage(char **argv)
{
    printf("Usage: %s [options]\n", argv[0]);
    printf("Options:\n");
    printf("   l=loopCount (default = %d)\n", NUM_LOOPS);
	printf("   t=threadCount (default = %d)\n", NUM_THREADS);
	printf("   e[cvsytpfabdFSrDTkmCer] - enable specific tests\n");
	printf("       c=cgConstruct    v=cgVerify    s=signVerify   y=symTest\n");
	printf("       t=timeThread     p=sslPing     f=getFields    a=attach\n");
	printf("       b=bsafeSignVfy   d=bsafeDES    F=getCachedFields\n");
	printf("       S=sslThrash      r=cspRand     D=derDecode    T=SecTrustEval\n");
	printf("       k=kcStatus       m=mdsLookup   C=digestClient e=cssmErrorStr\n");
	printf("       R=TrustSetting   B=DBOpenClose o=copyRoots\n");
	printf("   o=test_specific_opts (see source for details)\n");
	printf("   a(bort on error)\n");
	printf("   r(un loop)\n");
	printf("   q(uiet)\n");
	printf("   v(erbose)\n");
	printf("   s(ilent)\n");
	printf("   h(elp)\n");
    exit(1);
}

/* it happens from time to time on SSL ping */
#include <signal.h>
void sigpipe(int sig) 
{ 
	fflush(stdin);
	printf("***SIGPIPE***\n");
}

/* common thread-safe routines */
static Security::DevRandomGenerator devRand;

CSSM_RETURN threadGetRandData(
	const TestParams 	*testParams,
	CSSM_DATA_PTR		data,		// mallocd by caller
	unsigned			numBytes)	// how much to fill
{
	devRand.random(data->Data, numBytes);
	data->Length = numBytes;
	return CSSM_OK;
}

/* delay a random amount, 0<delay<10ms */
#define MAX_DELAY_US	10000
void randomDelay()
{
	unsigned char usec;
	devRand.random(&usec, 1);
	usec %= 10000;
	usleep(usec);
}

/* in case printf() is malevolently unsafe */

static Mutex printLock;

void printChar(char c)
{
	StLock<Mutex> _(printLock);
	printf("%c", c);
	fflush(stdout);
}

/* 
 * Optionally start up a CFRunLoop. This is needed to field keychain event callbacks, used
 * to maintain root cert cache coherency. 
 */
 
/* first we need something to register so we *have* a run loop */
static OSStatus kcCacheCallback (
   SecKeychainEvent keychainEvent,
   SecKeychainCallbackInfo *info,
   void *context)
{
	return noErr;
}

/* main thread has to wait for this to be set to know a run loop has been set up */
static int runLoopInitialized = 0;

/* this is the thread which actually runs the CFRunLoop */
void *cfRunLoopThread(void *arg)
{
	OSStatus ortn = SecKeychainAddCallback(kcCacheCallback, 
		kSecTrustSettingsChangedEventMask, NULL);
	if(ortn) {
		printf("registerCacheCallbacks: SecKeychainAddCallback returned %d", (int32_t)ortn);
		/* Not sure how this could ever happen - maybe if there is no run loop active? */
		return NULL;
	}
	runLoopInitialized = 1;
	CFRunLoopRun();
	/* should not be reached */
	printf("\n*** Hey! CFRunLoopRun() exited!***\n");
	return NULL;
}

static int startCFRunLoop()
{
	pthread_t runLoopThread;
	
	int result = pthread_create(&runLoopThread, NULL, cfRunLoopThread, NULL);
	if(result) {
		printf("***pthread_create returned %d, aborting\n", result);
		return -1;
	}
	return 0;
}

/* main pthread body */
void *testThread(void *arg)
{
	TestParams *testParams = (TestParams *)arg;
	int status;
	
	TestDef *thisTestDef = &testArray[testParams->testNum];
	status = thisTestDef->testRun(testParams);
	if(!testParams->quiet) {
		printf("\n...thread %d test %s exiting with status %d\n", 
			testParams->threadNum, thisTestDef->testName, status);
	}
	pthread_exit((void*)status);
	/* NOT REACHED */
	return (void *)status;
}

/*
 * Set enables in testArray[] 
 */
static void setOneEnable(testFcn f)
{
	unsigned dex;
	for(dex=0; dex<NUM_THREAD_TESTS; dex++) {
		if(testArray[dex].testRun == f) {
			testArray[dex].enable = 1;
			return;
		}
	}
	printf("****setOneEnable: test not found\n");
	exit(1);
}

static void setTestEnables(const char *enables, char **argv)
{
	/* first turn 'em all off */
	unsigned dex;
	for(dex=0; dex<NUM_THREAD_TESTS; dex++) {
		testArray[dex].enable = 0;
	}
	
	/* enable specific ones */
	while(*enables != '\0') {
		switch(*enables) {
			case 'c':	setOneEnable(cgConstruct); break;
			case 'v':	setOneEnable(cgVerify); break;
			case 's':	setOneEnable(signVerify); break;
			case 'y':	setOneEnable(symTest); break;
			case 't':	setOneEnable(timeThread); break;
			case 'p':	setOneEnable(sslPing); break;
			case 'f':	setOneEnable(getFields); break;
			case 'F':	setOneEnable(getCachedFields); break;
			case 'a':	setOneEnable(attachTest); break;
			case 'S':	setOneEnable(sslThrash); break;
			case 'r':	setOneEnable(cspRand); break;
			case 'D':	setOneEnable(derDecodeTest); break;
			case 'T':	setOneEnable(secTrustEval); break;
			case 'k':	setOneEnable(kcStatus); break;
			case 'C':	setOneEnable(digestClient); break;
			case 'm':	setOneEnable(mdsLookup); break;
			case 'e':	setOneEnable(cssmErrStr); break;
			case 'R':	setOneEnable(trustSettingsEval); break;
			case 'B':   setOneEnable(dbOpenCloseEval); break;
			case 'o':   setOneEnable(copyRootsTest); break;
			#if		BSAFE_ENABLE
			case 'b':	setOneEnable(rsaSignTest); break;
			case 'd':	setOneEnable(desTest); break;
			#endif
			default:
				usage(argv);
		}
		enables++;
	}
}

int main(int argc, char **argv)
{   
	CSSM_CSP_HANDLE	cspHand = 0;
	CSSM_CL_HANDLE	clHand = 0;
	CSSM_TP_HANDLE	tpHand = 0;
	unsigned		errCount = 0;
	TestParams		*testParams;
	TestParams		*thisTest;
	unsigned		dex;
	pthread_t		*threadList;
	int				arg;
	char			*argp;
	int				result;
	TestDef			*thisTestDef;
	unsigned		numValidTests;
	unsigned		i,j;
	
	/* user-spec'd parameters */
	char			quiet = 0;
	char			verbose = 0;
	unsigned		numThreads = NUM_THREADS;
	unsigned		numLoops = NUM_LOOPS;
	char			*testOpts = NULL;
	bool			abortOnError = false;
	bool			silent = false;
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'l':
				numLoops = atoi(&argp[2]);
				break;
		    case 't':
				numThreads = atoi(&argp[2]);
				break;
				break;
			case 'q':
				quiet = 1;
				break;
			case 'v':
				verbose = 1;
				break;
			case 'o':
				if((argp[1] != '=') || (argp[2] == '\0')) {
					usage(argv);
				}
				testOpts = argp + 2;
				break;
			case 'e':
				setTestEnables(argp + 1, argv);
				break;
			case 'a':
				abortOnError = true;
				break;
			case 'r':
				startCFRunLoop();
				break;
			case 's':
				silent = true;
				quiet = 1;
				break;
			default:
				usage(argv);
		}
	}

	/* attach to all three modules */
	cspHand = cspStartup();
	if(cspHand == 0) {
		exit(1);
	}
	clHand = clStartup();
	if(clHand == 0) {
		goto abort;
	}
	tpHand = tpStartup(); 
	if(tpHand == 0) {
		goto abort;
	}
	signal(SIGPIPE, sigpipe);
	
	/* malloc and init TestParams for all requested threads */
	testParams = (TestParams *)malloc(numThreads * sizeof(TestParams));
	for(dex=0; dex<numThreads; dex++) {
		thisTest 				= &testParams[dex];
		thisTest->numLoops 		= numLoops;
		thisTest->verbose 		= verbose;
		thisTest->quiet 		= quiet;
		thisTest->threadNum 	= dex;
		thisTest->cspHand 		= cspHand;
		thisTest->clHand 		= clHand;
		thisTest->tpHand 		= tpHand;
		thisTest->testOpts		= testOpts;
		
		if(dex < 10) {
			/* 0..9 */
			thisTest->progressChar 	= '0' + dex;
		}
		else if(dex < 36) {
			/* a..z */
			thisTest->progressChar 	= 'a' + dex - 10;
		}
		else {
			/* A..Z and if X can run more threads than that, I'll be surprised */
			thisTest->progressChar 	= 'Z' + dex - 36;
		}
	}
	
	/* Adjust testArray for tests which are actually enabled */
	numValidTests = 0;
	dex=0;
	for(i=0; i<NUM_THREAD_TESTS; i++) {
		if(testArray[dex].enable) {
			numValidTests++;
			dex++;
		}
		else {
			/* delete this one, move remaining tests up */
			for(j=dex; j<NUM_THREAD_TESTS-1; j++) {
				testArray[j] = testArray[j+1];
			}
			/* and re-examine testArray[dex], which we just rewrote */
		}
	}
	
	if(!silent) {
		printf("Starting threadTest; args: ");
		for(i=1; i<(unsigned)argc; i++) {
			printf("%s ", argv[i]);
		}
		printf("\n");
	}
	
	/* assign a test module to each thread and run its init routine */
	for(dex=0; dex<numThreads; dex++) {
		/* roll the dice */
		thisTest = &testParams[dex];
		thisTest->testNum = genRand(0, numValidTests - 1);
		
		thisTestDef = &testArray[thisTest->testNum];
		if(!quiet) {
			printf("...thread %d: test %s\n", dex, thisTestDef->testName);
		}
		result = thisTestDef->testInit(thisTest);
		if(result) {
			printf("***Error on %s init; aborting\n", thisTestDef->testName);
			errCount++;
			goto abort;
		}
	}
	

	/* start up each thread */
	threadList = (pthread_t *)malloc(numThreads * sizeof(pthread_t));
	for(dex=0; dex<numThreads; dex++) {
		int result = pthread_create(&threadList[dex], NULL, 
			testThread, &testParams[dex]);
		if(result) {
			printf("***pthread_create returned %d, aborting\n", result);
			errCount++;
			goto abort;
		}
	}
	
	/* wait for each thread to complete */
	for(dex=0; dex<numThreads; dex++) {
		void *status;
		result = pthread_join(threadList[dex], &status);
		if(result) {
			printf("***pthread_join returned %d, aborting\n", result);
			goto abort;
		}
		if(!quiet) {
			printf("\n...joined thread %d, status %d\n", 
				dex, status ? 1 : 0);
		}
		if(status != NULL) {
			errCount++;
			if(abortOnError) {
				break;
			}
		}
	}
	if(errCount || !quiet) {
		printf("threadTest complete; errCount %d\n", errCount);
	}
abort:
	if(cspHand != 0) {
		CSSM_ModuleDetach(cspHand);
	}
	if(clHand != 0) {
		CSSM_ModuleDetach(clHand);
	}
	if(tpHand != 0) {
		CSSM_ModuleDetach(tpHand);
	}
	return errCount;
}