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


//
// MetaRecord.cpp
//

#include "MetaRecord.h"
#include <Security/trackingallocator.h>

MetaRecord::MetaRecord(CSSM_DB_RECORDTYPE inRecordType) :
    mRecordType(inRecordType)
{
}

MetaRecord::MetaRecord(const CSSM_DB_RECORD_ATTRIBUTE_INFO &inInfo)
:	mRecordType(inInfo.DataRecordType)
{
	try
	{
		setRecordAttributeInfo(inInfo);
	}
	catch (...)
	{
		for_each_delete(mAttributeVector.begin(), mAttributeVector.end());
	}
}

MetaRecord::MetaRecord(CSSM_DB_RECORDTYPE inRelationID,
                       uint32 inNumberOfAttributes,
					   const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo) :
    mRecordType(inRelationID)
{
	// XXX Is there any particular reason not to allow this?
#if 0
	if (inNumberOfAttributes == 0 || inAttributeInfo == NULL)
		CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_NUM_ATTRIBUTES);
#endif

	try {
		for (uint32 anIndex = 0; anIndex < inNumberOfAttributes; anIndex++)
		{
			string aName;
			if (inAttributeInfo[anIndex].AttributeName)
				aName = string(inAttributeInfo[anIndex].AttributeName);
				
			const CssmData *aNameID = NULL;
			if (inAttributeInfo[anIndex].AttributeNameID.Length > 0)
				aNameID = &CssmData::overlay(inAttributeInfo[anIndex].AttributeNameID);

			uint32 aNumber = inAttributeInfo[anIndex].AttributeId;
			createAttribute(
				inAttributeInfo[anIndex].AttributeName ? &aName : NULL,
				aNameID, aNumber,
				inAttributeInfo[anIndex].DataType);
		}
	}
	catch (...)
	{
		for_each_delete(mAttributeVector.begin(), mAttributeVector.end());
	}
}

MetaRecord::~MetaRecord()
{
	for_each_delete(mAttributeVector.begin(), mAttributeVector.end());
}

void
MetaRecord::setRecordAttributeInfo(const CSSM_DB_RECORD_ATTRIBUTE_INFO &inInfo)
{
	// XXX Is there any particular reason not to allow this?
#if 0
    if (inInfo.NumberOfAttributes == 0 || inInfo.AttributeInfo == NULL)
        CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_NUM_ATTRIBUTES);
#endif

    for (uint32 anIndex = 0; anIndex < inInfo.NumberOfAttributes; anIndex++)
    {
        switch (inInfo.AttributeInfo[anIndex].AttributeNameFormat)
        {
            case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
            {
                string aName(inInfo.AttributeInfo[anIndex].Label.AttributeName);
                createAttribute(&aName, nil, anIndex,
								inInfo.AttributeInfo[anIndex].AttributeFormat);
                break;
            }
            case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
            {
                const CssmData &aNameID = CssmOid::overlay(inInfo.AttributeInfo[anIndex].Label.AttributeOID);
                createAttribute(nil, &aNameID, anIndex,
								inInfo.AttributeInfo[anIndex].AttributeFormat);
                break;
            }
            case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
            {
                uint32 aNumber = inInfo.AttributeInfo[anIndex].Label.AttributeID;
                createAttribute(nil, nil, aNumber,
								inInfo.AttributeInfo[anIndex].AttributeFormat);
                break;
            }
            default:
                CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
                break;
        }
    }
}

void
MetaRecord::createAttribute(const string *inAttributeName,
							const CssmOid *inAttributeOID,
                            uint32 inAttributeID,
							CSSM_DB_ATTRIBUTE_FORMAT inAttributeFormat)
{
	// Index of new element is current size of vector
    uint32 anAttributeIndex = mAttributeVector.size();
    bool aInsertedAttributeName = false;
    bool aInsertedAttributeOID = false;
    bool aInsertedAttributeID = false;

    if (inAttributeName)
    {
        if (!mNameStringMap.insert(NameStringMap::value_type(*inAttributeName, anAttributeIndex)).second)
            CssmError::throwMe(CSSMERR_DL_FIELD_SPECIFIED_MULTIPLE);
        aInsertedAttributeName = true;
    }
    try
    {
        if (inAttributeOID)
        {
            if (!mNameOIDMap.insert(NameOIDMap::value_type(*inAttributeOID, anAttributeIndex)).second)
                CssmError::throwMe(CSSMERR_DL_FIELD_SPECIFIED_MULTIPLE);
            aInsertedAttributeOID = true;
        }

		if (!mNameIntMap.insert(NameIntMap::value_type(inAttributeID, anAttributeIndex)).second)
			CssmError::throwMe(CSSMERR_DL_FIELD_SPECIFIED_MULTIPLE);
		aInsertedAttributeID = true;

		// Note: this no longer throws INVALID_FIELD_NAME since the attribute will always have
		// an attribute ID by which it is known

		mAttributeVector.push_back(MetaAttribute::create(inAttributeFormat,
			anAttributeIndex, inAttributeID));
    }
    catch(...)
    {
        if (aInsertedAttributeName)
            mNameStringMap.erase(*inAttributeName);
        if (aInsertedAttributeOID)
            mNameOIDMap.erase(*inAttributeOID);
        if (inAttributeID)
            mNameIntMap.erase(inAttributeID);
		
        throw;
    }
}


