1/*
2 * hashTimeSA.cpp - measure performance of digest ops, standalone version (no
3 *    dependency on Security.framewortk or on CommonCrypto portion of libSystem).
4 */
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <CommonCrypto/CommonDigest.h>		/* static lib used in Tiger */
10#include "MD5.h"				/* CryptKit version used in Panther and prior */
11#include "SHA1.h"				/* ditto */
12#include <Security/cssmtype.h>	/* for ALGID values */
13#include <Security/cssmapple.h>	/* more ALGID values */
14#include <CoreFoundation/CFDate.h>
15
16/* enumerate digest algorithms our way */
17typedef int HT_Alg;
18enum {
19	HA_MD5 = 0,
20	HA_SHA1,
21	HA_SHA224,
22	HA_SHA256,
23	HA_SHA384,
24	HA_SHA512
25};
26
27#define FIRST_ALG	HA_MD5
28#define LAST_ALG	HA_SHA512
29
30static void usage(char **argv)
31{
32    printf("Usage: %s c|k [option ...]\n", argv[0]);
33	printf("   c=CommonCrypto; k=CryptKit\n");
34    printf("Options:\n");
35	printf("   a=alg; default=all\n");
36	printf("           algs: m : MD5\n");
37	printf("                 s : SHA1\n");
38	printf("                 4 : SHA224\n");
39	printf("                 2 : SHA256\n");
40	printf("                 3 : SHA384\n");
41	printf("                 5 : SHA512\n");
42	printf("   l=loops (only valid if testspec is given)\n");
43	printf("   v verify digest by printing it\n");
44	exit(1);
45}
46
47static void dumpDigest(
48	const unsigned char *digest,
49	unsigned len)
50{
51	for(unsigned dex=0; dex<len; dex++) {
52		printf("%02X", *digest++);
53		if((dex % 4) == 3) {
54			printf(" ");
55		}
56	}
57	printf("\n");
58}
59
60/* sort-of random, but repeatable */
61static void initPtext(
62	unsigned char *ptext,
63	unsigned len)
64{
65	srandom(1);
66	for(unsigned dex=0; dex<len; dex++) {
67		*ptext++ = random();
68	}
69}
70
71/* passed to each test */
72typedef struct {
73	unsigned			loops;
74	CSSM_ALGORITHMS		algId;		// MD5, SHA1
75	bool				dumpDigest;
76} TestParams;
77
78#define MAX_DIGEST_SIZE		64			// we provide, no malloc below CSSM
79
80#define PTEXT_SIZE			1000		// to digest in bytes
81#define INNER_LOOPS			500
82
83
84/* SHA1 digest is not orthoganal, fix up here */
85static void ckSha1Final(
86	void *ctx,
87	unsigned char *digest)
88{
89	sha1GetDigest((sha1Obj)ctx, digest);
90}
91
92typedef void (*ckInitFcn)(void *digestCtx);
93typedef void (*ckUpdateFcn)(void *digestCtx, const void *data, unsigned len);
94typedef void (*ckFinalFcn)(void *digestCtx, unsigned char *digest);
95
96static CSSM_RETURN hashDataRateCryptKit(
97	TestParams	*params)
98{
99	ckUpdateFcn		updatePtr = NULL;
100	ckFinalFcn		finalPtr = NULL;
101	ckInitFcn			initPtr = NULL;
102	struct MD5Context	md5;
103	sha1Obj				sha;
104	void			*ctx;
105
106	unsigned 		loop;
107	unsigned		iloop;
108	double			startTime, endTime;
109	double			timeSpent, timeSpentMs;
110	uint8			ptext[PTEXT_SIZE];
111	uint8			digest[MAX_DIGEST_SIZE];
112	unsigned		digestLen = 16;
113
114	/* we reuse this one inside the loop */
115	switch(params->algId) {
116		case CSSM_ALGID_SHA1:
117			sha = sha1Alloc();
118			ctx = sha;
119			initPtr = (ckInitFcn)sha1Reinit;
120			updatePtr = (ckUpdateFcn)sha1AddData;
121			finalPtr = (ckFinalFcn)ckSha1Final;
122			digestLen = 20;
123			break;
124		case CSSM_ALGID_MD5:
125			ctx = &md5;
126			initPtr = (ckInitFcn)MD5Init;
127			updatePtr = (ckUpdateFcn)MD5Update;
128			finalPtr = (ckFinalFcn)MD5Final;
129			break;
130		default:
131			printf("***Sorry, CryptKit can only do SHA1 and MD5.\n");
132			return 1;
133	}
134
135	/* random data, const thru the loops */
136	initPtext(ptext, PTEXT_SIZE);
137
138	/* start critical timing loop */
139	startTime = CFAbsoluteTimeGetCurrent();
140	for(loop=0; loop<params->loops; loop++) {
141		initPtr(ctx);
142		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
143			updatePtr(ctx, ptext, PTEXT_SIZE);
144		}
145		finalPtr(ctx, digest);
146	}
147	endTime = CFAbsoluteTimeGetCurrent();
148	timeSpent = endTime - startTime;
149	timeSpentMs = timeSpent * 1000.0;
150
151	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
152	float totalBytes   = params->loops * bytesPerLoop;
153
154	/* careful, KByte = 1024, ms = 1/1000 */
155	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
156		bytesPerLoop, params->loops,
157		timeSpentMs, timeSpentMs / (double)params->loops,
158		((float)totalBytes / 1024.0) / timeSpent);
159	if(params->dumpDigest) {
160		dumpDigest(digest, digestLen);
161	}
162	return CSSM_OK;
163}
164
165typedef union {
166	CC_MD5_CTX		md5;
167	CC_SHA1_CTX		sha;
168	CC_SHA256_CTX	sha256;
169	CC_SHA512_CTX	sha512;
170} CC_CTX;
171
172typedef void (*ccInitFcn)(void *digestCtx);
173typedef void (*ccUpdateFcn)(void *digestCtx, const void *data, CC_LONG len);
174typedef void (*ccFinalFcn)(unsigned char *digest, void *digestCtx);
175
176static CSSM_RETURN hashDataRateCommonCrypto(
177	TestParams	*params)
178{
179	CC_CTX			ctx;
180	ccUpdateFcn		updatePtr = NULL;
181	ccFinalFcn		finalPtr = NULL;
182	ccInitFcn		initPtr = NULL;
183	unsigned 		loop;
184	unsigned		iloop;
185	double			startTime, endTime;
186	double			timeSpent, timeSpentMs;
187	uint8			ptext[PTEXT_SIZE];
188	uint8			digest[MAX_DIGEST_SIZE];
189	unsigned		digestLen = 16;
190
191	/* we reuse this one inside the loop */
192	switch(params->algId) {
193		case CSSM_ALGID_SHA1:
194			initPtr = (ccInitFcn)CC_SHA1_Init;
195			updatePtr = (ccUpdateFcn)CC_SHA1_Update;
196			finalPtr = (ccFinalFcn)CC_SHA1_Final;
197			digestLen = 20;
198			break;
199		case CSSM_ALGID_SHA224:
200			initPtr = (ccInitFcn)CC_SHA224_Init;
201			updatePtr = (ccUpdateFcn)CC_SHA224_Update;
202			finalPtr = (ccFinalFcn)CC_SHA224_Final;
203			digestLen = 28;
204			break;
205		case CSSM_ALGID_SHA256:
206			initPtr = (ccInitFcn)CC_SHA256_Init;
207			updatePtr = (ccUpdateFcn)CC_SHA256_Update;
208			finalPtr = (ccFinalFcn)CC_SHA256_Final;
209			digestLen = 32;
210			break;
211		case CSSM_ALGID_SHA384:
212			initPtr = (ccInitFcn)CC_SHA384_Init;
213			updatePtr = (ccUpdateFcn)CC_SHA384_Update;
214			finalPtr = (ccFinalFcn)CC_SHA384_Final;
215			digestLen = 48;
216			break;
217		case CSSM_ALGID_SHA512:
218			initPtr = (ccInitFcn)CC_SHA512_Init;
219			updatePtr = (ccUpdateFcn)CC_SHA512_Update;
220			finalPtr = (ccFinalFcn)CC_SHA512_Final;
221			digestLen = 64;
222			break;
223		case CSSM_ALGID_MD5:
224			initPtr = (ccInitFcn)CC_MD5_Init;
225			updatePtr = (ccUpdateFcn)CC_MD5_Update;
226			finalPtr = (ccFinalFcn)CC_MD5_Final;
227			digestLen = 16;
228			break;
229		default:
230			printf("***BRRRZAP!\n");
231			return 1;
232	}
233
234	/* random data, const thru the loops */
235	initPtext(ptext, PTEXT_SIZE);
236
237	/* start critical timing loop */
238	startTime = CFAbsoluteTimeGetCurrent();
239	for(loop=0; loop<params->loops; loop++) {
240		initPtr(&ctx);
241		for(iloop=0; iloop<INNER_LOOPS; iloop++) {
242			updatePtr(&ctx, ptext, PTEXT_SIZE);
243		}
244		finalPtr(digest, &ctx);
245	}
246	endTime = CFAbsoluteTimeGetCurrent();
247	timeSpent = endTime - startTime;
248	timeSpentMs = timeSpent * 1000.0;
249
250	float bytesPerLoop = INNER_LOOPS * PTEXT_SIZE;
251	float totalBytes   = params->loops * bytesPerLoop;
252
253	/* careful, KByte = 1024, ms = 1/1000 */
254	printf("   Digest %.0f bytes : %u ops in %.2f ms; %f ms/op, %.0f KBytes/s\n",
255		bytesPerLoop, params->loops,
256		timeSpentMs, timeSpentMs / (double)params->loops,
257		((float)totalBytes / 1024.0) / timeSpent);
258	if(params->dumpDigest) {
259		dumpDigest(digest, digestLen);
260	}
261	return CSSM_OK;
262}
263
264typedef CSSM_RETURN (*testRunFcn)(TestParams *testParams);
265
266/*
267 * Static declaration of a test
268 */
269typedef struct {
270	const char 			*testName;
271	unsigned			loops;
272	testRunFcn			run;
273	char				testSpec;		// for t=xxx cmd line opt
274} TestDefs;
275
276static TestDefs testDefsCryptKit =
277{ 	"Large data digest, CryptKit",
278	1000,
279	hashDataRateCryptKit,
280	'd',
281};
282
283static TestDefs testDefsCommonCrypto =
284{ 	"Large data digest, CommonCrypto",
285	1000,
286	hashDataRateCommonCrypto,
287	'd',
288};
289
290static void algToAlgId(
291	HT_Alg			alg,
292	CSSM_ALGORITHMS	*algId,
293	const char		**algStr)
294{
295	switch(alg) {
296		case HA_MD5:
297			*algId = CSSM_ALGID_MD5;
298			*algStr = "MD5";
299			break;
300		case HA_SHA1:
301			*algId = CSSM_ALGID_SHA1;
302			*algStr = "SHA1";
303			break;
304		case HA_SHA224:
305			*algId = CSSM_ALGID_SHA224;
306			*algStr = "SHA224";
307			break;
308		case HA_SHA256:
309			*algId = CSSM_ALGID_SHA256;
310			*algStr = "SHA256";
311			break;
312		case HA_SHA384:
313			*algId = CSSM_ALGID_SHA384;
314			*algStr = "SHA384";
315			break;
316		case HA_SHA512:
317			*algId = CSSM_ALGID_SHA512;
318			*algStr = "SHA512";
319			break;
320		default:
321			printf("***algToAlgId screwup\n");
322			exit(1);
323	}
324}
325
326int main(int argc, char **argv)
327{
328	TestParams 		testParams;
329	TestDefs		*testDefs = NULL;
330	CSSM_RETURN		crtn;
331	int 			arg;
332	char			*argp;
333	unsigned		cmdLoops = 0;		// can be specified in cmd line
334										// if not, use TestDefs.loops
335	HT_Alg			alg;
336	const char		*algStr;
337	int				firstAlg = FIRST_ALG;
338	int				lastAlg = LAST_ALG;
339
340	memset(&testParams, 0, sizeof(testParams));
341
342	if(argc < 2) {
343		usage(argv);
344	}
345	switch(argv[1][0]) {
346		case 'c':
347			testDefs = &testDefsCommonCrypto;
348			break;
349		case 'k':
350			testDefs = &testDefsCryptKit;
351			break;
352		default:
353			usage(argv);
354	}
355
356	for(arg=2; arg<argc; arg++) {
357		argp = argv[arg];
358		switch(argp[0]) {
359			case 'l':
360				cmdLoops = atoi(&argp[2]);
361				break;
362			case 'a':
363				if(argp[1] == '\0') {
364					usage(argv);
365				}
366				switch(argp[2]) {
367					case 'm':
368						firstAlg = lastAlg = HA_MD5;
369						break;
370					case 's':
371						firstAlg = lastAlg = HA_SHA1;
372						break;
373					case '4':
374						firstAlg = lastAlg = HA_SHA224;
375						break;
376					case '2':
377						firstAlg = lastAlg = HA_SHA256;
378						break;
379					case '3':
380						firstAlg = lastAlg = HA_SHA384;
381						break;
382					case '5':
383						firstAlg = lastAlg = HA_SHA512;
384						break;
385					default:
386						usage(argv);
387				}
388				break;
389			case 'v':
390				testParams.dumpDigest = true;
391				break;
392			default:
393				usage(argv);
394		}
395	}
396
397	printf("%s:\n", testDefs->testName);
398	if(cmdLoops) {
399		/* user specified */
400		testParams.loops = cmdLoops;
401	}
402	else {
403		/* default */
404		testParams.loops = testDefs->loops;
405	}
406	if((lastAlg > HA_SHA1) && (testDefs == &testDefsCryptKit)) {
407		/* CryptKit can only do MD5 and SHA1 */
408		lastAlg = HA_SHA1;
409	}
410	for(alg=firstAlg; alg<=lastAlg; alg++) {
411		algToAlgId(alg, &testParams.algId, &algStr);
412		printf("   === %s ===\n", algStr);
413		crtn = testDefs->run(&testParams);
414		if(crtn) {
415			printf("***Error detected in test, somehow....aborting.\n");
416			exit(1);
417		}
418	}
419	return 0;
420}
421