ccSymTest.cpp   [plain text]


/* Copyright 2006 Apple Computer, Inc.
 *
 * ccSymTest.c - test CommonCrypto symmetric encrypt/decrypt.
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <CommonCrypto/CommonCryptor.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include "common.h"

/*
 * Defaults.
 */
#define LOOPS_DEF		500
#define MIN_DATA_SIZE	8
#define MAX_DATA_SIZE	10000						/* bytes */
#define MAX_KEY_SIZE	kCCKeySizeMaxRC4			/* bytes */
#define MAX_BLOCK_SIZE	kCCBlockSizeAES128			/* bytes */
#define LOOP_NOTIFY		250

/*
 * Enumerate algs our own way to allow iteration.
 */
typedef enum {
	ALG_AES_128 = 1,	/* 128 bit block, 128 bit key */
	ALG_AES_192,		/* 128 bit block, 192 bit key */
	ALG_AES_256,		/* 128 bit block, 256 bit key */
	ALG_DES,
	ALG_3DES,
	ALG_CAST,
	ALG_RC4,
	/* these aren't in CommonCrypto (yet?) */
	ALG_RC2,
	ALG_RC5,
	ALG_BFISH,
	ALG_ASC,
	ALG_NULL					/* normally not used */
} SymAlg;
#define ALG_FIRST			ALG_AES_128
#define ALG_LAST			ALG_RC4


#define LOG_SIZE			0
#if		LOG_SIZE
#define logSize(s)	printf s
#else
#define logSize(s)
#endif

static void usage(char **argv)
{
	printf("usage: %s [options]\n", argv[0]);
	printf("   Options:\n");
	printf("   a=algorithm (d=DES; 3=3DES; a=AES128; n=AES192; A=AES256; \n");
	printf("     c=CAST; 4=RC4; default=all)\n");
	printf("   l=loops (default=%d; 0=forever)\n", LOOPS_DEF);
	printf("   m=maxPtextSize (default=%d)\n", MAX_DATA_SIZE);
	printf("   n=minPtextSize (default=%d)\n", MIN_DATA_SIZE);
	printf("   k=keySizeInBytes\n");
	printf("   p=pauseInterval (default=0, no pause)\n");
	printf("   o (no padding, well-aligned plaintext)\n");
	printf("   e (ECB only)\n");
	printf("   E (CBC only, no ECB)\n");
	printf("   u (no multi-update ops)\n");
	printf("   U (only multi-update ops)\n");
	printf("   x (always allocate context)\n");
	printf("   X (never allocate context)\n");
	printf("   v(erbose)\n");
	printf("   q(uiet)\n");
	printf("   h(elp)\n");
	exit(1);
}

static void printCCError(const char *str, CCCryptorStatus crtn)
{
	const char *errStr;
	char unknownStr[200];
	
	switch(crtn) {
		case kCCSuccess: errStr = "kCCSuccess"; break;
		case kCCParamError: errStr = "kCCParamError"; break;
		case kCCBufferTooSmall: errStr = "kCCBufferTooSmall"; break;
		case kCCMemoryFailure: errStr = "kCCMemoryFailure"; break;
		case kCCAlignmentError: errStr = "kCCAlignmentError"; break;
		case kCCDecodeError: errStr = "kCCDecodeError"; break;
		case kCCUnimplemented: errStr = "kCCUnimplemented"; break;
		default:
			sprintf(unknownStr, "Unknown(%ld)\n", (long)crtn);
			errStr = unknownStr;
			break;
	}
	printf("***%s returned %s\n", str, errStr);
}

/* max context size */
#define CC_MAX_CTX_SIZE	kCCContextSizeRC4

/* 
 * We write a marker at end of expected output and at end of caller-allocated 
 * CCCryptorRef, and check at the end to make sure they weren't written 
 */
#define MARKER_LENGTH	8
#define MARKER_BYTE		0x7e

/* 
 * Test harness for CCCryptor with lots of options. 
 */
