dltests.cpp   [plain text]


#include "dltests.h"
#include "csptests.h"
#include "attributes.h"
#include <security_cdsa_client/multidldb.h>
#include <vector>
#include <security_cdsa_client/securestorage.h> // For CSPDL.
#include <security_cdsa_client/genkey.h>
#include <security_utilities/trackingallocator.h>

using namespace CssmClient;

// Configuration.
#define HEX_DIGITS_PER_LINE  20
#define INDENT_SIZE 2


const CSSM_GUID* gSelectedFileGuid = &gGuidAppleFileDL;



static void testDLCreate(const Guid &dlGuid);
static void testDLDelete(const Guid &dlGuid, bool throwOnError);
static void testGen(const Guid &cspDlGuid);
static void testDLCrypt(const Guid &cspDlGuid);
static void testMultiDLDb(const Guid &dlGuid);
static void dumpRelation(uint32 indent, Db &db, uint32 relationID, const char *relationName, bool printSchema);
static void dumpRecord(uint32 indent, const DbAttributes &record, const CssmData &data, const DbUniqueRecord &uniqueId);

#define CSSM_DB_RELATION(RELATIONID) RecordAttrInfo ## RELATIONID

#define CSSM_DB_DEFINE_RELATION_BEGIN(RELATIONID) \
static const CSSM_DB_ATTRIBUTE_INFO AttrInfo ## RELATIONID[] =

