/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
// Tester - test driver for securityserver client side.
//
#include "testclient.h"
#include "testutils.h"
#include <unistd.h>		// getopt(3)
#include <set>


//
// Global constants
//
const CssmData null;		// zero pointer, zero length constant data
const AccessCredentials nullCred;	// null credentials

CSSM_GUID ssguid = { 1,2,3 };
CssmSubserviceUid ssuid(ssguid);


//
// Local functions
//
static void usage();
static void runtest(char type);


//
// Default test set
//
static char testCodes[] = ".cesaAbdkKt";


//
// Main program
//
int main(int argc, char *argv[])
{
	setbuf(stdout, NULL);

	long ranseq = 0;	// random stress test count
	long ranseed = 1;	// random seed for it

	int arg;
	while ((arg = getopt(argc, argv, "r:v")) != -1) {
		switch (arg) {
		case 'r': {
			ranseq = atoi(optarg);
			if (const char *colon = strchr(optarg, ':'))
				ranseed = atoi(colon + 1);
			else
				ranseed = getpid() ^ time(NULL);
			break;
			}
		case 'v':
			verbose = true;
			break;
		default:
			usage();
		}
	}
	if (optind < argc - 1)
		usage();
	const char *sequence = argv[optind];
	if (sequence && !strcmp(sequence, "+"))
		sequence = testCodes;
		
	if (ranseq) {	// repeated random (stress test) sequence
		if (!sequence)
			sequence = testCodes;
		printf("*** Random stress test: %ld iterations from <%s> with seed=%ld\n",
			ranseq, sequence, ranseed);
		srandom(ranseed);
		int setSize = strlen(sequence);
		for (long n = 0; n < ranseq; n++) {
			char type = sequence[random() % setSize];
			printf("\n[%ld:%c]", n, type);
			runtest(type);
		}
		printf("*** Random test sequence complete.\n");
		exit(0);
	} else {		// single-pass selected tests sequence
		if (!sequence)
			sequence = ".";	// default to ping test
		for (const char *s = sequence; *s; s++)
			runtest(*s);
		printf("*** Test sequence complete.\n");
		exit(0);
	}
}

void usage()
{
	fprintf(stderr, "Usage: SSTester [-r count[:seed]] [-v] [%s|.|+]\n",
		testCodes);
	exit(2);
}


//
// Run a single type test
//
void runtest(char type)
{	
	try {
		debug("SStest", "Start test <%c>", type);
		switch (type) {
		case '.':	// default
			integrity();
			break;
		case '-':
			adhoc();
			break;
		case 'a':
			acls();
			break;
		case 'A':
			authAcls();
			break;
		case 'b':
			blobs();
			break;
		case 'c':
			codeSigning();
			break;
		case 'd':
			databases();
			break;
		case 'e':
			desEncryption();
			break;
		case 'k':
			keychainAcls();
			break;
		case 'K':
			keyBlobs();
			break;
		case 's':
			signWithRSA();
			break;
		case 't':
			authorizations();
			break;
		case 'T':
			timeouts();
			break;
		default:
			error("Invalid test selection (%c)", type);
		}
		printf("** Test step complete.\n");
		debug("SStest", "End test <%c>", type);
	} catch (CssmCommonError &err) {
		error(err, "Unexpected exception");
	} catch (...) {
		error("Unexpected system exception");
	}
}


//
// Basic integrity test.
//
void integrity()
{
	ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard());
	
	printf("* Generating random sample: ");
	DataBuffer<11> sample;
	ss.generateRandom(sample);
	for (uint32 n = 0; n < sample.length(); n++)
		printf("%.2x", ((unsigned char *)sample)[n]);
	printf("\n");
}


//
// Database timeouts
// @@@ Incomplete and not satisfactory
//
void timeouts()
{
	printf("* Database timeout locks test\n");
    CssmAllocator &alloc = CssmAllocator::standard();
	ClientSession ss(alloc, alloc);
    
    DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL);
    DLDbIdentifier dbId2(ssuid, "/tmp/two", NULL);
	DBParameters initialParams1 = { 4, false };	// 4 seconds timeout
	DBParameters initialParams2 = { 8, false };	// 8 seconds timeout
    
	// credential to set keychain passphrase
    AutoCredentials pwCred(alloc);
    StringData password("mumbojumbo");
    pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(password));
    
	DbHandle db1 = ss.createDb(dbId1, &pwCred, NULL, initialParams1);
	DbHandle db2 = ss.createDb(dbId2, &pwCred, NULL, initialParams2);
	detail("Databases created");
    
    // generate a key
	const CssmCryptoData seed(StringData("rain tonight"));
	FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES,
		&::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64),
		&::Context::Attr(CSSM_ATTRIBUTE_SEED, seed),
		NULL);
    KeyHandle key;
    CssmKey::Header header;
    ss.generateKey(db1, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
        CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT,
        /*cred*/NULL, NULL, key, header);
    ss.generateKey(db2, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
        CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT,
        /*cred*/NULL, NULL, key, header);
	detail("Keys generated and stored");
	
	// credential to provide keychain passphrase
    AutoCredentials pwCred2(alloc);
    pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(password));

	//@@@ incomplete
	ss.releaseDb(db1);
	ss.releaseDb(db2);
}


//
// Ad-hoc test area.
// Used for whatever is needed at the moment...
//
void adhoc()
{
	printf("* Ad-hoc test sequence (now what does it do *this* time?)\n");
	
	Cssm cssm1;
	Cssm cssm2;
	cssm1->init();
	cssm2->init();
	
	{
		Module m1(gGuidAppleCSP, cssm1);
		Module m2(gGuidAppleCSP, cssm2);
		CSP r1(m1);
		CSP r2(m2);
		
		Digest d1(r1, CSSM_ALGID_SHA1);
		Digest d2(r2, CSSM_ALGID_SHA1);
		
		StringData foo("foo de doo da blech");
		DataBuffer<30> digest1, digest2;
		d1.digest(foo, digest1);
		d2.digest(foo, digest2);
		if (digest1 == digest2)
			detail("Digests verify");
		else
			error("Digests mismatch");
	}
	
	cssm1->terminate();
	cssm2->terminate();
}