// Create a packed record from the given inputs.
void
MetaRecord::packRecord(WriteSection &inWriteSection,
                       const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
                       const CssmData *inData) const
{
    uint32 aDataSize;
    if (inData)
        aDataSize = inData->Length;
    else
        aDataSize = 0;

    inWriteSection.put(OffsetDataSize, aDataSize);
    uint32 anOffset = OffsetAttributeOffsets + AtomSize * mAttributeVector.size();
    if (aDataSize)
        anOffset = inWriteSection.put(anOffset, aDataSize, inData->Data);

    vector<uint32> aNumValues(mAttributeVector.size(), ~0UL);
    vector<CSSM_DATA_PTR> aValues(mAttributeVector.size());
    uint32 anIndex;

    if (inAttributes == NULL)
        inWriteSection.put(OffsetSemanticInformation, 0);
    else
    {
        inWriteSection.put(OffsetSemanticInformation, inAttributes->SemanticInformation);

        // Put the supplied attribute values into the list of attributes
        // and values.
        anIndex = inAttributes->NumberOfAttributes;
        // Make sure that AttributeData is a valid array.
		if (anIndex > 0)
			Required(inAttributes->AttributeData);

        while (anIndex-- > 0)
        {
            CSSM_DB_ATTRIBUTE_DATA &anAttribute = inAttributes->AttributeData[anIndex];
            uint32 anAttributeIndex = attributeIndex(anAttribute.Info);
			// Make sure that the caller specified the attribute values in the correct format.
			if (anAttribute.Info.AttributeFormat != mAttributeVector[anAttributeIndex]->attributeFormat())
				CssmError::throwMe(CSSMERR_DL_INCOMPATIBLE_FIELD_FORMAT);

            // If this attribute was specified before, throw.
            if (aNumValues[anAttributeIndex] != ~0UL)
                CssmError::throwMe(CSSMERR_DL_FIELD_SPECIFIED_MULTIPLE);

            aNumValues[anAttributeIndex] = anAttribute.NumberOfValues;
            aValues[anAttributeIndex] = anAttribute.Value;
        }
    }

    for (anIndex = 0; anIndex < mAttributeVector.size(); ++anIndex)
    {
        const MetaAttribute &aMetaAttribute = *mAttributeVector[anIndex];
        uint32 aNumberOfValues = aNumValues[anIndex];
        // Now call the parsingmodule for each attribute that
        // wasn't explicitly specified and that has a parsingmodule.
        if (aNumberOfValues == ~0UL)
            aNumberOfValues = aDataSize == 0 ? 0 : aMetaAttribute.parse(*inData, aValues[anIndex]);

        // XXX When do we throw CSSMERR_DL_MISSING_VALUE?  Maybe if an
		// attribute is part of a unique index.

        // Now we have a valuelist for this attribute.  Let's encode it.
        aMetaAttribute.packAttribute(inWriteSection, anOffset, aNumberOfValues, aValues[anIndex]);
    }

	inWriteSection.put(OffsetRecordSize, anOffset);
    inWriteSection.size(anOffset);
}

inline void
MetaRecord::unpackAttribute(const ReadSection &inReadSection,
							CssmAllocator &inAllocator,
                            CSSM_DB_ATTRIBUTE_DATA &inoutAttribute) const
{
    const MetaAttribute &aMetaAttribute = metaAttribute(inoutAttribute.Info);
    // XXX: See ISSUES on whether AttributeFormat should be an outputvalue or not.
	inoutAttribute.Info.AttributeFormat = aMetaAttribute.attributeFormat();
    aMetaAttribute.unpackAttribute(inReadSection, inAllocator,
                                   inoutAttribute.NumberOfValues,
								   inoutAttribute.Value);
}