#define CSSM_DB_DEFINE_RELATION_END(RELATIONID) \
; \
static const CSSM_DB_RECORD_ATTRIBUTE_INFO CSSM_DB_RELATION(RELATIONID) = \
{ \
    RELATIONID, \
    sizeof(AttrInfo ## RELATIONID) / sizeof(CSSM_DB_ATTRIBUTE_INFO), \
    const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrInfo ## RELATIONID) \
}

// GENERIC PASSWORDS
CSSM_DB_DEFINE_RELATION_BEGIN(CSSM_DL_DB_RECORD_GENERIC_PASSWORD)
{
	CSSM_DB_ATTR(Attributes::Class), 
	CSSM_DB_ATTR(Attributes::CreationDate), 
	CSSM_DB_ATTR(Attributes::ModDate), 
	CSSM_DB_ATTR(Attributes::Description),
	CSSM_DB_ATTR(Attributes::Comment),
	CSSM_DB_ATTR(Attributes::Creator),
	CSSM_DB_ATTR(Attributes::Type),
	CSSM_DB_ATTR(Attributes::ScrCode),
	CSSM_DB_ATTR(Attributes::Label),
	CSSM_DB_ATTR(Attributes::Invisible),
	CSSM_DB_ATTR(Attributes::Negative),
	CSSM_DB_ATTR(Attributes::Custom),
	//CSSM_DB_ATTR(Attributes::Protected),
	CSSM_DB_ATTR(Attributes::Account),
	CSSM_DB_ATTR(Attributes::Service),
	CSSM_DB_ATTR(Attributes::Generic)
}
CSSM_DB_DEFINE_RELATION_END(CSSM_DL_DB_RECORD_GENERIC_PASSWORD);


// APPLESHARE PASSWORDS
CSSM_DB_DEFINE_RELATION_BEGIN(CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD)
{
	CSSM_DB_ATTR(Attributes::Class), 
	CSSM_DB_ATTR(Attributes::CreationDate), 
	CSSM_DB_ATTR(Attributes::ModDate), 
	CSSM_DB_ATTR(Attributes::Description),
	CSSM_DB_ATTR(Attributes::Comment),
	CSSM_DB_ATTR(Attributes::Creator),
	CSSM_DB_ATTR(Attributes::Type),
	CSSM_DB_ATTR(Attributes::ScrCode),
	CSSM_DB_ATTR(Attributes::Label),
	CSSM_DB_ATTR(Attributes::Invisible),
	CSSM_DB_ATTR(Attributes::Negative),
	CSSM_DB_ATTR(Attributes::Custom),
	//CSSM_DB_ATTR(Attributes::Protected),
	CSSM_DB_ATTR(Attributes::Volume),
	CSSM_DB_ATTR(Attributes::Addr),
	CSSM_DB_ATTR(Attributes::Signature),
	CSSM_DB_ATTR(Attributes::ProtocolType)
}
CSSM_DB_DEFINE_RELATION_END(CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD);

// INTERNET PASSWORDS
CSSM_DB_DEFINE_RELATION_BEGIN(CSSM_DL_DB_RECORD_INTERNET_PASSWORD)
{ 
	CSSM_DB_ATTR(Attributes::Class), 
	CSSM_DB_ATTR(Attributes::CreationDate), 
	CSSM_DB_ATTR(Attributes::ModDate), 
	CSSM_DB_ATTR(Attributes::Description),
	CSSM_DB_ATTR(Attributes::Comment),
	CSSM_DB_ATTR(Attributes::Creator),
	CSSM_DB_ATTR(Attributes::Type),
	CSSM_DB_ATTR(Attributes::ScrCode),
	CSSM_DB_ATTR(Attributes::Label),
	CSSM_DB_ATTR(Attributes::Invisible),
	CSSM_DB_ATTR(Attributes::Negative),
	CSSM_DB_ATTR(Attributes::Custom),
	//CSSM_DB_ATTR(Attributes::Protected),
	CSSM_DB_ATTR(Attributes::Account),
	CSSM_DB_ATTR(Attributes::SecDomain),
	CSSM_DB_ATTR(Attributes::Server),
	CSSM_DB_ATTR(Attributes::AuthType),
	CSSM_DB_ATTR(Attributes::Port),
	CSSM_DB_ATTR(Attributes::Path),
	CSSM_DB_ATTR(Attributes::ProtocolType)
}
CSSM_DB_DEFINE_RELATION_END(CSSM_DL_DB_RECORD_INTERNET_PASSWORD);

// INTERNET PASSWORDS
CSSM_DB_DEFINE_RELATION_BEGIN(CSSM_DL_DB_RECORD_SYMMETRIC_KEY)
{ 
	CSSM_DB_ATTR(Attributes::KeyClass),
	CSSM_DB_ATTR(Attributes::PrintName),
	CSSM_DB_ATTR(Attributes::Alias),
	CSSM_DB_ATTR(Attributes::Permanent),
	CSSM_DB_ATTR(Attributes::Private),
	CSSM_DB_ATTR(Attributes::Modifiable),
	CSSM_DB_ATTR(Attributes::Label),
	CSSM_DB_ATTR(Attributes::ApplicationTag),
	CSSM_DB_ATTR(Attributes::KeyCreator),
	CSSM_DB_ATTR(Attributes::KeyType),
	CSSM_DB_ATTR(Attributes::KeySizeInBits),
	CSSM_DB_ATTR(Attributes::EffectiveKeySize),
	CSSM_DB_ATTR(Attributes::StartDate),
	CSSM_DB_ATTR(Attributes::EndDate),
	CSSM_DB_ATTR(Attributes::Sensitive),
	CSSM_DB_ATTR(Attributes::AlwaysSensitive),
	CSSM_DB_ATTR(Attributes::Extractable),
	CSSM_DB_ATTR(Attributes::NeverExtractable),
	CSSM_DB_ATTR(Attributes::Encrypt),
	CSSM_DB_ATTR(Attributes::Decrypt),
	CSSM_DB_ATTR(Attributes::Derive),
	CSSM_DB_ATTR(Attributes::Sign),
	CSSM_DB_ATTR(Attributes::Verify),
	CSSM_DB_ATTR(Attributes::SignRecover),
	CSSM_DB_ATTR(Attributes::VerifyRecover),
	CSSM_DB_ATTR(Attributes::Wrap),
	CSSM_DB_ATTR(Attributes::UnWrap)
}
CSSM_DB_DEFINE_RELATION_END(CSSM_DL_DB_RECORD_SYMMETRIC_KEY);


static const CSSM_DB_RECORD_ATTRIBUTE_INFO KCAttrs[] =
{
	CSSM_DB_RELATION(CSSM_DL_DB_RECORD_GENERIC_PASSWORD),
	CSSM_DB_RELATION(CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD),
	CSSM_DB_RELATION(CSSM_DL_DB_RECORD_INTERNET_PASSWORD)
	//CSSM_DB_RELATION(CSSM_DL_DB_RECORD_SYMMETRIC_KEY)
};

static const CSSM_DB_RECORD_INDEX_INFO recordIndex = 
{
    CSSM_DB_RECORDTYPE_APP_DEFINED_START, // CSSM_DB_RECORDTYPE
    0,	//%%% for now
    NULL	//%%% for now
};
static const    CSSM_DB_RECORD_INDEX_INFO recordIndexes[] = {recordIndex, recordIndex, recordIndex};

// parse info (to improve later)
static const        CSSM_DB_PARSING_MODULE_INFO parseInfo =
{
    CSSM_DB_RECORDTYPE_APP_DEFINED_START,
    {
        {0,0,0,{0}},
        {0,0},
        0,
        0
    }
};
static const    CSSM_DB_PARSING_MODULE_INFO parseInfos[] = {parseInfo, parseInfo, parseInfo};

static const CSSM_DBINFO	KCDBInfo =
{
    sizeof(KCAttrs) / sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO),
    const_cast<CSSM_DB_PARSING_MODULE_INFO_PTR>(parseInfos),
    const_cast<CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR>(KCAttrs),
    const_cast<CSSM_DB_RECORD_INDEX_INFO_PTR>(recordIndexes),
    CSSM_TRUE,
    NULL,
    NULL
};



