/*
 * 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.
 */


//
// testutils - utilities for unit test drivers
//
#include "testutils.h"

using namespace CssmClient;

bool verbose = false;


//
// Error and diagnostic drivers
//
void error(const char *msg = NULL, ...)
{
	if (msg) {
		va_list args;
		va_start(args, msg);
		vfprintf(stderr, msg, args);
		va_end(args);
		putc('\n', stderr);
	}
	abort();
}

void error(const CssmCommonError &err, const char *msg = NULL, ...)
{
	if (msg) {
		va_list args;
		va_start(args, msg);
		vfprintf(stderr, msg, args);
		va_end(args);
		fprintf(stderr, ": %s", cssmErrorString(err.cssmError()).c_str());
		putc('\n', stderr);
	}
	abort();
}

void detail(const char *msg = NULL, ...)
{
	if (verbose) {
		va_list args;
		va_start(args, msg);
		vfprintf(stdout, msg, args);
		va_end(args);
		putc('\n', stdout);
	}
}

void detail(const CssmCommonError &err, const char *msg)
{
	if (verbose)
		printf("%s (ok): %s\n", msg, cssmErrorString(err).c_str());
}

void prompt(const char *msg)
{
	if (isatty(fileno(stdin)))
		printf("[%s]", msg);
}

void prompt()
{
	if (isatty(fileno(stdin)))
		printf(" OK\n");
}


//
// FakeContext management
//
FakeContext::FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, uint32 count)
: Context(type, alg)
{
	NumberOfAttributes = count;
	ContextAttributes = new Attr[count];
}


FakeContext::FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, ...) 
: Context(type, alg)
{
	// count arguments
	va_list args;
	va_start(args, alg);
	uint32 count = 0;
	while (va_arg(args, Attr *))
		count++;
	va_end(args);
	
	// make vector
	NumberOfAttributes = count;
	ContextAttributes = new Attr[count];
	
	// stuff vector
	va_start(args, alg);
	for (uint32 n = 0; n < count; n++)
		(*this)[n] = *va_arg(args, Attr *);
	va_end(args);
}


//
// ACL test driver class
//
AclTester::AclTester(ClientSession &ss, const AclEntryInput *acl) : session(ss)
{
	// make up a DES key
	StringData keyBits("Tweedle!");
	CssmKey key(keyBits);
	key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
	
	// wrap in the key
	CssmData unwrappedData;
	FakeContext unwrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0);
    CssmKey::Header keyHeader;
    ss.unwrapKey(noDb, unwrapContext, noKey, noKey,
		key,
		CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
		CSSM_KEYATTR_EXTRACTABLE,
		NULL /*cred*/, acl,
		unwrappedData, keyRef, keyHeader);
	detail("Key seeded with ACL");
}


void AclTester::testWrap(const AccessCredentials *cred, const char *howWrong)
{
	FakeContext wrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0);
	CssmWrappedKey wrappedKey;
	try {
		session.wrapKey(wrapContext, noKey, keyRef,
			cred, NULL /*descriptive*/, wrappedKey);
		if (howWrong) {
			error("WRAP MISTAKENLY SUCCEEDED: %s", howWrong);
		}
		detail("extract OK");
	} catch (const CssmCommonError &err) {
		if (!howWrong)
			error(err, "FAILED TO EXTRACT KEY");
		detail(err, "extract failed OK");
	}
}

void AclTester::testEncrypt(const AccessCredentials *cred, const char *howWrong)
{
    CssmKey keyForm; memset(&keyForm, 0, sizeof(keyForm));
    StringData iv("Aardvark");
    StringData clearText("blah");
	CssmData remoteCipher;
    try {
        if (cred) {
            FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES,
                &::Context::Attr(CSSM_ATTRIBUTE_KEY, keyForm),
                &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv),
                &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8),
                &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1),
                &::Context::Attr(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS, *cred),
                NULL);
            session.encrypt(cryptoContext, keyRef, clearText, remoteCipher);
        } else {
            FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES,
                &::Context::Attr(CSSM_ATTRIBUTE_KEY, keyForm),
                &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv),
                &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8),
                &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1),
                NULL);
            session.encrypt(cryptoContext, keyRef, clearText, remoteCipher);
        }
		if (howWrong) {
			error("ENCRYPT MISTAKENLY SUCCEEDED: %s", howWrong);
		}
		detail("encrypt OK");
	} catch (CssmCommonError &err) {
		if (!howWrong)
			error(err, "FAILED TO ENCRYPT");
		detail(err, "encrypt failed");
	}
}


//
// Database test driver class
//
DbTester::DbTester(ClientSession &ss, const char *path,
	const AccessCredentials *cred, int timeout, bool sleepLock) 
: session(ss), dbId(ssuid, path, NULL)
{
	params.idleTimeout = timeout;
	params.lockOnSleep = sleepLock;
	dbRef = ss.createDb(dbId, cred, NULL, params);
	detail("Database %s created", path);
}


void DbTester::unlock(const char *howWrong)
{
	session.lock(dbRef);
	try {
		session.unlock(dbRef);
		if (howWrong)
			error("DATABASE MISTAKENLY UNLOCKED: %s", howWrong);
	} catch (CssmError &err) {
		if (!howWrong)
			error(err, howWrong);
		detail(err, howWrong);
	}
}

void DbTester::changePassphrase(const AccessCredentials *cred, const char *howWrong)
{
	session.lock(dbRef);
	try {
		session.changePassphrase(dbRef, cred);
		if (howWrong)
			error("PASSPHRASE CHANGE MISTAKENLY SUCCEEDED: %s", howWrong);
	} catch (CssmError &err) {
		if (!howWrong)
			error(err, howWrong);
		detail(err, howWrong);
	}
}
