hashTime.cpp   [plain text]


/*
 * hashTime.cpp - measure performance of digest ops
 */
 
#include <Security/Security.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cputime.h"
#include "cspwrap.h"
#include "common.h"
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <CommonCrypto/CommonDigest.h>
#include "MD5.h"			/* CryptKit version used Panther and prior */
#include "SHA1.h"			/* ditto */

/* enumerate digest algorithms our way */
typedef int HT_Alg;
enum {
	HA_MD5 = 0,
	HA_SHA1,
	HA_SHA224,
	HA_SHA256,
	HA_SHA384,
	HA_SHA512
};

#define FIRST_ALG	HA_MD5
#define LAST_ALG	HA_SHA512

static void usage(char **argv)
{
    printf("Usage: %s [option ...]\n", argv[0]);
    printf("Options:\n");
	printf("   t=testspec; default=all\n");
	printf("     test specs: c : digest context setup/teardown\n");
	printf("                 b : basic single block digest\n");
	printf("                 d : digest lots of data\n");
	printf("   a=alg; default=all\n");
	printf("           algs: m : MD5\n");
	printf("                 s : SHA1\n");
	printf("                 4 : SHA224\n");
	printf("                 2 : SHA256\n");
	printf("                 3 : SHA384\n");
	printf("                 5 : SHA512\n");
	printf("   l=loops (only valid if testspec is given)\n");
	printf("   o (use openssl implementations, MD5 and SHA1 only)\n");
	printf("   c (use CommonCrypto implementation)\n");
	printf("   k (use CryptKit implementations, MD5 and SHA1 only\n");
	printf("   v verify digest by printing it\n");
	exit(1);
}

static void dumpDigest(
	const unsigned char *digest,
	unsigned len)
{
	for(unsigned dex=0; dex<len; dex++) {
		printf("%02X", *digest++);
		if((dex % 4) == 3) {
			printf(" ");
		}
	}
	printf("\n");
}

/* sort-of random, but repeatable */
static void initPtext(
	unsigned char *ptext,
	unsigned len)
{
	srandom(1);
	for(unsigned dex=0; dex<len; dex++) {
		*ptext++ = random();
	}
}

/* passed to each test */	
typedef struct {
	unsigned			loops;
	CSSM_CSP_HANDLE		cspHand;
	CSSM_ALGORITHMS		algId;		// MD5, SHA1
	bool				dumpDigest;
} TestParams;

/* just CDSA context setup/teardown - no CSP activity */
static CSSM_RETURN hashContext(
	TestParams	*params)
{
	CSSM_CC_HANDLE	ccHand;
	CSSM_RETURN 	crtn;
	unsigned 		loop;
	CPUTime 		startTime;
	double			timeSpentMs;
	
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
			params->algId, &ccHand);
		if(crtn) {
			return crtn;
		}
		crtn = CSSM_DeleteContext(ccHand);
		if(crtn) {
			return crtn;
		}
	}
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	printf("   context setup/delete : %u ops in %.2f ms; %f ms/op\n",
		params->loops, timeSpentMs, timeSpentMs / (double)params->loops);
	return CSSM_OK;
}

/* Minimal CSP init/digest/final */
#define BASIC_BLOCK_SIZE	64		// to digest in bytes
#define MAX_DIGEST_SIZE		64		// we provide, no malloc below CSSM

static CSSM_RETURN hashBasic(
	TestParams	*params)
{
	CSSM_CC_HANDLE	ccHand;
	CSSM_RETURN 	crtn;
	unsigned 		loop;
	CPUTime 		startTime;
	double			timeSpentMs;
	uint8			ptext[BASIC_BLOCK_SIZE];
	uint8			digest[MAX_DIGEST_SIZE];
	CSSM_DATA		ptextData = {BASIC_BLOCK_SIZE, ptext};
	CSSM_DATA		digestData = {MAX_DIGEST_SIZE, digest};
	
	/* we reuse this one inside the loop */
	crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
			params->algId, &ccHand);
	if(crtn) {
		return crtn;
	}
	
	/* random data, const thru the loops */
	appGetRandomBytes(ptext, BASIC_BLOCK_SIZE);
	
	/* start critical timing loop */
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		crtn = CSSM_DigestDataInit(ccHand);
		if(crtn) {
			return crtn;
		}
		crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
		if(crtn) {
			return crtn;
		}
		crtn = CSSM_DigestDataFinal(ccHand, &digestData);
		if(crtn) {
			return crtn;
		}
	}
	CSSM_DeleteContext(ccHand);
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	printf("   Digest one %u byte block : %u ops in %.2f ms; %f ms/op\n",
		BASIC_BLOCK_SIZE, params->loops, 
		timeSpentMs, timeSpentMs / (double)params->loops);
	return CSSM_OK;
}