void dltests(bool autoCommit)
{
	testDLDelete(gGuidAppleFileDL, false);
	testDLCreate(gGuidAppleFileDL);
	testMultiDLDb(gGuidAppleFileDL);

	testDLDelete(gGuidAppleCSPDL, false);
	testDLCreate(gGuidAppleCSPDL);
	testGen(gGuidAppleCSPDL);
	testDLCrypt(gGuidAppleCSPDL);
	testMultiDLDb(gGuidAppleCSPDL);
	//testDLDelete(gGuidAppleCSPDL, true);
}

static void testDLCreate(const Guid &dlGuid)
{
	DL appledl(dlGuid);
	Db testDb(appledl, DBNAME1);
	testDb->dbInfo(&KCDBInfo);
	testDb->create();
}

static void testDLDelete(const Guid &dlGuid, bool throwOnError)
{
	DL appledl(dlGuid);
	Db testDb(appledl, DBNAME1);
	try
	{
		testDb->deleteDb();
	}
	catch(CssmError e)
	{
		if (throwOnError || e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST)
			throw;
	}
}

static void testGen(const Guid &cspDlGuid)
{
	printf("\n* performing CSP/DL keygen test...\n");
	CSPDL cspdl(cspDlGuid);
	Db db(cspdl, DBNAME1);

    printf("Generating permanent key\n");
	GenerateKey genKey(cspdl, CSSM_ALGID_DES, 64);
	genKey.database(db);
	CssmPolyData label("First Key!");
	Key key = genKey(KeySpec(CSSM_KEYUSE_ANY,
							 CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
							 label));
    printf("done\n");
}

