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


//
// CSPsession - Plugin framework for CSP plugin modules
//
#include <security_cdsa_plugin/CSPsession.h>
#include <security_cdsa_plugin/cssmplugin.h>
#include <security_cdsa_utilities/cssmbridge.h>


typedef CSPFullPluginSession::CSPContext CSPContext;


//
// PluginContext construction
//
CSPPluginSession::PluginContext::~PluginContext()
{ }


//
// Internal utilities
//
CssmData CSPFullPluginSession::makeBuffer(size_t size, Allocator &alloc)
{
	return CssmData(alloc.malloc(size), size);
}

inline size_t CSPFullPluginSession::totalBufferSize(const CssmData *data, uint32 count)
{
	size_t size = 0;
	for (uint32 n = 0; n < count; n++)
		size += data[n].length();
	return size;
}


//
// Notify a context that its underlying CSSM context has (well, may have) changed.
// The default reaction is to ask the frame to delete the context and start over.
//
bool CSPPluginSession::PluginContext::changed(const Context &context)
{
    return false;	// delete me, please
}


//
// The Session's init() function calls your setupContext() method to prepare
// it for action, then calls the context's init() method.
//
CSPContext *CSPFullPluginSession::init(CSSM_CC_HANDLE ccHandle,
									CSSM_CONTEXT_TYPE type,
									const Context &context, bool encoding)
{
    CSPContext *ctx = getContext<CSPContext>(ccHandle);
    checkOperation(context.type(), type);

    // ask the implementation to set up an internal context
    setupContext(ctx, context, encoding);
    assert(ctx != NULL);	// must have context now (@@@ throw INTERNAL_ERROR instead?)
    ctx->mType = context.type();
    ctx->mDirection = encoding;
    setContext(ccHandle, ctx);

    // initialize the context and return it
    ctx->init(context, encoding);
    return ctx;
}


//
// Retrieve a context for a staged operation in progress.
//
CSPContext *CSPFullPluginSession::getStagedContext(CSSM_CC_HANDLE ccHandle,
	CSSM_CONTEXT_TYPE type, bool encoding)
{
	CSPContext *ctx = getContext<CSPContext>(ccHandle);
	if (ctx == NULL)
		CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);	//@@@ better diagnostic?
	checkOperation(ctx->type(), type);
	if (ctx->encoding() != encoding)
		CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);
	return ctx;
}


//
// The Session's checkState() function is called for subsequent staged operations
// (update/final) to verify that the user didn't screw up the sequencing.
//
void CSPFullPluginSession::checkOperation(CSSM_CONTEXT_TYPE ctxType, CSSM_CONTEXT_TYPE opType)
{
    switch (opType) {
        case CSSM_ALGCLASS_NONE:	// no check
            return;
        case CSSM_ALGCLASS_CRYPT:	// symmetric or asymmetric encryption
            if (ctxType == CSSM_ALGCLASS_SYMMETRIC ||
                ctxType == CSSM_ALGCLASS_ASYMMETRIC)
                return;
        default:					// plain match
            if (ctxType == opType)
                return;
    }
    CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);
}


//
// The default implementations of the primary context operations throw internal
// errors. You must implement any of these that are actually called by the
// operations involved. The others, of course, can be left alone.
//
void CSPContext::init(const Context &context, bool encoding)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::update(const CssmData &data)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::update(void *inp, size_t &inSize, void *outp, size_t &outSize)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::final(CssmData &out)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::final(const CssmData &in)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::generate(const Context &, CssmKey &pubKey, CssmKey &privKey)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::generate(const Context &, uint32, CssmData &params,
                                                uint32 &attrCount, Context::Attr * &attrs)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

size_t CSPContext::inputSize(size_t outSize)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

size_t CSPContext::outputSize(bool final, size_t inSize)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

void CSPContext::minimumProgress(size_t &in, size_t &out)
{ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); }

CSPFullPluginSession::CSPContext *CSPContext::clone(Allocator &)
{ CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); }

void CSPContext::setDigestAlgorithm(CSSM_ALGORITHMS digestAlg)
{ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); }