CCCryptorStatus doCCCrypt(
	bool forEncrypt,
	CCAlgorithm encrAlg,			
	bool doCbc,
	bool doPadding,
	const void *keyBytes, size_t keyLen,
	const void *iv,
	bool randUpdates,
	bool inPlace,								/* !doPadding only */
	size_t ctxSize,								/* if nonzero, we allocate ctx */
	bool askOutSize,
	const uint8_t *inText, size_t inTextLen,
	uint8_t **outText, size_t *outTextLen)		/* both returned, WE malloc */
{
	CCCryptorRef	cryptor = NULL;
	CCCryptorStatus crtn;
	CCOperation		op = forEncrypt ? kCCEncrypt : kCCDecrypt;
	CCOptions		options = 0;
	uint8_t			*outBuf = NULL;			/* mallocd output buffer */
	uint8_t			*outp;					/* running ptr into outBuf */
	const uint8		*inp;					/* running ptr into inText */
	size_t			outLen;					/* bytes remaining in outBuf */
	size_t			toMove;					/* bytes remaining in inText */
	size_t			thisMoveOut;			/* output from CCCryptUpdate()/CCCryptFinal() */
	size_t			outBytes;				/* total bytes actually produced in outBuf */
	char			ctx[CC_MAX_CTX_SIZE];	/* for CCCryptorCreateFromData() */
	uint8_t			*textMarker = NULL;		/* 8 bytes of marker here after expected end of 
											 * output */
	char			*ctxMarker = NULL;		/* ditto for caller-provided context */
	unsigned		dex;
	size_t			askedOutSize;			/* from the lib */
	size_t			thisOutLen;				/* dataOutAvailable we use */
	
	if(ctxSize > CC_MAX_CTX_SIZE) {
		printf("***HEY! Adjust CC_MAX_CTX_SIZE!\n");
		exit(1);
	}
	if(!doCbc) {
		options |= kCCOptionECBMode;
	}
	if(doPadding) {
		options |= kCCOptionPKCS7Padding;
	}
	
	/* just hack this one */
	outLen = inTextLen;
	if(forEncrypt) {
		outLen += MAX_BLOCK_SIZE;
	}
	
	outBuf = (uint8_t *)malloc(outLen + MARKER_LENGTH);
	memset(outBuf, 0xEE, outLen + MARKER_LENGTH);
	
	/* library should not touch this memory */
	textMarker = outBuf + outLen;
	memset(textMarker, MARKER_BYTE, MARKER_LENGTH);
	
	/* subsequent errors to errOut: */

	if(inPlace) {
		memmove(outBuf, inText, inTextLen);
		inp = outBuf;
	}
	else {
		inp = inText;
	}

	if(!randUpdates) {
		/* one shot */
		if(askOutSize) {
			crtn = CCCrypt(op, encrAlg, options,
				keyBytes, keyLen, iv,
				inp, inTextLen,
				outBuf, 0, &askedOutSize);
			if(crtn != kCCBufferTooSmall) {
				printf("***Did not get kCCBufferTooSmall as expected\n");
				printf("   alg %d inTextLen %lu cbc %d padding %d keyLen %lu\n",
					(int)encrAlg, (unsigned long)inTextLen, (int)doCbc, (int)doPadding,
					(unsigned long)keyLen);
				printCCError("CCCrypt", crtn);
				crtn = -1;
				goto errOut;
			}
			outLen = askedOutSize;
		}
		crtn = CCCrypt(op, encrAlg, options,
			keyBytes, keyLen, iv,
			inp, inTextLen,
			outBuf, outLen, &outLen);
		if(crtn) {
			printCCError("CCCrypt", crtn);
			goto errOut;
		}
		*outText = outBuf;
		*outTextLen = outLen;
		goto errOut;
	}
	
	/* random multi updates */
	if(ctxSize) {
		size_t ctxSizeCreated;
		
		if(askOutSize) {
			crtn = CCCryptorCreateFromData(op, encrAlg, options,
				keyBytes, keyLen, iv,
				ctx, 0 /* ctxSize */,
				&cryptor, &askedOutSize);
			if(crtn != kCCBufferTooSmall) {
				printf("***Did not get kCCBufferTooSmall as expected\n");
				printCCError("CCCryptorCreateFromData", crtn);
				crtn = -1;
				goto errOut;
			}
			ctxSize = askedOutSize;
		}
		crtn = CCCryptorCreateFromData(op, encrAlg, options,
			keyBytes, keyLen, iv,
			ctx, ctxSize, &cryptor, &ctxSizeCreated);
		if(crtn) {
			printCCError("CCCryptorCreateFromData", crtn);
			return crtn;
		}
		ctxMarker = ctx + ctxSizeCreated;
		memset(ctxMarker, MARKER_BYTE, MARKER_LENGTH);
	}
	else {
		crtn = CCCryptorCreate(op, encrAlg, options,
			keyBytes, keyLen, iv,
			&cryptor);
		if(crtn) {
			printCCError("CCCryptorCreate", crtn);
			return crtn;
		}
	}
	
	toMove = inTextLen;		/* total to go */
	outp = outBuf;
	outBytes = 0;			/* bytes actually produced in outBuf */
	
	while(toMove) {
		uint32 thisMoveIn;			/* input to CCryptUpdate() */
		
		thisMoveIn = genRand(1, toMove);
		logSize(("###ptext segment len %lu\n", (unsigned long)thisMoveIn)); 
		if(askOutSize) {
			thisOutLen = CCCryptorGetOutputLength(cryptor, thisMoveIn, false);
		}
		else {
			thisOutLen = outLen;
		}
		crtn = CCCryptorUpdate(cryptor, inp, thisMoveIn,
			outp, thisOutLen, &thisMoveOut);
		if(crtn) {
			printCCError("CCCryptorUpdate", crtn);
			goto errOut;
		}
		inp			+= thisMoveIn;
		toMove		-= thisMoveIn;
		outp		+= thisMoveOut;
		outLen   	-= thisMoveOut;
		outBytes	+= thisMoveOut;
	}
	
	if(doPadding) {
		/* Final is not needed if padding is disabled */
		if(askOutSize) {
			thisOutLen = CCCryptorGetOutputLength(cryptor, 0, true);
		}
		else {
			thisOutLen = outLen;
		}
		crtn = CCCryptorFinal(cryptor, outp, thisOutLen, &thisMoveOut);
	}
	else {
		thisMoveOut = 0;
		crtn = kCCSuccess;
	}
	
	if(crtn) {
		printCCError("CCCryptorFinal", crtn);
		goto errOut;
	}
	
	outBytes += thisMoveOut;
	*outText = outBuf;
	*outTextLen = outBytes;
	crtn = kCCSuccess;

	for(dex=0; dex<MARKER_LENGTH; dex++) {
		if(textMarker[dex] != MARKER_BYTE) {
			printf("***lib scribbled on our textMarker memory (op=%s)!\n",
				forEncrypt ? "encrypt" : "decrypt");
			crtn = (CCCryptorStatus)-1;
		}
	}
	if(ctxSize) {
		for(dex=0; dex<MARKER_LENGTH; dex++) {
			if(ctxMarker[dex] != MARKER_BYTE) {
				printf("***lib scribbled on our ctxMarker memory (op=%s)!\n",
					forEncrypt ? "encrypt" : "decrypt");
				crtn = (CCCryptorStatus)-1;
			}
		}
	}
	
errOut:
	if(crtn) {
		if(outBuf) {
			free(outBuf);
		}
	}
	if(cryptor) {
		CCCryptorRelease(cryptor);
	}
	return crtn;
}