/* Lots of data */
#define PTEXT_SIZE			1000		// to digest in bytes
#define INNER_LOOPS			1000

static CSSM_RETURN hashDataRate(
	TestParams	*params)
{
	CSSM_CC_HANDLE	ccHand;
	CSSM_RETURN 	crtn;
	unsigned 		loop;
	unsigned		iloop;
	CPUTime 		startTime;
	double			timeSpent, timeSpentMs;
	uint8			ptext[PTEXT_SIZE];
	uint8			digest[MAX_DIGEST_SIZE];
	CSSM_DATA		ptextData = {PTEXT_SIZE, ptext};
	CSSM_DATA		digestData = {MAX_DIGEST_SIZE, digest};
	
	/* we reuse this one inside the loop */
	crtn = CSSM_CSP_CreateDigestContext(params->cspHand,
			params->algId, &ccHand);
	if(crtn) {
		return crtn;
	}
	
	/* random data, const thru the loops */
	initPtext(ptext, PTEXT_SIZE);
	
	/* start critical timing loop */
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		crtn = CSSM_DigestDataInit(ccHand);
		if(crtn) {
			return crtn;
		}
		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
			crtn = CSSM_DigestDataUpdate(ccHand, &ptextData, 1);
			if(crtn) {
				return crtn;
			}
		}
		crtn = CSSM_DigestDataFinal(ccHand, &digestData);
		if(crtn) {
			return crtn;
		}
	}
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	timeSpent = timeSpentMs / 1000.0;
	
	CSSM_DeleteContext(ccHand);
	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
	float totalBytes   = params->loops * bytesPerLoop;
	
	/* careful, KByte = 1024, ms = 1/1000 */
	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
		bytesPerLoop, params->loops, 
		timeSpentMs, timeSpentMs / (double)params->loops,
		((float)totalBytes / 1024.0) / timeSpent);
	if(params->dumpDigest) {
		dumpDigest(digest, digestData.Length);
	}
	return CSSM_OK;
}

/* Lots of data, openssl version */

typedef union {
	MD5_CTX		md5;
	SHA_CTX		sha;
} OS_CTX;

typedef void (*initFcn)(void *digestCtx);
typedef void (*updateFcn)(void *digestCtx, const void *data, unsigned long len);
typedef void (*finalFcn)(unsigned char *digest, void *digestCtx);

static CSSM_RETURN hashDataRateOpenssl(
	TestParams	*params)
{
	OS_CTX			ctx;
	initFcn			initPtr = NULL;
	updateFcn		updatePtr = NULL;
	finalFcn		finalPtr = NULL;
	unsigned 		loop;
	unsigned		iloop;
	CPUTime 		startTime;
	double			timeSpent, timeSpentMs;
	uint8			ptext[PTEXT_SIZE];
	uint8			digest[MAX_DIGEST_SIZE];
	unsigned		digestLen = 16;
	
	/* we reuse this one inside the loop */
	switch(params->algId) {
		case CSSM_ALGID_SHA1:
			initPtr = (initFcn)SHA1_Init;
			updatePtr = (updateFcn)SHA1_Update;
			finalPtr = (finalFcn)SHA1_Final;
			digestLen = 20;
			break;
		case CSSM_ALGID_MD5:
			initPtr = (initFcn)MD5_Init;
			updatePtr = (updateFcn)MD5_Update;
			finalPtr = (finalFcn)MD5_Final;
			break;
		default:
			printf("***Sorry, Openssl can only do SHA1 and MD5.\n");
			return 1;
	}
		
	/* random data, const thru the loops */
	initPtext(ptext, PTEXT_SIZE);
	
	/* start critical timing loop */
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		initPtr(&ctx);
		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
			updatePtr(&ctx, ptext, PTEXT_SIZE);
		}
		finalPtr(digest, &ctx);
	}
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	timeSpent = timeSpentMs / 1000.0;
	
	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
	float totalBytes   = params->loops * bytesPerLoop;
	
	/* careful, KByte = 1024, ms = 1/1000 */
	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
		bytesPerLoop, params->loops, 
		timeSpentMs, timeSpentMs / (double)params->loops,
		((float)totalBytes / 1024.0) / timeSpent);
	if(params->dumpDigest) {
		dumpDigest(digest, digestLen);
	}
	return CSSM_OK;
}