void CSPContext::update(const CssmData *in,
						uint32 inCount, Writer &writer)
{
    const CssmData *lastIn = in + inCount;
    CssmData current;
    for (;;) {
        if (current.length() == 0) {
            if (in == lastIn)
                return;		// all done
            current = *in++;
			continue; // Just in case next block is zero length too.
        }
        // match up current input and output buffers
        void *outP; size_t outSize;
        writer.nextBlock(outP, outSize);
        size_t inSize = inputSize(outSize);
        if (inSize > current.length())
            inSize = current.length();	// cap to remaining input buffer
        if (inSize > 0) {
            // we can stuff into the current output buffer - do it
            update(current.data(), inSize, outP, outSize);
            current.use(inSize);
            writer.use(outSize);
        } else {
            // We have remaining output buffer space, but not enough
            // for the algorithm to make progress with it. We must proceed with
            // a bounce buffer and split it manually into this and the next buffer(s).
            size_t minOutput;
            minimumProgress(inSize, minOutput);
            assert(minOutput > outSize);		// PluginContext consistency (not fatal)
            char splitBuffer[128];
            assert(minOutput <= sizeof(splitBuffer)); // @@@ static buffer for now
            outSize = sizeof(splitBuffer);
            if (current.length() < inSize)
                inSize = current.length();	// cap to data remaining in input buffer
            update(current.data(), inSize, splitBuffer, outSize);
            assert(inSize > 0);				// progress made
            writer.put(splitBuffer, outSize);	// stuff into buffer, the hard way
            current.use(inSize);
        }
    }
}

void CSPContext::final(CssmData &out, Allocator &alloc)
{
    size_t needed = outputSize(true, 0);
    if (out) {
        if (out.length() < needed)
            CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
    } else {
        out = makeBuffer(needed, alloc);
    }
    final(out);
}

void CSPContext::final(Writer &writer, Allocator &alloc)
{
    if (size_t needed = outputSize(true, 0)) {
        // need to generate additional output
        writer.allocate(needed, alloc);		// belt + suspender
    
        void *addr; size_t size;
        writer.nextBlock(addr, size);		// next single block available
        if (needed <= size) {				// rest fits into one block
            CssmData chunk(addr, size);
            final(chunk);
            writer.use(chunk.length());
        } else {							// need to split it up
            char splitBuffer[128];
            assert(needed <= sizeof(splitBuffer));
            CssmData chunk(splitBuffer, sizeof(splitBuffer));
            final(chunk);
            writer.put(chunk.data(), chunk.length());
        }
    }
}


//
// Default context response functions
//
CSPPluginSession::PluginContext *
CSPPluginSession::contextCreate(CSSM_CC_HANDLE, const Context &)
{
	return NULL;	// request no local context
}

void CSPPluginSession::contextUpdate(CSSM_CC_HANDLE ccHandle,
                                     const Context &context, PluginContext * &ctx)
{
    // call update notifier in context object
    if (ctx && !ctx->changed(context)) {
        // context requested that it be removed
        delete ctx;
        ctx = NULL;
    }
}

void CSPPluginSession::contextDelete(CSSM_CC_HANDLE, const Context &, PluginContext *)
{
    // do nothing (you can't prohibit deletion here)
}


//
// Default event notification handler.
// This default handler calls the virtual context* methods to dispose of context actions.
//
void CSPPluginSession::EventNotify(CSSM_CONTEXT_EVENT event,
                                   CSSM_CC_HANDLE ccHandle, const Context &context)
{
    switch (event) {
        case CSSM_CONTEXT_EVENT_CREATE:
            if (PluginContext *ctx = contextCreate(ccHandle, context)) {
				StLock<Mutex> _(contextMapLock);
                assert(contextMap[ccHandle] == NULL);	// check context re-creation
                contextMap[ccHandle] = ctx;
            }
            break;
        case CSSM_CONTEXT_EVENT_UPDATE:
            // note that the handler can change the map entry (even to NULL, if desired)
			{
				StLock<Mutex> _(contextMapLock);
				contextUpdate(ccHandle, context, contextMap[ccHandle]);
			}
            break;
        case CSSM_CONTEXT_EVENT_DELETE:
			{
				StLock<Mutex> _(contextMapLock);
				if (PluginContext *ctx = contextMap[ccHandle]) {
					contextDelete(ccHandle, context, ctx);
					delete ctx;
				}
				contextMap.erase(ccHandle);
			}
			break;
        default:
            CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);	// unexpected event code
    }
}


