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


//
// KCCursor.cpp
//

#include "KCCursor.h"

#include "Item.h"
#include "Schema.h"
#include "cssmdatetime.h"
#include "Globals.h"
#include "StorageManager.h"
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <Security/SecKeychainAPIPriv.h>

using namespace KeychainCore;
using namespace CssmClient;
using namespace CSSMDateTimeUtils;

//
// KCCursorImpl
//
KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, SecItemClass itemClass, const SecKeychainAttributeList *attrList) :
	mSearchList(searchList),
	mCurrent(mSearchList.begin())
{
    recordType(Schema::recordTypeFor(itemClass));

	if (!attrList) // No additional selectionPredicates: we are done
		return;

	conjunctive(CSSM_DB_AND);
	const SecKeychainAttribute *end=&attrList->attr[attrList->count];
	// Add all the attrs in attrs list to the cursor.
	for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
	{
        const CssmDbAttributeInfo &info = Schema::attributeInfo(attr->tag);
        void *buf = attr->data;
        UInt32 length = attr->length;
        uint8 timeString[16];
    
        // XXX This code is duplicated in NewItemImpl::setAttribute()
        // Convert a 4 or 8 byte TIME_DATE to a CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE
        // style attribute value.
        if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE)
        {
            if (length == sizeof(UInt32))
            {
                MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf),
                                        16, &timeString);
                buf = &timeString;
                length = 16;
            }
            else if (length == sizeof(SInt64))
            {
                MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf),
                                            16, &timeString);
                buf = &timeString;
                length = 16;
            }
        }
        add(CSSM_DB_EQUAL,info, CssmData(buf,length));
	}
}

KCCursorImpl::KCCursorImpl(const StorageManager::KeychainList &searchList, const SecKeychainAttributeList *attrList) :
	mSearchList(searchList),
	mCurrent(mSearchList.begin())
{
	if (!attrList) // No additional selectionPredicates: we are done
		return;

	conjunctive(CSSM_DB_AND);
	bool foundClassAttribute=false;
	const SecKeychainAttribute *end=&attrList->attr[attrList->count];
	// Add all the attrs in attrs list to the cursor.
	for (const SecKeychainAttribute *attr=attrList->attr; attr != end; ++attr)
	{
		if (attr->tag!=kSecClassItemAttr)	// a regular attribute
		{
            const CssmDbAttributeInfo &info = Schema::attributeInfo(attr->tag);
            void *buf = attr->data;
            UInt32 length = attr->length;
            uint8 timeString[16];
        
            // XXX This code is duplicated in NewItemImpl::setAttribute()
            // Convert a 4 or 8 byte TIME_DATE to a CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE
            // style attribute value.
            if (info.format() == CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE)
            {
                if (length == sizeof(UInt32))
                {
                    MacSecondsToTimeString(*reinterpret_cast<const UInt32 *>(buf),
                                           16, &timeString);
                    buf = &timeString;
                    length = 16;
                }
                else if (length == sizeof(SInt64))
                {
                    MacLongDateTimeToTimeString(*reinterpret_cast<const SInt64 *>(buf),
                                                16, &timeString);
                    buf = &timeString;
                    length = 16;
                }
            }
			add(CSSM_DB_EQUAL,info, CssmData(buf,length));

			continue;
		}
		
		// the class attribute
		if (foundClassAttribute || attr->length != sizeof(SecItemClass))
			MacOSError::throwMe(paramErr); // We have 2 different 'clas' attributes

		recordType(Schema::recordTypeFor(*reinterpret_cast<SecItemClass *>(attr->data)));
		foundClassAttribute=true;
	}
}

KCCursorImpl::~KCCursorImpl()
{
}

bool
KCCursorImpl::next(Item &item)
{
	DbAttributes dbAttributes;
	DbUniqueRecord uniqueId;

	for (;;)
	{
		if (!mDbCursor)
		{
			if (mCurrent == mSearchList.end())
			{
				// No more keychains to search so we are done.
				return false;
			}

			mDbCursor = DbCursor((*mCurrent)->database(), *this);
		}

		bool gotRecord;
		try
		{
			gotRecord = mDbCursor->next(&dbAttributes, NULL, uniqueId);
		}
		catch(const CssmCommonError &err)
		{
			OSStatus status = err.osStatus();
			if (status != CSSMERR_DL_DATASTORE_DOESNOT_EXIST
				&& status != CSSMERR_DL_INVALID_RECORDTYPE)
				throw;

			gotRecord = false;
		}

		// If we did not get a record from the current keychain or the current
		// keychain did not exist skip to the next keychain in the list.
		if (!gotRecord)
		{
			++mCurrent;
			mDbCursor = DbCursor();
			continue;
		}

		// If doing a search for all records skip the db blob added by the
		// CSP/DL and skip symmetric key items.
		// @@@ This is wrong since we should only skip symmetric keys that are
		// group keys and not user generated symmetric keys.
		if (mDbCursor->recordType() == CSSM_DL_DB_RECORD_ANY &&
			(dbAttributes.recordType() == 0x80008000
			 || dbAttributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY))
			continue;

		break;
	}

	// Go though Keychain since item might already exist.
	item = (*mCurrent)->item(dbAttributes.recordType(), uniqueId);
	return true;
}