static void testDLCrypt(const Guid &cspDlGuid)
{
    printf("\n* performing encrypt/decrypt test...\n");
	CSPDL cspdl(cspDlGuid);
	Db db(cspdl, DBNAME1);

    printf("Finding key\n");
	DbCursor cursor(db);
	cursor->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
	DbUniqueRecord keyId;
	CssmDataContainer keyData;
	if (!cursor->next(NULL, &keyData, keyId))
		CssmError::throwMe(CSSMERR_DL_ENDOFDATA);

	Key key(cspdl, *reinterpret_cast<CSSM_KEY *>(keyData.Data));

    printf("done\n");

	// Gnerate IV
    printf("Generating iv\n");
	//CssmData iv = Random(csp, CSSM_ALGID_SHARandom)(8);
	CssmPolyData iv("12345678");

	CssmPolyData in("Om mani padme hum");
	printf("input=");
	dump(in);

	// Encrypt
    printf("Encrypting\n");

	Encrypt encrypt(cspdl, CSSM_ALGID_DES);
	encrypt.mode(CSSM_ALGMODE_CBCPadIV8);
	encrypt.padding(CSSM_PADDING_PKCS1);
	encrypt.initVector(iv);
	encrypt.key(key);
	CssmData cipher;
	CssmData remcipher;
	encrypt.encrypt(&in, 1, &cipher, 1);
	encrypt.final(remcipher);
	printf("ciphertext=");
	dump(cipher);
	printf("remainder=");
	dump(remcipher);

	// Decrypt
    printf("Decrypting\n");

	Decrypt decrypt(cspdl, CSSM_ALGID_DES);
	decrypt.key(key);
	decrypt.mode(CSSM_ALGMODE_CBCPadIV8);
	decrypt.padding(CSSM_PADDING_PKCS1);
	decrypt.initVector(iv);
	CssmData plain;
	CssmData remplain;
	CssmData inp[] = { cipher, remcipher };
	decrypt.decrypt(inp, 2, &plain, 1);
	decrypt.final(remplain);
	printf("plaintext=");
	dump(plain);
	printf("remainder=");
	dump(remplain);

    printf("end encrypt/decrypt test\n");
}

static void print(sint32 value)
{
	printf("%ld", value);
}

static void print(double value)
{
	printf("%g", value);
}

static void print(uint32 value)
{
	uint8 *bytes = reinterpret_cast<uint8 *>(&value);
	bool ascii = true;
	for (uint32 ix = 0; ix < sizeof(uint32); ++ix)
		if (bytes[ix] < 0x20 || bytes[ix] > 0x7f)
		{
			ascii = false;
			break;
		}

	if (ascii)
	{
		putchar('\'');
		for (uint32 ix = 0; ix < sizeof(uint32); ++ix)
			putchar(bytes[ix]);

		printf("' (0x%08lx)", value);
	}
	else
		printf("0x%08lx", value);
}

static void printAsString(uint32 indent, const CSSM_DATA &value)
{
	printf("%.*s", static_cast<int>(value.Length), value.Data);
}

static void print(uint32 indent, const char *value)
{
	printf("%s", value);
}

static void printIndent(uint32 indent)
{
	//if (indent == 0)
	//	return;

	putchar('\n');
	for (uint32 ix = 0; ix < indent; ++ix)
		putchar(' ');
}

static void printRange(uint32 length, const uint8 *data)
{
	for (uint32 ix = 0; ix < HEX_DIGITS_PER_LINE; ++ix)
	{
		if (ix && ix % 4 == 0)
			putchar(' ');

		if (ix < length)
			printf("%02x", static_cast<unsigned int>(data[ix]));
		else
			printf("  ");
	}

	printf("  ");
	for (uint32 ix = 0; ix < length; ++ix)
	{
		if (data[ix] < 0x20 || data[ix] > 0x7f)
			putchar('.');
		else
			putchar(data[ix]);
	}
}

static void print(uint32 indent, const CSSM_DATA &value)
{
	if (value.Length == 0)
		return;

	if (value.Length > HEX_DIGITS_PER_LINE)
	{
		uint32 ix;
		for (ix = 0; ix < value.Length - HEX_DIGITS_PER_LINE; ix += HEX_DIGITS_PER_LINE)
		{
			printIndent(indent);
			printRange(HEX_DIGITS_PER_LINE, &value.Data[ix]);
		}
		printIndent(indent);
		printRange(value.Length - ix, &value.Data[ix]);
		printIndent(indent - INDENT_SIZE);
	}
	else
		printRange(value.Length, value.Data);
}

static void printOID(uint32 indent, const CSSM_OID &value)
{
	print(indent, value);
}