//
// Defaults for methods you *should* implement.
// If you don't, they'll throw UNIMPLEMENTED.
//
void CSPFullPluginSession::getKeySize(const CssmKey &key, CSSM_KEY_SIZE &size)
{ unimplemented(); }
 

//
// Encryption and decryption
//
void CSPFullPluginSession::EncryptData(CSSM_CC_HANDLE ccHandle,
                                       const Context &context,
                                       const CssmData clearBufs[],
                                       uint32 clearBufCount,
                                       CssmData cipherBufs[],
                                       uint32 cipherBufCount,
                                       uint32 &bytesEncrypted,
                                       CssmData &remData,
                                       CSSM_PRIVILEGE privilege)
{
    Writer writer(cipherBufs, cipherBufCount, &remData);
    CSPContext *ctx = init(ccHandle, CSSM_ALGCLASS_CRYPT, context, true);
    size_t outNeeded = ctx->outputSize(true, totalBufferSize(clearBufs, clearBufCount));
    writer.allocate(outNeeded, *this);
    ctx->update(clearBufs, clearBufCount, writer);
    ctx->final(writer, *this);
    bytesEncrypted = writer.close();
}

void CSPFullPluginSession::EncryptDataInit(CSSM_CC_HANDLE ccHandle,
                             const Context &context,
                             CSSM_PRIVILEGE Privilege)
{
    init(ccHandle, CSSM_ALGCLASS_CRYPT, context, true);
}

void CSPFullPluginSession::EncryptDataUpdate(CSSM_CC_HANDLE ccHandle,
                                         const CssmData clearBufs[],
                                         uint32 clearBufCount,
                                         CssmData cipherBufs[],
                                         uint32 cipherBufCount,
                                         uint32 &bytesEncrypted)
{
    CSPContext *alg = getStagedContext(ccHandle, CSSM_ALGCLASS_CRYPT, true);
    Writer writer(cipherBufs, cipherBufCount);
    size_t outNeeded = alg->outputSize(false, totalBufferSize(clearBufs, clearBufCount));
    writer.allocate(outNeeded, *this);
    alg->update(clearBufs, clearBufCount, writer);
    bytesEncrypted = writer.close();
}

void CSPFullPluginSession::EncryptDataFinal(CSSM_CC_HANDLE ccHandle,
                              CssmData &remData)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_CRYPT, true)->final(remData, *this);
}


void CSPFullPluginSession::DecryptData(CSSM_CC_HANDLE ccHandle,
                                   const Context &context,
                                   const CssmData cipherBufs[],
                                   uint32 cipherBufCount,
                                   CssmData clearBufs[],
                                   uint32 clearBufCount,
                                   uint32 &bytesDecrypted,
                                   CssmData &remData,
                                   CSSM_PRIVILEGE privilege)
{
    Writer writer(clearBufs, clearBufCount, &remData);
    CSPContext *ctx = init(ccHandle, CSSM_ALGCLASS_CRYPT, context, false);
    size_t outNeeded = ctx->outputSize(true, totalBufferSize(cipherBufs, cipherBufCount));
    writer.allocate(outNeeded, *this);
    ctx->update(cipherBufs, cipherBufCount, writer);
    ctx->final(writer, *this);
    bytesDecrypted = writer.close();
}

void CSPFullPluginSession::DecryptDataInit(CSSM_CC_HANDLE ccHandle,
                             const Context &context,
                             CSSM_PRIVILEGE Privilege)
{
    init(ccHandle, CSSM_ALGCLASS_CRYPT, context, false);
}

void CSPFullPluginSession::DecryptDataUpdate(CSSM_CC_HANDLE ccHandle,
                               const CssmData cipherBufs[],
                               uint32 cipherBufCount,
                               CssmData clearBufs[],
                               uint32 clearBufCount,
                               uint32 &bytesDecrypted)
{
    CSPContext *ctx = getStagedContext(ccHandle, CSSM_ALGCLASS_CRYPT, false);
    Writer writer(clearBufs, clearBufCount);
    size_t outNeeded = ctx->outputSize(false, totalBufferSize(cipherBufs, cipherBufCount));
    writer.allocate(outNeeded, *this);
    ctx->update(cipherBufs, cipherBufCount, writer);
    bytesDecrypted = writer.close();
}