static int doTest(const uint8_t *ptext,
	size_t ptextLen,
	CCAlgorithm encrAlg,			
	bool doCbc,
	bool doPadding,
	bool nullIV,			/* if CBC, use NULL IV */
	uint32 keySizeInBytes,
	bool stagedEncr,
	bool stagedDecr,
	bool inPlace,	
	size_t ctxSize,		
	bool askOutSize,
	bool quiet)
{
	uint8_t			keyBytes[MAX_KEY_SIZE];
	uint8_t			iv[MAX_BLOCK_SIZE];
	uint8_t			*ivPtrEncrypt;
	uint8_t			*ivPtrDecrypt;
	uint8_t			*ctext = NULL;		/* mallocd by doCCCrypt */
	size_t			ctextLen = 0;
	uint8_t			*rptext = NULL;		/* mallocd by doCCCrypt */
	size_t			rptextLen;
	CCCryptorStatus	crtn;
	int				rtn = 0;
	
	/* random key */
	appGetRandomBytes(keyBytes, keySizeInBytes);
	
	/* random IV if needed */
	if(doCbc) {
		if(nullIV) {
			memset(iv, 0, MAX_BLOCK_SIZE);
			
			/* flip a coin, give one side NULL, the other size zeroes */
			if(genRand(1,2) == 1) {
				ivPtrEncrypt = NULL;
				ivPtrDecrypt = iv;
			}
			else {
				ivPtrEncrypt = iv;
				ivPtrDecrypt = NULL;
			}
		}
		else {
			appGetRandomBytes(iv, MAX_BLOCK_SIZE);
			ivPtrEncrypt = iv;
			ivPtrDecrypt = iv;
		}
	}	
	else {
		ivPtrEncrypt = NULL;
		ivPtrDecrypt = NULL;
	}

	crtn = doCCCrypt(true, encrAlg, doCbc, doPadding,
		keyBytes, keySizeInBytes, ivPtrEncrypt,
		stagedEncr, inPlace, ctxSize, askOutSize,
		ptext, ptextLen,
		&ctext, &ctextLen);
	if(crtn) {
		rtn = testError(quiet);
		if(rtn) {
			goto abort;
		}
	}
	
	logSize(("###ctext len %lu\n", ctextLen)); 
	
	crtn = doCCCrypt(false, encrAlg, doCbc, doPadding,
		keyBytes, keySizeInBytes, ivPtrDecrypt,
		stagedDecr, inPlace, ctxSize, askOutSize,
		ctext, ctextLen,
		&rptext, &rptextLen);
	if(crtn) {
		rtn = testError(quiet);
		if(rtn) {
			goto abort;
		}
	}

	logSize(("###rptext len %lu\n", rptextLen)); 
	
	/* compare ptext, rptext */
	if(ptextLen != rptextLen) {
		printf("Ptext length mismatch: expect %lu, got %lu\n", ptextLen, rptextLen);
		rtn = testError(quiet);
		if(rtn) {
			goto abort;
		}
	}
	if(memcmp(ptext, rptext, ptextLen)) {
		printf("***data miscompare\n");
		rtn = testError(quiet);
	}
abort:
	if(ctext) {
		free(ctext);
	}
	if(rptext) {
		free(rptext);
	}
	return rtn;
}