void
MetaRecord::unpackRecord(const ReadSection &inReadSection,
						 CssmAllocator &inAllocator,
                         CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
                         CssmData *inoutData,
						 CSSM_QUERY_FLAGS inQueryFlags) const
{
	// XXX Use POD wrapper for inoutAttributes here.
	TrackingAllocator anAllocator(inAllocator);
    if (inoutData)
    {
        // XXX Treat KEY records specially.

        // If inQueryFlags & CSSM_QUERY_RETURN_DATA is true return the raw
        // key bits in the CSSM_KEY structure
        Range aDataRange = dataRange(inReadSection);
    	inoutData->Length = aDataRange.mSize;
        inoutData->Data = inReadSection.allocCopyRange(aDataRange, anAllocator);
    }

    if (inoutAttributes)
    {
        inoutAttributes->DataRecordType = dataRecordType();
        inoutAttributes->SemanticInformation = semanticInformation(inReadSection);
        uint32 anIndex = inoutAttributes->NumberOfAttributes;

        // Make sure that AttributeData is a valid array.
        if (anIndex > 0 && inoutAttributes->AttributeData == NULL)
            CssmError::throwMe(CSSM_ERRCODE_INVALID_POINTER);

        while (anIndex-- > 0)
        {
            unpackAttribute(inReadSection, anAllocator,
            				inoutAttributes->AttributeData[anIndex]);
        }
    }

	// Don't free anything the trackingAllocator allocated when it is destructed.
	anAllocator.commit();
}

// Return the index (0 though NumAttributes - 1) of the attribute
// represented by inAttributeInfo