void CSPFullPluginSession::DecryptDataFinal(CSSM_CC_HANDLE ccHandle,
                                      CssmData &remData)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_CRYPT, false)->final(remData, *this);
}
               
void CSPFullPluginSession::QuerySize(CSSM_CC_HANDLE ccHandle,
									 const Context &context,
									 CSSM_BOOL encrypt,
									 uint32 querySizeCount,
									 QuerySizeData *dataBlock)
{
	if (querySizeCount == 0)
		return;	// nothing ventured, nothing gained
	CSPContext *ctx = getContext<CSPContext>(ccHandle);	// existing context?
	if (ctx == NULL)	// force internal context creation (as best we can)
		ctx = init(ccHandle, context.type(), context, encrypt);
	// If QuerySizeCount > 1, we assume this inquires about a staged
	// operation, and the LAST item gets the 'final' treatment.
	//@@@ Intel revised algspec says "use the staged flag" -- TBD
	for (uint32 n = 0; n < querySizeCount; n++) {
		// the outputSize() call might throw CSSMERR_CSP_QUERY_SIZE_UNKNOWN
		dataBlock[n].SizeOutputBlock =
			ctx->outputSize(n == querySizeCount-1, dataBlock[n].inputSize());
	}
	//@@@ if we forced a context creation, should we discard it now?
}


//
// Key wrapping and unwrapping.
//
void CSPFullPluginSession::WrapKey(CSSM_CC_HANDLE CCHandle,
								const Context &Context,
								const AccessCredentials &AccessCred,
								const CssmKey &Key,
								const CssmData *DescriptiveData,
								CssmKey &WrappedKey,
								CSSM_PRIVILEGE Privilege)
{
	unimplemented();
}

void CSPFullPluginSession::UnwrapKey(CSSM_CC_HANDLE CCHandle,
								const Context &Context,
								const CssmKey *PublicKey,
								const CssmKey &WrappedKey,
								uint32 KeyUsage,
								uint32 KeyAttr,
								const CssmData *KeyLabel,
								const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
								CssmKey &UnwrappedKey,
								CssmData &DescriptiveData,
								CSSM_PRIVILEGE Privilege)
{
	unimplemented();
}

void CSPFullPluginSession::DeriveKey(CSSM_CC_HANDLE CCHandle,
							const Context &Context,
							CssmData &Param,
							uint32 KeyUsage,
							uint32 KeyAttr,
							const CssmData *KeyLabel,
							const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
							CssmKey &DerivedKey)
{
	unimplemented();
}


//
// Message Authentication Codes.
// Almost like signatures (signatures with symmetric keys), though the
// underlying implementation may be somewhat different.
//
void CSPFullPluginSession::GenerateMac(CSSM_CC_HANDLE ccHandle,
                                   const Context &context,
                                   const CssmData dataBufs[],
                                   uint32 dataBufCount,
                                   CssmData &mac)
{
    GenerateMacInit(ccHandle, context);
    GenerateMacUpdate(ccHandle, dataBufs, dataBufCount);
    GenerateMacFinal(ccHandle, mac);
}

void CSPFullPluginSession::GenerateMacInit(CSSM_CC_HANDLE ccHandle,
                                           const Context &context)
{
    init(ccHandle, CSSM_ALGCLASS_MAC, context, true);
}

void CSPFullPluginSession::GenerateMacUpdate(CSSM_CC_HANDLE ccHandle,
                                             const CssmData dataBufs[],
                                             uint32 dataBufCount)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_MAC, true)->update(dataBufs, dataBufCount);
}

void CSPFullPluginSession::GenerateMacFinal(CSSM_CC_HANDLE ccHandle,
                                            CssmData &mac)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_MAC, true)->final(mac, *this);
}

void CSPFullPluginSession::VerifyMac(CSSM_CC_HANDLE ccHandle,
                                     const Context &context,
                                     const CssmData dataBufs[],
                                     uint32 dataBufCount,
                                     const CssmData &mac)
{
    VerifyMacInit(ccHandle, context);
    VerifyMacUpdate(ccHandle, dataBufs, dataBufCount);
    VerifyMacFinal(ccHandle, mac);
}