static const char *format(CSSM_DB_ATTRIBUTE_FORMAT format)
{
	switch(format)
	{
	case CSSM_DB_ATTRIBUTE_FORMAT_STRING: return "string";
	case CSSM_DB_ATTRIBUTE_FORMAT_SINT32: return "sint32";
	case CSSM_DB_ATTRIBUTE_FORMAT_UINT32: return "uint32";
	case CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM: return "big_num";
	case CSSM_DB_ATTRIBUTE_FORMAT_REAL: return "real";
	case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE: return "time_date";
	case CSSM_DB_ATTRIBUTE_FORMAT_BLOB: return "blob";
	case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32: return "multi_uint32";
	case CSSM_DB_ATTRIBUTE_FORMAT_COMPLEX: return "complex";
	default: abort();
	}
}

static void print(uint32 indent, const CssmDbAttributeData &attr)
{
	bool multiValues = false;
	if (attr.size() == 0)
	{
		printf("<array/>");
		return;
	}

	if (attr.size() != 1)
	{
		printIndent(indent);
		printf("<array>");
		indent += INDENT_SIZE;
		multiValues = true;
	}

	for (uint32 ix = 0; ix < attr.size(); ++ix)
	{
		if (multiValues)
			printIndent(indent);

		printf("<%s>", format(attr.format()));
		switch (attr.format())
		{
		case CSSM_DB_ATTRIBUTE_FORMAT_STRING:
			printAsString(indent + INDENT_SIZE, attr.at(ix));
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_UINT32:
			print(attr.at<uint32>(ix));
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_SINT32:
			print(attr.at<sint32>(ix));
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_REAL:
			print(attr.at<double>(ix));
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE:
			printf("%*s", 15, attr.at<const char *>(ix));
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32:
		case CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM:
		case CSSM_DB_ATTRIBUTE_FORMAT_BLOB:
		case CSSM_DB_ATTRIBUTE_FORMAT_COMPLEX:
		default:
			print(indent + INDENT_SIZE, attr.at<const CssmData &>(ix));
			break;
		}
		printf("</%s>", format(attr.format()));
	}

	if (multiValues)
	{
		indent -= INDENT_SIZE;
		printIndent(indent);
		printf("</array>");
	}
}