bool isBitSet(unsigned bit, unsigned word) 
{
	if(bit > 31) {
		printf("We don't have that many bits\n");
		exit(1);
	}
	unsigned mask = 1 << bit;
	return (word & mask) ? true : false;
}

int main(int argc, char **argv)
{
	int					arg;
	char				*argp;
	unsigned			loop;
	uint8				*ptext;
	size_t				ptextLen;
	bool				stagedEncr = false;
	bool				stagedDecr = false;
	bool				doPadding;
	bool				doCbc = false;
	bool				nullIV;
	const char			*algStr;
	CCAlgorithm			encrAlg;	
	int					i;
	int					currAlg;		// ALG_xxx
	uint32				minKeySizeInBytes;
	uint32				maxKeySizeInBytes;
	uint32				keySizeInBytes = 0;
	int					rtn = 0;
	uint32				blockSize;		// for noPadding case
	size_t				ctxSize;		// always set per alg
	size_t				ctxSizeUsed;	// passed to doTest
	bool				askOutSize;		// inquire output size each op
	
	/*
	 * User-spec'd params
	 */
	bool		keySizeSpec = false;		// false: use rand key size
	SymAlg		minAlg = ALG_FIRST;
	SymAlg		maxAlg = ALG_LAST;
	unsigned	loops = LOOPS_DEF;
	bool		verbose = false;
	size_t		minPtextSize = MIN_DATA_SIZE;
	size_t		maxPtextSize = MAX_DATA_SIZE;
	bool		quiet = false;
	unsigned	pauseInterval = 0;
	bool		paddingSpec = false;		// true: user calls doPadding, const
	bool		cbcSpec = false;			// ditto for doCbc
	bool		stagedSpec = false;			// ditto for stagedEncr and stagedDecr
	bool		inPlace = false;			// en/decrypt in place for ECB
	bool		allocCtxSpec = false;		// use allocCtx
	bool		allocCtx = false;			// allocate context ourself
	
	for(arg=1; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'a':
				if(argp[1] != '=') {
					usage(argv);
				}
				switch(argp[2]) {
					case 's':
						minAlg = maxAlg = ALG_ASC;
						break;
					case 'd':
						minAlg = maxAlg = ALG_DES;
						break;
					case '3':
						minAlg = maxAlg = ALG_3DES;
						break;
					case '2':
						minAlg = maxAlg = ALG_RC2;
						break;
					case '4':
						minAlg = maxAlg = ALG_RC4;
						break;
					case '5':
						minAlg = maxAlg = ALG_RC5;
						break;
					case 'a':
						minAlg = maxAlg = ALG_AES_128;
						break;
					case 'n':
						minAlg = maxAlg = ALG_AES_192;
						break;
					case 'A':
						minAlg = maxAlg = ALG_AES_256;
						break;
					case 'b':
						minAlg = maxAlg = ALG_BFISH;
						break;
					case 'c':
						minAlg = maxAlg = ALG_CAST;
						break;
					default:
						usage(argv);
				}
				if(maxAlg > ALG_LAST) {
					/* we left them in the switch but we can't use them */
					usage(argv);
				}
				break;
		    case 'l':
				loops = atoi(&argp[2]);
				break;
		    case 'n':
				minPtextSize = atoi(&argp[2]);
				break;
		    case 'm':
				maxPtextSize = atoi(&argp[2]);
				break;
		    case 'k':
		    	minKeySizeInBytes = maxKeySizeInBytes = atoi(&argp[2]);
		    	keySizeSpec = true;
				break;
			case 'x':
				allocCtxSpec = true;
				allocCtx = true;
				break;
			case 'X':
				allocCtxSpec = true;
				allocCtx = false;
				break;
		    case 'v':
		    	verbose = true;
				break;
		    case 'q':
		    	quiet = true;
				break;
		    case 'p':
		    	pauseInterval = atoi(&argp[2]);;
				break;
			case 'o':
				doPadding = false;
				paddingSpec = true;
				break;
			case 'e':
				doCbc = false;
				cbcSpec = true;
				break;
			case 'E':
				doCbc = true;
				cbcSpec = true;
				break;
		    case 'u':
		    	stagedEncr = false;
		    	stagedDecr = false;
				stagedSpec = true;
				break;
		    case 'U':
		    	stagedEncr = true;
		    	stagedDecr = true;
				stagedSpec = true;
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	ptext = (uint8 *)malloc(maxPtextSize);
	if(ptext == NULL) {
		printf("Insufficient heap space\n");
		exit(1);
	}
	/* ptext length set in test loop */
	
	printf("Starting ccSymTest; args: ");
	for(i=1; i<argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");
	
	if(pauseInterval) {
		fpurge(stdin);
		printf("Top of test; hit CR to proceed: ");
		getchar();
	}

	for(currAlg=minAlg; currAlg<=maxAlg; currAlg++) {
		switch(currAlg) {
			case ALG_DES:
				encrAlg = kCCAlgorithmDES;
				blockSize = kCCBlockSizeDES;
				minKeySizeInBytes = kCCKeySizeDES;
				maxKeySizeInBytes = minKeySizeInBytes;
				ctxSize = kCCContextSizeDES;
				algStr = "DES";
				break;
			case ALG_3DES:
				encrAlg = kCCAlgorithm3DES;
				blockSize = kCCBlockSize3DES;
				minKeySizeInBytes = kCCKeySize3DES;
				maxKeySizeInBytes = minKeySizeInBytes;
				ctxSize = kCCContextSize3DES;
				
				algStr = "3DES";
				break;
			case ALG_AES_128:
				encrAlg = kCCAlgorithmAES128;
				blockSize = kCCBlockSizeAES128;
				minKeySizeInBytes = kCCKeySizeAES128;
				maxKeySizeInBytes = minKeySizeInBytes;
				ctxSize = kCCContextSizeAES128;
				algStr = "AES128";
				break;
			case ALG_AES_192:
				encrAlg = kCCAlgorithmAES128;
				blockSize = kCCBlockSizeAES128;
				minKeySizeInBytes = kCCKeySizeAES192;
				maxKeySizeInBytes = minKeySizeInBytes;
				ctxSize = kCCContextSizeAES128;
				algStr = "AES192";
				break;
			case ALG_AES_256:
				encrAlg = kCCAlgorithmAES128;
				blockSize = kCCBlockSizeAES128;
				minKeySizeInBytes = kCCKeySizeAES256;
				maxKeySizeInBytes = minKeySizeInBytes;
				ctxSize = kCCContextSizeAES128;
				algStr = "AES256";
				break;
			case ALG_CAST:
				encrAlg = kCCAlgorithmCAST;
				blockSize = kCCBlockSizeCAST;
				minKeySizeInBytes = kCCKeySizeMinCAST;
				maxKeySizeInBytes = kCCKeySizeMaxCAST;
				ctxSize = kCCContextSizeCAST;
				algStr = "CAST";
				break;
			case ALG_RC4:
				encrAlg = kCCAlgorithmRC4;
				blockSize = 0;
				minKeySizeInBytes = kCCKeySizeMinRC4;
				maxKeySizeInBytes = kCCKeySizeMaxRC4;
				ctxSize = kCCContextSizeRC4;
				algStr = "RC4";
				break;
			default:
				printf("***BRRZAP!\n");
				exit(1);
		}
		if(!quiet || verbose) {
			printf("Testing alg %s\n", algStr);
		}
		for(loop=1; ; loop++) {
			ptextLen = genRand(minPtextSize, maxPtextSize);
			appGetRandomBytes(ptext, ptextLen);
			
			/* per-loop settings */
			if(!keySizeSpec) {
				if(minKeySizeInBytes == maxKeySizeInBytes) {
					keySizeInBytes = minKeySizeInBytes;
				}
				else {
					keySizeInBytes = genRand(minKeySizeInBytes, maxKeySizeInBytes);
				}
			}
			if(blockSize == 0) {
				/* stream cipher */
				doCbc = false;
				doPadding = false;
			}
			else {
				if(!cbcSpec) {
					doCbc = isBitSet(0, loop);
				}
				if(!paddingSpec) {
					doPadding = isBitSet(1, loop);
				}
			}
			if(!doPadding && (blockSize != 0)) {
				/* align plaintext */
				ptextLen = (ptextLen / blockSize) * blockSize;
				if(ptextLen == 0) {
					ptextLen = blockSize;
				}
			}
			if(!stagedSpec) {
				stagedEncr = isBitSet(2, loop);
				stagedDecr = isBitSet(3, loop);
			}
			if(doCbc) {
				nullIV = isBitSet(4, loop);
			}
			else {
				nullIV = false;
			}
			inPlace = isBitSet(5, loop);
			if(allocCtxSpec) {
				ctxSizeUsed = allocCtx ? ctxSize : 0;
			}
			else if(isBitSet(6, loop)) {
				ctxSizeUsed = ctxSize;
			}
			else {
				ctxSizeUsed = 0;
			}
			askOutSize = isBitSet(7, loop);
			if(!quiet) {
			   	if(verbose || ((loop % LOOP_NOTIFY) == 0)) {
					printf("..loop %3d ptextLen %lu keyLen %d cbc=%d padding=%d stagedEncr=%d "
							"stagedDecr=%d\n",
						loop, (unsigned long)ptextLen, (int)keySizeInBytes, 
						(int)doCbc, (int)doPadding,
					 	(int)stagedEncr, (int)stagedDecr);
					printf("           nullIV %d inPlace %d ctxSize %d askOutSize %d\n",
						(int)nullIV, (int)inPlace, (int)ctxSizeUsed, (int)askOutSize);
				}
			}
			
			if(doTest(ptext, ptextLen,
					encrAlg, doCbc, doPadding, nullIV,
					keySizeInBytes,
					stagedEncr,	stagedDecr, inPlace, ctxSizeUsed, askOutSize,
					quiet)) {
				rtn = 1;
				break;
			}
			if(pauseInterval && ((loop % pauseInterval) == 0)) {
				char c;
				fpurge(stdin);
				printf("Hit CR to proceed, q to abort: ");
				c = getchar();
				if(c == 'q') {
					goto testDone;
				}
			}
			if(loops && (loop == loops)) {
				break;
			}
		}	/* main loop */
		if(rtn) {
			break;
		}
		
	}	/* for algs */
	
testDone:
	if(pauseInterval) {
		fpurge(stdin);
		printf("ModuleDetach/Unload complete; hit CR to exit: ");
		getchar();
	}
	if((rtn == 0) && !quiet) {
		printf("%s test complete\n", argv[0]);
	}
	free(ptext);
	return rtn;
}