void CSPFullPluginSession::VerifyMacInit(CSSM_CC_HANDLE ccHandle,
                                         const Context &context)
{
    init(ccHandle, CSSM_ALGCLASS_MAC, context, false);
}

void CSPFullPluginSession::VerifyMacUpdate(CSSM_CC_HANDLE ccHandle,
                                           const CssmData dataBufs[],
                                           uint32 dataBufCount)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_MAC, false)->update(dataBufs, dataBufCount);
}

void CSPFullPluginSession::VerifyMacFinal(CSSM_CC_HANDLE ccHandle,
                                          const CssmData &mac)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_MAC, false)->final(mac);
}


//
// Signatures
//
void CSPFullPluginSession::SignData(CSSM_CC_HANDLE ccHandle,
                                const Context &context,
                                const CssmData dataBufs[],
                                uint32 dataBufCount,
                                CSSM_ALGORITHMS digestAlgorithm,
                                CssmData &Signature)
{
	SignDataInit(ccHandle, context);
	if(digestAlgorithm != CSSM_ALGID_NONE) {
		getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, 
			true)->setDigestAlgorithm(digestAlgorithm);
	}
	SignDataUpdate(ccHandle, dataBufs, dataBufCount);
	SignDataFinal(ccHandle, Signature);
}

void CSPFullPluginSession::SignDataInit(CSSM_CC_HANDLE ccHandle,
                                    const Context &context)
{
    init(ccHandle, CSSM_ALGCLASS_SIGNATURE, context, true);
}

void CSPFullPluginSession::SignDataUpdate(CSSM_CC_HANDLE ccHandle,
                    const CssmData dataBufs[],
                    uint32 dataBufCount)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, true)->update(dataBufs, dataBufCount);
}

void CSPFullPluginSession::SignDataFinal(CSSM_CC_HANDLE ccHandle,
                           CssmData &signature)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, true)->final(signature, *this);
}


void CSPFullPluginSession::VerifyData(CSSM_CC_HANDLE ccHandle,
                                  const Context &context,
                                  const CssmData dataBufs[],
                                  uint32 dataBufCount,
                                  CSSM_ALGORITHMS digestAlgorithm,
                                  const CssmData &Signature)
{
	VerifyDataInit(ccHandle, context);
	if(digestAlgorithm != CSSM_ALGID_NONE) {
		getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, 
			false)->setDigestAlgorithm(digestAlgorithm);
	}
	VerifyDataUpdate(ccHandle, dataBufs, dataBufCount);
	VerifyDataFinal(ccHandle, Signature);
}

void CSPFullPluginSession::VerifyDataInit(CSSM_CC_HANDLE ccHandle, const Context &context)
{
    init(ccHandle, CSSM_ALGCLASS_SIGNATURE, context, false);
}

void CSPFullPluginSession::VerifyDataUpdate(CSSM_CC_HANDLE ccHandle,
                                        const CssmData dataBufs[],
                                        uint32 dataBufCount)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, false)->update(dataBufs, dataBufCount);
}

void CSPFullPluginSession::VerifyDataFinal(CSSM_CC_HANDLE ccHandle,
                     const CssmData &signature)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_SIGNATURE, false)->final(signature);
}


//
// Digesting
//
void CSPFullPluginSession::DigestData(CSSM_CC_HANDLE ccHandle,
               const Context &context,
               const CssmData dataBufs[],
               uint32 DataBufCount,
               CssmData &Digest)
{
    DigestDataInit(ccHandle, context);
    DigestDataUpdate(ccHandle, dataBufs, DataBufCount);
    DigestDataFinal(ccHandle, Digest);
}

void CSPFullPluginSession::DigestDataInit(CSSM_CC_HANDLE ccHandle, const Context &context)
{
    init(ccHandle, CSSM_ALGCLASS_DIGEST, context);
}

void CSPFullPluginSession::DigestDataUpdate(CSSM_CC_HANDLE ccHandle,
                              const CssmData dataBufs[],
                              uint32 dataBufCount)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_DIGEST)->update(dataBufs, dataBufCount);
}

void CSPFullPluginSession::DigestDataFinal(CSSM_CC_HANDLE ccHandle,
                                           CssmData &digest)
{
    getStagedContext(ccHandle, CSSM_ALGCLASS_DIGEST)->final(digest, *this);
}