/* Lots of data, CommonCrypto version (not thru CSP) */

typedef union {
	CC_MD5_CTX		md5;
	CC_SHA1_CTX		sha;
	CC_SHA256_CTX	sha256;
	CC_SHA512_CTX	sha512;
} CC_CTX;

typedef void (*ccUpdateFcn)(void *digestCtx, const void *data, CC_LONG len);
typedef void (*ccFinalFcn)(unsigned char *digest, void *digestCtx);

static CSSM_RETURN hashDataRateCommonCrypto(
	TestParams	*params)
{
	CC_CTX			ctx;
	ccUpdateFcn		updatePtr = NULL;
	ccFinalFcn		finalPtr = NULL;
	initFcn			initPtr = NULL;
	unsigned 		loop;
	unsigned		iloop;
	CPUTime 		startTime;
	double			timeSpent, timeSpentMs;
	uint8			ptext[PTEXT_SIZE];
	uint8			digest[MAX_DIGEST_SIZE];
	unsigned		digestLen = 16;
	
	/* we reuse this one inside the loop */
	switch(params->algId) {
		case CSSM_ALGID_SHA1:
			initPtr = (initFcn)CC_SHA1_Init;
			updatePtr = (ccUpdateFcn)CC_SHA1_Update;
			finalPtr = (ccFinalFcn)CC_SHA1_Final;
			digestLen = CC_SHA1_DIGEST_LENGTH;
			break;
		case CSSM_ALGID_SHA224:
			initPtr = (initFcn)CC_SHA224_Init;
			updatePtr = (ccUpdateFcn)CC_SHA224_Update;
			finalPtr = (ccFinalFcn)CC_SHA224_Final;
			digestLen = CC_SHA224_DIGEST_LENGTH;
			break;
		case CSSM_ALGID_SHA256:
			initPtr = (initFcn)CC_SHA256_Init;
			updatePtr = (ccUpdateFcn)CC_SHA256_Update;
			finalPtr = (ccFinalFcn)CC_SHA256_Final;
			digestLen = CC_SHA256_DIGEST_LENGTH;
			break;
		case CSSM_ALGID_SHA384:
			initPtr = (initFcn)CC_SHA384_Init;
			updatePtr = (ccUpdateFcn)CC_SHA384_Update;
			finalPtr = (ccFinalFcn)CC_SHA384_Final;
			digestLen = CC_SHA384_DIGEST_LENGTH;
			break;
		case CSSM_ALGID_SHA512:
			initPtr = (initFcn)CC_SHA512_Init;
			updatePtr = (ccUpdateFcn)CC_SHA512_Update;
			finalPtr = (ccFinalFcn)CC_SHA512_Final;
			digestLen = CC_SHA512_DIGEST_LENGTH;
			break;
		case CSSM_ALGID_MD5:
			initPtr = (initFcn)CC_MD5_Init;
			updatePtr = (ccUpdateFcn)CC_MD5_Update;
			finalPtr = (ccFinalFcn)CC_MD5_Final;
			digestLen = CC_MD5_DIGEST_LENGTH;
			break;
		default:
			printf("***BRRRZAP!\n");
			return 1;
	}
		
	/* random data, const thru the loops */
	initPtext(ptext, PTEXT_SIZE);
	
	/* start critical timing loop */
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		initPtr(&ctx);
		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
			updatePtr(&ctx, ptext, PTEXT_SIZE);
		}
		finalPtr(digest, &ctx);
	}
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	timeSpent = timeSpentMs / 1000.0;
	
	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
	float totalBytes   = params->loops * bytesPerLoop;
	
	/* careful, KByte = 1024, ms = 1/1000 */
	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
		bytesPerLoop, params->loops, 
		timeSpentMs, timeSpentMs / (double)params->loops,
		((float)totalBytes / 1024.0) / timeSpent);
	if(params->dumpDigest) {
		dumpDigest(digest, digestLen);
	}
	return CSSM_OK;
}