static void print(uint32 indent, const CssmDbAttributeInfo &info)
{
		switch (info.nameFormat())
		{
		case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
		{
			printf("<string>");
			print(indent + INDENT_SIZE, info.Label.AttributeName);
			printf("</string>");
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
		{
			printf("<integer>");
			print(info.Label.AttributeID);
			printf("</integer>");
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
		{
			printf("<oid>");
			printOID(indent + INDENT_SIZE, info.Label.AttributeOID);
			printf("</oid>");
			break;
		}
		default:
			throw Error(CSSMERR_DL_DATABASE_CORRUPT);
		}
}

void dumpDb(char *dbName, bool printSchema)
{
	DL appledl(*gSelectedFileGuid);
	Db db(appledl, dbName);
	DbCursor relations(db);
	relations->recordType(CSSM_DL_DB_SCHEMA_INFO);
	DbAttributes schemaRecord(db, 2);
	schemaRecord.add(Attributes::RelationID);
	schemaRecord.add(Attributes::RelationName);
	CssmDataContainer data;
	DbUniqueRecord uniqueId(db);

	uint32 indent = 0;
	printf("<database>");
	indent += INDENT_SIZE;
	printIndent(indent);
	printf("<name>%s</name>", dbName);
	while (relations->next(&schemaRecord, &data, uniqueId))
	{
		uint32 relationID = schemaRecord.at(0);
		if (!printSchema && CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID
			&& relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
			continue;

		printIndent(indent);
		printf("<relation>");
		string relationName = schemaRecord.at(1);
		dumpRelation(indent + INDENT_SIZE, db, relationID, relationName.c_str(), printSchema);
		printIndent(indent);
		printf("</relation>");
	}

	indent -= INDENT_SIZE;
	printIndent(indent);
	printf("</database>\n");
}

static void dumpRelation(uint32 indent, Db &db, uint32 relationID, const char *relationName, bool printSchema)
{
	TrackingAllocator anAllocator(Allocator::standard());

	printIndent(indent);
	printf("<name>");
	print(indent + INDENT_SIZE, relationName);
	printf("</name>");
	printIndent(indent);
	printf("<id>");
	print(relationID);
	printf("</id>");

	// Create a cursor on the SCHEMA_ATTRIBUTES table for records with RelationID == relationID
	DbCursor attributes(db);
	attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES);
	attributes->add(CSSM_DB_EQUAL, Attributes::RelationID, relationID);

	// Set up a record for retriving the SCHEMA_ATTRIBUTES
	DbAttributes schemaRecord(db, 5);
	schemaRecord.add(Attributes::AttributeNameFormat);
	schemaRecord.add(Attributes::AttributeFormat);
	schemaRecord.add(Attributes::AttributeName);
	schemaRecord.add(Attributes::AttributeID);
	schemaRecord.add(Attributes::AttributeNameID);

	DbAttributes record(db);
	CssmDataContainer data;
	DbUniqueRecord uniqueId(db);

	if (printSchema)
	{
		printIndent(indent);
		printf("<schema>");
		indent += INDENT_SIZE;
	}

	while (attributes->next(&schemaRecord, &data, uniqueId))
	{
		CssmDbAttributeInfo &anInfo = record.add().info();
		anInfo.AttributeNameFormat = schemaRecord.at(0);
		anInfo.AttributeFormat = schemaRecord.at(1);
		switch (anInfo.AttributeNameFormat)
		{
		case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
		{
			CssmDbAttributeData &anAttributeName = schemaRecord.at(2);
			
			string name = static_cast<string>(anAttributeName);
			anInfo.Label.AttributeName = reinterpret_cast<char *>(anAllocator.malloc(name.size() + 1));
			strcpy(anInfo.Label.AttributeName, name.c_str());

			// XXX Need to copy the memory.  For now avoid it being freed.
			anAttributeName.Value[0].Data = NULL;
			anAttributeName.Value[0].Length = 0;
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
		{
			CssmDbAttributeData &anAttributeID = schemaRecord.at(3);
			anInfo.Label.AttributeID = anAttributeID;
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
		{
			CssmDbAttributeData &anAttributeOID = schemaRecord.at(4);
			anInfo.Label.AttributeOID = anAttributeOID;

			// XXX Need to copy the memory.  For now avoid it being freed.
			anAttributeOID.Value[0].Data = NULL;
			anAttributeOID.Value[0].Length = 0;
			break;
		}
		default:
			throw Error(CSSMERR_DL_DATABASE_CORRUPT);
		}

		if (printSchema)
		{
			printIndent(indent);
			print(indent, anInfo);
			printf("<format>%s</format>", format(anInfo.format()));
		}
	}

	if (printSchema)
	{
		indent -= INDENT_SIZE;
		printIndent(indent);
		printf("</schema>");
	}

	DbCursor records(db);
	records->recordType(relationID);
	printIndent(indent);
	printf("<records>");
	indent += INDENT_SIZE;
	while (records->next(&record, &data, uniqueId))
		dumpRecord(indent, record, data, uniqueId);

	indent -= INDENT_SIZE;
	printIndent(indent);
	printf("</records>");
}

static void
dumpRecord(uint32 indent, const DbAttributes &record, const CssmData &data, const DbUniqueRecord &uniqueId)
{
	const CSSM_DB_UNIQUE_RECORD *recId = static_cast<const DbUniqueRecord &>(uniqueId);
	uint32 recCount = recId->RecordIdentifier.Length;
	const uint32 *recArray = reinterpret_cast<const uint32 *>(recId->RecordIdentifier.Data);
	printIndent(indent);
	printf("<recid>");
	for (uint32 ix = 0; ix < recCount / 4; ++ix)
	{
		if (ix != 0)
			putchar(' ');
		printf("0x%08lx", recArray[ix]);
	}
	printf("</recid>");

	// Print the attributes
	printIndent(indent);
	if (record.size() == 0)
	{
		printf("<attributes/>");
	}
	else
	{
		printf("<attributes>");
		indent += INDENT_SIZE;
		for (uint32 ix = 0; ix < record.size(); ix++)
		{
			const CssmDbAttributeData &anAttr = record.at(ix);
			if (anAttr.size()) // Skip zero valued attributes.
			{
				printIndent(indent);
				print(indent + INDENT_SIZE, anAttr.info());
				print(indent + INDENT_SIZE, anAttr);
			}
		}

		indent -= INDENT_SIZE;
		printIndent(indent);
		printf("</attributes>");
	}

	// Print the data
	printIndent(indent);
	if (data.length())
	{
		printf("<data>");
		print(indent + INDENT_SIZE, data);
		printf("</data>");
	}
	else
		printf("<data/>");
}

static void testMultiDLDb(const Guid &dlGuid)
{
	// Setup a list of DLDbIdentifier object to hand off the MultiDLDb.
	vector<DLDbIdentifier> list;
	list.push_back(DLDbIdentifier(CssmSubserviceUid(dlGuid), "multidb1.db", NULL));
	list.push_back(DLDbIdentifier(CssmSubserviceUid(dlGuid), "multidb2.db", NULL));

	// Create MultiDLDb instance.
	MultiDLDb multiDLDb(list, false);

	// Get a handle for the first and second Db.
	Db db1(multiDLDb->database(list[0]));
	Db db2(multiDLDb->database(list[1]));

	// Until this point no CSSM API's have been called!

	// Delete both databases if they exist. 
	try
	{ db1->deleteDb(); }
	catch(CssmError e)
	{ if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) throw; }

	try
	{ db2->deleteDb(); }
	catch(CssmError e)
	{ if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST) throw; }

	// XXX Note to self if you set the schema but do not call create()
	// explicitly maybe the db should only be created if it did not yet exist...

	// Set the schema of both databases so they get created on activate.
	db1->dbInfo(&KCDBInfo);
	db2->dbInfo(&KCDBInfo);

	// Insert a record into each database.
	DbAttributes attrs(db1);
	attrs.add(Attributes::Comment, "This is the first comment").add("This is the second comment", attrs);
	attrs.add(Attributes::Label, "Item1");
	CssmPolyData testdata1("testdata1");
	db1->insert(CSSM_DL_DB_RECORD_GENERIC_PASSWORD, &attrs, &testdata1);

	attrs.clear();
	attrs.add(Attributes::Comment, "This is the second comment");
	attrs.add(Attributes::Label, "Item (in database2).");
	CssmPolyData testdata2("testdata2");
	db2->insert(CSSM_DL_DB_RECORD_GENERIC_PASSWORD, &attrs, &testdata2);

	// Create a cursor on the multiDLDb.
	DbCursor cursor(multiDLDb);
	// Set the type of records we wish to query.
	cursor->recordType(CSSM_DL_DB_RECORD_GENERIC_PASSWORD);
	cursor->add(CSSM_DB_EQUAL, Attributes::Comment, "This is the second comment");

	DbUniqueRecord uniqueId; // Empty uniqueId calling cursor.next will initialize.
	CssmDataContainer data; // XXX Think about data's allocator.

	// Iterate over the records in all the db's in the multiDLDb.
	while (cursor->next(&attrs, &data, uniqueId))
	{
		// Print the record data.
		dumpRecord(0, attrs, data, uniqueId);
	}
}

#if 0
	CssmDb::Impl *CssmDL::Impl::newDb(args) { new CssmDbImpl(args); }

	SecureStorage ss(Guid);
	CssmDb db(ss, DBNAME);
	CssmUniqueId unique;
	db.insert(attr, data, unique);

	Cursor cursor(db);
	CssmKey key;
	cursor.next(key);

	Cssm cssm;
	Module module(cssm);
	CSPDL cspdl(module);


	SecureStorage ss(Guid);
	CssmDb db = ss->db(DBNAME);
	CssmUniqueId unique;
	db->insert(attr, data, unique);

	Cursor cursor(db);
	CssmKey key;
	cursor->next(key);


#endif