void CSPFullPluginSession::DigestDataClone(CSSM_CC_HANDLE ccHandle,
                                           CSSM_CC_HANDLE clonedCCHandle)
{
    CSPContext *cloned = getStagedContext(ccHandle, CSSM_ALGCLASS_DIGEST)->clone(*this);
    cloned->mDirection = true;
    cloned->mType = CSSM_ALGCLASS_DIGEST;
    setContext(clonedCCHandle, cloned);
}


//
// Key generation, Derivation, and inquiry
//
void CSPFullPluginSession::GenerateKey(CSSM_CC_HANDLE ccHandle,
                         const Context &context,
                         uint32 keyUsage,
                         uint32 keyAttr,
                         const CssmData *keyLabel,
                         const CSSM_RESOURCE_CONTROL_CONTEXT *credAndAclEntry,
                         CssmKey &key,
                         CSSM_PRIVILEGE privilege)
{
    CSPContext *alg = init(ccHandle, CSSM_ALGCLASS_KEYGEN, context);
    setKey(key, context, CSSM_KEYCLASS_SESSION_KEY, keyAttr, keyUsage);
    CssmKey blank;		// dummy 2nd key (not used)
    alg->generate(context, key, blank);
}

void CSPFullPluginSession::GenerateKeyPair(CSSM_CC_HANDLE ccHandle,
                             const Context &context,
                             uint32 publicKeyUsage,
                             uint32 publicKeyAttr,
                             const CssmData *publicKeyLabel,
                             CssmKey &publicKey,
                             uint32 privateKeyUsage,
                             uint32 privateKeyAttr,
                             const CssmData *privateKeyLabel,
                             const CSSM_RESOURCE_CONTROL_CONTEXT *credAndAclEntry,
                             CssmKey &privateKey,
                             CSSM_PRIVILEGE privilege)
{
    CSPContext *alg = init(ccHandle, CSSM_ALGCLASS_KEYGEN, context);
    
    setKey(publicKey, context, CSSM_KEYCLASS_PUBLIC_KEY, publicKeyAttr, publicKeyUsage);
    setKey(privateKey, context, CSSM_KEYCLASS_PRIVATE_KEY, privateKeyAttr, privateKeyUsage);
    alg->generate(context, publicKey, privateKey);
    //@@@ handle labels
    //@@@ handle reference keys
}

void CSPFullPluginSession::ObtainPrivateKeyFromPublicKey(const CssmKey &PublicKey,
														 CssmKey &PrivateKey)
{
	unimplemented();
}

void CSPFullPluginSession::QueryKeySizeInBits(CSSM_CC_HANDLE ccHandle,
						const Context *context,
						const CssmKey *key,
						CSSM_KEY_SIZE &keySize)
{
	if (context) {
		getKeySize(context->get<CssmKey>(CSSM_ATTRIBUTE_KEY, CSSMERR_CSP_MISSING_ATTR_KEY),
			keySize);
	} else {
		getKeySize(CssmKey::required(key), keySize);
	}
}


//
// Free a key object.
//
void CSPFullPluginSession::FreeKey(const AccessCredentials *AccessCred,
								   CssmKey &key,
								   CSSM_BOOL Delete)
{
	free(key.data());
}


//
// Random number and parameter generation
//
void CSPFullPluginSession::GenerateRandom(CSSM_CC_HANDLE ccHandle,
                                          const Context &context,
                                          CssmData &randomNumber)
{
    init(ccHandle, CSSM_ALGCLASS_RANDOMGEN, context)->final(randomNumber, *this);
}

void CSPFullPluginSession::GenerateAlgorithmParams(CSSM_CC_HANDLE ccHandle,
                                    const Context &context,
                                    uint32 paramBits,
                                    CssmData &param,
                                    uint32 &attrCount,
                                    CSSM_CONTEXT_ATTRIBUTE_PTR &attrs)
{
    Context::Attr *attrList;
    init(ccHandle, CSSM_ALGCLASS_NONE, context)->generate(context, paramBits,
                                                          param, attrCount, attrList);
    attrs = attrList;
}