/* Lots of data, CryptKit version */

/* cryptkit final routines are not orthoganal, fix up here */
static void ckSha1Final(
	unsigned char *digest, 
	void *ctx)
{
	sha1GetDigest((sha1Obj)ctx, digest);
}

static void ckMD5Final(
	unsigned char *digest, 
	void *ctx)
{
	MD5Final((struct MD5Context *)ctx, digest);
}

typedef void (*ckUpdateFcn)(void *digestCtx, const void *data, unsigned len);
typedef void (*ckFinalFcn)(unsigned char *digest, void *digestCtx);

static CSSM_RETURN hashDataRateCryptKit(
	TestParams	*params)
{
	ckUpdateFcn		updatePtr = NULL;
	ckFinalFcn		finalPtr = NULL;
	initFcn			initPtr = NULL;
	struct MD5Context	md5;
	sha1Obj				sha;
	void			*ctx;

	unsigned 		loop;
	unsigned		iloop;
	CPUTime 		startTime;
	double			timeSpent, timeSpentMs;
	uint8			ptext[PTEXT_SIZE];
	uint8			digest[MAX_DIGEST_SIZE];
	unsigned		digestLen = 16;
	
	/* we reuse this one inside the loop */
	switch(params->algId) {
		case CSSM_ALGID_SHA1:
			sha = sha1Alloc();
			ctx = sha;
			initPtr = (initFcn)sha1Reinit;
			updatePtr = (ckUpdateFcn)sha1AddData;
			finalPtr = (ckFinalFcn)ckSha1Final;
			digestLen = 20;
			break;
		case CSSM_ALGID_MD5:
			ctx = &md5;
			initPtr = (initFcn)MD5Init;
			updatePtr = (ckUpdateFcn)MD5Update;
			finalPtr = (ckFinalFcn)ckMD5Final;
			break;
		default:
			printf("***Sorry, CryptKit can only do SHA1 and MD5.\n");
			return 1;
	}
		
	/* random data, const thru the loops */
	initPtext(ptext, PTEXT_SIZE);
	
	/* start critical timing loop */
	startTime = CPUTimeRead();
	for(loop=0; loop<params->loops; loop++) {
		initPtr(ctx);
		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
			updatePtr(ctx, ptext, PTEXT_SIZE);
		}
		finalPtr(digest, ctx);
	}
	timeSpentMs = CPUTimeDeltaMs(startTime, CPUTimeRead());
	timeSpent = timeSpentMs / 1000.0;
	
	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
	float totalBytes   = params->loops * bytesPerLoop;
	
	/* careful, KByte = 1024, ms = 1/1000 */
	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
		bytesPerLoop, params->loops, 
		timeSpentMs, timeSpentMs / (double)params->loops,
		((float)totalBytes / 1024.0) / timeSpent);
	if(params->dumpDigest) {
		dumpDigest(digest, digestLen);
	}
	return CSSM_OK;
}

typedef CSSM_RETURN (*testRunFcn)(TestParams *testParams);

/*
 * Static declaration of a test
 */
typedef struct {
	const char 			*testName;
	unsigned			loops;
	testRunFcn			run;
	char				testSpec;		// for t=xxx cmd line opt
} TestDefs;

static TestDefs testDefs[] = 
{
	{ 	"Digest context setup/teardown",
		100000,
		hashContext,
		'c',
	},
	{ 	"Basic single block digest",
		100000,
		hashBasic,
		'b',
	},
	{ 	"Large data digest",
		1000,
		hashDataRate,
		'd',
	},
};

static TestDefs testDefsOpenSSL[] = 
{
	{ 	"Digest context setup/teardown",
		100000,
		NULL,			// not implemented
		'c',
	},
	{ 	"Basic single block digest",
		100000,
		NULL,			// not implemented
		'b',
	},
	{ 	"Large data digest, OpenSSL",
		1000,
		hashDataRateOpenssl,
		'd',
	},
};

static TestDefs testDefsCommonCrypto[] = 
{
	{ 	"Digest context setup/teardown",
		100000,
		NULL,			// not implemented
		'c',
	},
	{ 	"Basic single block digest",
		100000,
		NULL,			// not implemented
		'b',
	},
	{ 	"Large data digest, CommonCrypto",
		1000,
		hashDataRateCommonCrypto,
		'd',
	},
};