#ifndef	NDEBUG
#define LOG_NAME_AS_STRING_FAIL		
#endif
uint32
MetaRecord::attributeIndex(const CSSM_DB_ATTRIBUTE_INFO &inAttributeInfo) const
{
	uint32 anIndex;
	switch (inAttributeInfo.AttributeNameFormat)
	{
	    case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
		{
			string aName(inAttributeInfo.Label.AttributeName);
			assert(aName.size() < 500);		// MDS leak debug
			NameStringMap::const_iterator it = mNameStringMap.find(aName);
			if (it == mNameStringMap.end()) {
				#ifdef	LOG_NAME_AS_STRING_FAIL
				printf("NAME_AS_STRING failure; attrName %s\n", 
					inAttributeInfo.Label.AttributeName);
				for(it = mNameStringMap.begin();
				    it != mNameStringMap.end();
					it++) {
						printf("name %s val %ul\n", it->first.c_str(), it->second);
				}
				#endif
				CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
			}
			anIndex = it->second;
			break;
		}
	    case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
	    {
			const CssmOid &aName = CssmOid::overlay(inAttributeInfo.Label.AttributeOID);
			NameOIDMap::const_iterator it = mNameOIDMap.find(aName);
			if (it == mNameOIDMap.end())
				CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
			anIndex = it->second;
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
		{
			uint32 aName = inAttributeInfo.Label.AttributeID;
			NameIntMap::const_iterator it = mNameIntMap.find(aName);
			if (it == mNameIntMap.end())
				CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
			anIndex = it->second;
			break;
		}
		default:
			CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
			break;
	}

	return anIndex;
}

const MetaAttribute &
MetaRecord::metaAttribute(const CSSM_DB_ATTRIBUTE_INFO &inAttributeInfo) const
{
	return *mAttributeVector[attributeIndex(inAttributeInfo)];
}

// Create a packed record from the given inputs and the old packed record inReadSection.
void
MetaRecord::updateRecord(const ReadSection &inReadSection,
						 WriteSection &inWriteSection,
						 const CssmDbRecordAttributeData *inAttributes,
						 const CssmData *inData,
						 CSSM_DB_MODIFY_MODE inModifyMode) const
{
	TrackingAllocator anAllocator(CssmAllocator::standard());

	// modify the opaque data associated with the record
	
    uint32 aDataSize;
	const uint8 *aDataData = NULL;
    
	if (inData)
	{
		// prepare to write new data
        aDataSize = inData->Length;
		aDataData = inData->Data;
	}
    else
	{
		// prepare to copy old data
        Range aDataRange = dataRange(inReadSection);
    	aDataSize = aDataRange.mSize;
		if (aDataSize)
			aDataData = inReadSection.range(aDataRange);
	}

	// compute the data offset; this will keep a running total of the record size
    uint32 anOffset = OffsetAttributeOffsets + AtomSize * mAttributeVector.size();
	
	// write the appropriate data to the new record
	inWriteSection.put(OffsetDataSize, aDataSize);
	if (aDataSize)
		anOffset = inWriteSection.put(anOffset, aDataSize, aDataData);

	// unpack the old attributes since some of them may need to be preserved
	
	auto_array<CssmDbAttributeData> attributeData(mAttributeVector.size());

	for (uint32 anAttributeIndex = mAttributeVector.size(); anAttributeIndex-- > 0; )
	{
		// unpack the old attribute data for this attribute index
		const MetaAttribute &attribute = *mAttributeVector[anAttributeIndex];
		attribute.unpackAttribute(inReadSection, anAllocator,
								  attributeData[anAttributeIndex].NumberOfValues,
								  attributeData[anAttributeIndex].Value);
	}
	
	// retrieve the currrent semantic information
	
	uint32 oldSemanticInformation = semanticInformation(inReadSection);
	
	// process each input attribute as necessary, based on the modification mode
	
	if (inAttributes == NULL)
	{
		// make sure the modification mode is NONE, otherwise it's an
		// error accordining to the spec
		if (inModifyMode != CSSM_DB_MODIFY_ATTRIBUTE_NONE)
			CssmError::throwMe(CSSMERR_DL_INVALID_MODIFY_MODE);
	}

	else {
	
		// modify the semantic information

		uint32 inSemanticInformation = inAttributes ? inAttributes->SemanticInformation : 0;

		if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_ADD)
			oldSemanticInformation |= inSemanticInformation;

		else if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_DELETE)
			oldSemanticInformation &= ~inSemanticInformation;
		
		else if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_REPLACE)
			oldSemanticInformation = inSemanticInformation;

		uint32 anIndex = inAttributes->NumberOfAttributes;
		if (anIndex > 0)
			Required(inAttributes->AttributeData);

		// modify the attributes

		while (anIndex-- > 0) {
	
			const CssmDbAttributeData &anAttribute = inAttributes->at(anIndex);
			uint32 anAttributeIndex = attributeIndex(anAttribute.info());
			if (anAttribute.format() != mAttributeVector[anAttributeIndex]->attributeFormat())
				CssmError::throwMe(CSSMERR_DL_INCOMPATIBLE_FIELD_FORMAT);

			CssmDbAttributeData &oldAttribute = attributeData[anAttributeIndex];
		
			// if the modify mode is ADD, merge new values with pre-existing values
		
			if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_ADD)
				oldAttribute.add(anAttribute, anAllocator);

			// if the modify mode is DELETE, remove the indicated values, or remove
			// all values if none are specified

			else if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_DELETE)
			{
				if (anAttribute.size() == 0)
					oldAttribute.deleteValues(anAllocator);
				else
					oldAttribute.deleteValues(anAttribute, anAllocator);
			}
		
			// if the modify mode is REPLACE, then replace the specified values, or
			// delete all values if no values are specified
		
			else if (inModifyMode == CSSM_DB_MODIFY_ATTRIBUTE_REPLACE)
			{
				oldAttribute.deleteValues(anAllocator);
				if (anAttribute.size() > 0)
					oldAttribute.add(anAttribute, anAllocator);
				else
					// The spec says "all values are deleted or the the value is replaced
					// with the default" but doesn't say which. We could call the parsing
					// module for the attribute here...if they were implemented! But instead
					// we choose "all values are deleted" and leave it at that.
					;
			}
		}
	}

	// write the resulting attributes into the new record
	
	inWriteSection.put(OffsetSemanticInformation, oldSemanticInformation);

	for (uint32 anIndex = 0; anIndex < mAttributeVector.size(); ++anIndex)
	{
		const MetaAttribute &metaAttribute = *mAttributeVector[anIndex];
		metaAttribute.packAttribute(inWriteSection, anOffset,
								    attributeData[anIndex].NumberOfValues,
									attributeData[anIndex].Value);
	}
	
	inWriteSection.put(OffsetRecordSize, anOffset);
	inWriteSection.size(anOffset);
}