//
// Login/Logout and token operational maintainance.
// These mean little without support by the actual implementation, but we can help...
// @@@ Should this be in CSP[non-Full]PluginSession?
//
void CSPFullPluginSession::Login(const AccessCredentials &AccessCred,
			const CssmData *LoginName,
			const void *Reserved)
{
	if (Reserved != NULL)
		CssmError::throwMe(CSSM_ERRCODE_INVALID_POINTER);
		
	// default implementation refuses to log in
	//@@@ should hand it to implementation virtual defaulting to this
	CssmError::throwMe(CSSMERR_CSP_INVALID_LOGIN_NAME);
}

void CSPFullPluginSession::Logout()
{
	if (!loggedIn(false))
		CssmError::throwMe(CSSMERR_CSP_NOT_LOGGED_IN);
}

void CSPFullPluginSession::VerifyDevice(const CssmData &DeviceCert)
{
	CssmError::throwMe(CSSMERR_CSP_DEVICE_VERIFY_FAILED);
}

void CSPFullPluginSession::GetOperationalStatistics(CSPOperationalStatistics &statistics)
{
	memset(&statistics, 0, sizeof(statistics));
	statistics.UserAuthenticated = loggedIn();
	//@@@ collect device flags - capability matrix setup?
	//@@@ collect token limitation parameters (static) - capability matrix setup?
	//@@@ collect token statistics (dynamic) - dynamic accounting call-downs?
}


//
// Utterly miscellaneous, rarely used, strange functions
//
void CSPFullPluginSession::RetrieveCounter(CssmData &Counter)
{
	unimplemented();
}

void CSPFullPluginSession::RetrieveUniqueId(CssmData &UniqueID)
{
	unimplemented();
}

void CSPFullPluginSession::GetTimeValue(CSSM_ALGORITHMS TimeAlgorithm, CssmData &TimeData)
{
	unimplemented();
}


//
// ACL retrieval and change operations
//
void CSPFullPluginSession::GetKeyOwner(const CssmKey &Key,
		CSSM_ACL_OWNER_PROTOTYPE &Owner)
{
	unimplemented();
}

void CSPFullPluginSession::ChangeKeyOwner(const AccessCredentials &AccessCred,
		const CssmKey &Key,
		const CSSM_ACL_OWNER_PROTOTYPE &NewOwner)
{
	unimplemented();
}

void CSPFullPluginSession::GetKeyAcl(const CssmKey &Key,
		const CSSM_STRING *SelectionTag,
		uint32 &NumberOfAclInfos,
		CSSM_ACL_ENTRY_INFO_PTR &AclInfos)
{
	unimplemented();
}

void CSPFullPluginSession::ChangeKeyAcl(const AccessCredentials &AccessCred,
		const CSSM_ACL_EDIT &AclEdit,
		const CssmKey &Key)
{
	unimplemented();
}

void CSPFullPluginSession::GetLoginOwner(CSSM_ACL_OWNER_PROTOTYPE &Owner)
{
	unimplemented();
}

void CSPFullPluginSession::ChangeLoginOwner(const AccessCredentials &AccessCred,
		const CSSM_ACL_OWNER_PROTOTYPE &NewOwner)
{
	unimplemented();
}

void CSPFullPluginSession::GetLoginAcl(const CSSM_STRING *SelectionTag,
		uint32 &NumberOfAclInfos,
		CSSM_ACL_ENTRY_INFO_PTR &AclInfos)
{
	unimplemented();
}

void CSPFullPluginSession::ChangeLoginAcl(const AccessCredentials &AccessCred,
		const CSSM_ACL_EDIT &AclEdit)
{
	unimplemented();
}



//
// Passthroughs (by default, unimplemented)
//
void CSPFullPluginSession::PassThrough(CSSM_CC_HANDLE CCHandle,
										const Context &Context,
										uint32 PassThroughId,
										const void *InData,
										void **OutData)
{
	unimplemented();
}


//
// KeyPool -- ReferencedKey management functionality
//
KeyPool::KeyPool()
{
}