static TestDefs testDefsCryptKit[] = 
{
	{ 	"Digest context setup/teardown",
		100000,
		NULL,			// not implemented
		'c',
	},
	{ 	"Basic single block digest",
		100000,
		NULL,			// not implemented
		'b',
	},
	{ 	"Large data digest, CryptKit",
		1000,
		hashDataRateCryptKit,
		'd',
	},
};


static void algToAlgId(
	HT_Alg			alg,
	CSSM_ALGORITHMS	*algId,
	const char		**algStr)
{
	switch(alg) {
		case HA_MD5:
			*algId = CSSM_ALGID_MD5;
			*algStr = "MD5";
			break;
		case HA_SHA1:
			*algId = CSSM_ALGID_SHA1;
			*algStr = "SHA1";
			break;
		case HA_SHA224:
			*algId = CSSM_ALGID_SHA224;
			*algStr = "SHA224";
			break;
		case HA_SHA256:
			*algId = CSSM_ALGID_SHA256;
			*algStr = "SHA256";
			break;
		case HA_SHA384:
			*algId = CSSM_ALGID_SHA384;
			*algStr = "SHA384";
			break;
		case HA_SHA512:
			*algId = CSSM_ALGID_SHA512;
			*algStr = "SHA512";
			break;
		default:
			printf("***algToAlgId screwup\n");
			exit(1);
	}
}

#define NUM_TESTS	(sizeof(testDefs) / sizeof(testDefs[0]))

int main(int argc, char **argv)
{
	TestParams 		testParams;
	TestDefs		*testDef;
	TestDefs		*ourTestDefs = testDefs;
	CSSM_RETURN		crtn;
	int 			arg;
	char			*argp;
	unsigned		cmdLoops = 0;		// can be specified in cmd line
										// if not, use TestDefs.loops
	char			testSpec = '\0';	// allows specification of one test
										// otherwise run all
	HT_Alg			alg;
	const char		*algStr;
	int				firstAlg = FIRST_ALG;
	int				lastAlg = LAST_ALG;
	
	memset(&testParams, 0, sizeof(testParams));
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 't':
				testSpec = argp[2];
				break;
			case 'l':
				cmdLoops = atoi(&argp[2]);
				break;
			case 'a':
				if(argp[1] == '\0') {
					usage(argv);
				}
				switch(argp[2]) {
					case 'm':
						firstAlg = lastAlg = HA_MD5;
						break;
					case 's':
						firstAlg = lastAlg = HA_SHA1;
						break;
					case '4':
						firstAlg = lastAlg = HA_SHA224;
						break;
					case '2':
						firstAlg = lastAlg = HA_SHA256;
						break;
					case '3':
						firstAlg = lastAlg = HA_SHA384;
						break;
					case '5':
						firstAlg = lastAlg = HA_SHA512;
						break;
					default:
						usage(argv);
				}
				break;
			case 'o':
				ourTestDefs = testDefsOpenSSL;
				break;
			case 'c':
				ourTestDefs = testDefsCommonCrypto;
				break;
			case 'k':
				ourTestDefs = testDefsCryptKit;
				break;
			case 'v':
				testParams.dumpDigest = true; 
				break;
			default:
				usage(argv);
		}
	}

	testParams.cspHand = cspStartup();
	if(testParams.cspHand == 0) {
		printf("***Error attaching to CSP. Aborting.\n");
		exit(1);
	}
	
	for(unsigned testNum=0; testNum<NUM_TESTS; testNum++) {
		testDef = &ourTestDefs[testNum];
		
		if(testSpec && (testDef->testSpec != testSpec)) {
			continue;
		}
		if(testDef->run == NULL) {
			continue;
		}
		printf("%s:\n", testDef->testName);
		if(cmdLoops) {
			/* user specified */
			testParams.loops = cmdLoops;
		}	
		else {
			/* default */
			testParams.loops = testDef->loops;
		}
		for(alg=firstAlg; alg<=lastAlg; alg++) {
			algToAlgId(alg, &testParams.algId, &algStr);
			printf("   === %s ===\n", algStr);
			crtn = testDef->run(&testParams);
			if(crtn) {
				exit(1);
			}
		}
	}
	return 0;
}