KeyPool::~KeyPool()
{
	StLock<Mutex> _(mKeyMapLock);
	// Delete every ReferencedKey in the pool, but be careful to deactivate them first
	// to keep them from calling erase (which would cause deadlock since we already hold mKeyMapLock).
	KeyMap::iterator end = mKeyMap.end();
	for (KeyMap::iterator it = mKeyMap.begin(); it != end; ++it)
	{
		try
		{
			it->second->deactivate();
		}
		catch(...) {}
		delete it->second;
	}
	mKeyMap.clear();
}

void
KeyPool::add(ReferencedKey &referencedKey)
{
	StLock<Mutex> _(mKeyMapLock);
	IFDEBUG(bool inserted =)
		mKeyMap.insert(KeyMap::value_type(referencedKey.keyReference(), &referencedKey)).second;
	// Since add is only called from the constructor of ReferencedKey we should
	// never add a key that is already in mKeyMap
	assert(inserted);
}

ReferencedKey &
KeyPool::findKey(const CSSM_KEY &key) const
{
	return findKeyReference(ReferencedKey::keyReference(key));
}

ReferencedKey &
KeyPool::findKeyReference(ReferencedKey::KeyReference keyReference) const
{
	StLock<Mutex> _(mKeyMapLock);
	KeyMap::const_iterator it = mKeyMap.find(keyReference);
	if (it == mKeyMap.end())
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);

	return *it->second;
}

void
KeyPool::erase(ReferencedKey &referencedKey)
{
	erase(referencedKey.keyReference());
}

ReferencedKey &
KeyPool::erase(ReferencedKey::KeyReference keyReference)
{
	StLock<Mutex> _(mKeyMapLock);
	KeyMap::iterator it = mKeyMap.find(keyReference);
	if (it == mKeyMap.end())
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);

	mKeyMap.erase(it);
	ReferencedKey &referencedKey = *it->second;
	return referencedKey;
}

// Erase keyReference from mKeyMap, free the ioKey, and delete the ReferencedKey
void
KeyPool::freeKey(Allocator &allocator, CSSM_KEY &ioKey)
{
	delete &erase(ReferencedKey::freeReferenceKey(allocator, ioKey));
}

//
// ReferencedKey class
//
ReferencedKey::ReferencedKey(KeyPool &keyPool) : mKeyPool(&keyPool)
{
	mKeyPool->add(*this);
}

ReferencedKey::~ReferencedKey()
{
	if (isActive())
		mKeyPool->erase(*this);
}

ReferencedKey::KeyReference
ReferencedKey::keyReference()
{
	// @@@ Possibly check isActive() and return an invalid reference if it is not set.
	return reinterpret_cast<ReferencedKey::KeyReference>(this);
}

//
// Making, retrieving and freeing Key references of CssmKeys
//
void
ReferencedKey::makeReferenceKey(Allocator &allocator, KeyReference keyReference, CSSM_KEY &key)
{
	key.KeyHeader.BlobType = CSSM_KEYBLOB_REFERENCE;
	key.KeyHeader.Format = CSSM_KEYBLOB_REF_FORMAT_INTEGER;
	key.KeyData.Length = sizeof(KeyReference);
	key.KeyData.Data = allocator.alloc<uint8>(sizeof(KeyReference));
	uint8 *cp = key.KeyData.Data;
	for (int i = sizeof(KeyReference); --i >= 0;)
	{
		cp[i] = keyReference & 0xff;
		keyReference = keyReference >> 8;
	}
}

ReferencedKey::KeyReference
ReferencedKey::keyReference(const CSSM_KEY &key)
{
	if (key.KeyHeader.BlobType != CSSM_KEYBLOB_REFERENCE
		|| key.KeyHeader.Format != CSSM_KEYBLOB_REF_FORMAT_INTEGER
		|| key.KeyData.Length != sizeof(KeyReference)
		|| key.KeyData.Data == NULL)
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE);

	const uint8 *cp = key.KeyData.Data;
	KeyReference keyReference = 0;
	for (uint32 i = 0; i < sizeof(KeyReference); ++i)
		keyReference = (keyReference << 8) + cp[i];

	return keyReference;
}

ReferencedKey::KeyReference
ReferencedKey::freeReferenceKey(Allocator &allocator, CSSM_KEY &key)
{
	KeyReference aKeyReference = keyReference(key);
	allocator.free(key.KeyData.Data);
	key.KeyData.Data = NULL;
	key.KeyData.Length = 0;
	return aKeyReference;
}
