/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * Internal PKCS #11 functions. Should only be called by pkcs11.c
 */
#include "pkcs11.h"
#include "pkcs11i.h"
#include "lowkeyi.h"
#include "secasn1.h"
#include "blapi.h"
#include "secerr.h"
#include "prnetdb.h" /* for PR_ntohl */
#include "sftkdb.h"
#include "softoken.h"

/*
 * ******************** Attribute Utilities *******************************
 */

/*
 * create a new attribute with type, value, and length. Space is allocated
 * to hold value.
 */
static SFTKAttribute *
sftk_NewAttribute(SFTKObject *object,
                  CK_ATTRIBUTE_TYPE type, const void *value, CK_ULONG len)
{
    SFTKAttribute *attribute;

    SFTKSessionObject *so = sftk_narrowToSessionObject(object);
    int index;

    if (so == NULL) {
        /* allocate new attribute in a buffer */
        PORT_Assert(0);
        return NULL;
    }
    /*
     * We attempt to keep down contention on Malloc and Arena locks by
     * limiting the number of these calls on high traversed paths. This
     * is done for attributes by 'allocating' them from a pool already
     * allocated by the parent object.
     */
    PZ_Lock(so->attributeLock);
    index = so->nextAttr++;
    PZ_Unlock(so->attributeLock);
    PORT_Assert(index < MAX_OBJS_ATTRS);
    if (index >= MAX_OBJS_ATTRS)
        return NULL;

    attribute = &so->attrList[index];
    attribute->attrib.type = type;
    attribute->freeAttr = PR_FALSE;
    attribute->freeData = PR_FALSE;
    if (value) {
        if (len <= ATTR_SPACE) {
            attribute->attrib.pValue = attribute->space;
        } else {
            attribute->attrib.pValue = PORT_Alloc(len);
            attribute->freeData = PR_TRUE;
        }
        if (attribute->attrib.pValue == NULL) {
            return NULL;
        }
        PORT_Memcpy(attribute->attrib.pValue, value, len);
        attribute->attrib.ulValueLen = len;
    } else {
        attribute->attrib.pValue = NULL;
        attribute->attrib.ulValueLen = 0;
    }
    attribute->attrib.type = type;
    attribute->handle = type;
    attribute->next = attribute->prev = NULL;
    return attribute;
}

/*
 * Free up all the memory associated with an attribute. Reference count
 * must be zero to call this.
 */
static void
sftk_DestroyAttribute(SFTKAttribute *attribute)
{
    if (attribute->freeData) {
        if (attribute->attrib.pValue) {
            /* clear out the data in the attribute value... it may have been
             * sensitive data */
            PORT_Memset(attribute->attrib.pValue, 0,
                        attribute->attrib.ulValueLen);
        }
        PORT_Free(attribute->attrib.pValue);
    }
    PORT_Free(attribute);
}

/*
 * release a reference to an attribute structure
 */
void
sftk_FreeAttribute(SFTKAttribute *attribute)
{
    if (attribute->freeAttr) {
        sftk_DestroyAttribute(attribute);
        return;
    }
}

static SFTKAttribute *
sftk_FindTokenAttribute(SFTKTokenObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *myattribute = NULL;
    SFTKDBHandle *dbHandle = NULL;
    CK_RV crv = CKR_HOST_MEMORY;

    myattribute = (SFTKAttribute *)PORT_Alloc(sizeof(SFTKAttribute));
    if (myattribute == NULL) {
        goto loser;
    }

    dbHandle = sftk_getDBForTokenObject(object->obj.slot, object->obj.handle);

    myattribute->handle = type;
    myattribute->attrib.type = type;
    myattribute->attrib.pValue = myattribute->space;
    myattribute->attrib.ulValueLen = ATTR_SPACE;
    myattribute->next = myattribute->prev = NULL;
    myattribute->freeAttr = PR_TRUE;
    myattribute->freeData = PR_FALSE;

    crv = sftkdb_GetAttributeValue(dbHandle, object->obj.handle,
                                   &myattribute->attrib, 1);

    /* attribute is bigger than our attribute space buffer, malloc it */
    if (crv == CKR_BUFFER_TOO_SMALL) {
        myattribute->attrib.pValue = NULL;
        crv = sftkdb_GetAttributeValue(dbHandle, object->obj.handle,
                                       &myattribute->attrib, 1);
        if (crv != CKR_OK) {
            goto loser;
        }
        myattribute->attrib.pValue = PORT_Alloc(myattribute->attrib.ulValueLen);
        if (myattribute->attrib.pValue == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
        myattribute->freeData = PR_TRUE;
        crv = sftkdb_GetAttributeValue(dbHandle, object->obj.handle,
                                       &myattribute->attrib, 1);
    }
loser:
    if (dbHandle) {
        sftk_freeDB(dbHandle);
    }
    if (crv != CKR_OK) {
        if (myattribute) {
            myattribute->attrib.ulValueLen = 0;
            sftk_FreeAttribute(myattribute);
            myattribute = NULL;
        }
    }
    return myattribute;
}

/*
 * look up and attribute structure from a type and Object structure.
 * The returned attribute is referenced and needs to be freed when
 * it is no longer needed.
 */
SFTKAttribute *
sftk_FindAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;
    SFTKSessionObject *sessObject = sftk_narrowToSessionObject(object);

    if (sessObject == NULL) {
        return sftk_FindTokenAttribute(sftk_narrowToTokenObject(object), type);
    }

    PZ_Lock(sessObject->attributeLock);
    sftkqueue_find(attribute, type, sessObject->head, sessObject->hashSize);
    PZ_Unlock(sessObject->attributeLock);

    return (attribute);
}

/*
 * Take a buffer and it's length and return it's true size in bits;
 */
unsigned int
sftk_GetLengthInBits(unsigned char *buf, unsigned int bufLen)
{
    unsigned int size = bufLen * 8;
    unsigned int i;

    /* Get the real length in bytes */
    for (i = 0; i < bufLen; i++) {
        unsigned char c = *buf++;
        if (c != 0) {
            unsigned char m;
            for (m = 0x80; m > 0; m = m >> 1) {
                if ((c & m) != 0) {
                    break;
                }
                size--;
            }
            break;
        }
        size -= 8;
    }
    return size;
}

/*
 * Constrain a big num attribute. to size and padding
 * minLength means length of the object must be greater than equal to minLength
 * maxLength means length of the object must be less than equal to maxLength
 * minMultiple means that object length mod minMultiple must equal 0.
 * all input sizes are in bits.
 * if any constraint is '0' that constraint is not checked.
 */
CK_RV
sftk_ConstrainAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type,
                        int minLength, int maxLength, int minMultiple)
{
    SFTKAttribute *attribute;
    int size;
    unsigned char *ptr;

    attribute = sftk_FindAttribute(object, type);
    if (!attribute) {
        return CKR_TEMPLATE_INCOMPLETE;
    }
    ptr = (unsigned char *)attribute->attrib.pValue;
    if (ptr == NULL) {
        sftk_FreeAttribute(attribute);
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }
    size = sftk_GetLengthInBits(ptr, attribute->attrib.ulValueLen);
    sftk_FreeAttribute(attribute);

    if ((minLength != 0) && (size < minLength)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }
    if ((maxLength != 0) && (size > maxLength)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }
    if ((minMultiple != 0) && ((size % minMultiple) != 0)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }
    return CKR_OK;
}

PRBool
sftk_hasAttributeToken(SFTKTokenObject *object, CK_ATTRIBUTE_TYPE type)
{
    CK_ATTRIBUTE template;
    CK_RV crv;
    SFTKDBHandle *dbHandle;

    dbHandle = sftk_getDBForTokenObject(object->obj.slot, object->obj.handle);
    template.type = type;
    template.pValue = NULL;
    template.ulValueLen = 0;

    crv = sftkdb_GetAttributeValue(dbHandle, object->obj.handle, &template, 1);
    sftk_freeDB(dbHandle);

    /* attribute is bigger than our attribute space buffer, malloc it */
    return (crv == CKR_OK) ? PR_TRUE : PR_FALSE;
}

/*
 * return true if object has attribute
 */
PRBool
sftk_hasAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;
    SFTKSessionObject *sessObject = sftk_narrowToSessionObject(object);

    if (sessObject == NULL) {
        return sftk_hasAttributeToken(sftk_narrowToTokenObject(object), type);
    }

    PZ_Lock(sessObject->attributeLock);
    sftkqueue_find(attribute, type, sessObject->head, sessObject->hashSize);
    PZ_Unlock(sessObject->attributeLock);

    return (PRBool)(attribute != NULL);
}

/*
 * add an attribute to an object
 */
static void
sftk_AddAttribute(SFTKObject *object, SFTKAttribute *attribute)
{
    SFTKSessionObject *sessObject = sftk_narrowToSessionObject(object);

    if (sessObject == NULL)
        return;
    PZ_Lock(sessObject->attributeLock);
    sftkqueue_add(attribute, attribute->handle,
                  sessObject->head, sessObject->hashSize);
    PZ_Unlock(sessObject->attributeLock);
}

/*
 * copy an unsigned attribute into a SECItem. Secitem is allocated in
 * the specified arena.
 */
CK_RV
sftk_Attribute2SSecItem(PLArenaPool *arena, SECItem *item, SFTKObject *object,
                        CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;

    item->data = NULL;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return CKR_TEMPLATE_INCOMPLETE;

    (void)SECITEM_AllocItem(arena, item, attribute->attrib.ulValueLen);
    if (item->data == NULL) {
        sftk_FreeAttribute(attribute);
        return CKR_HOST_MEMORY;
    }
    PORT_Memcpy(item->data, attribute->attrib.pValue, item->len);
    sftk_FreeAttribute(attribute);
    return CKR_OK;
}

/*
 * fetch multiple attributes into  SECItems. Secitem data is allocated in
 * the specified arena.
 */
CK_RV
sftk_MultipleAttribute2SecItem(PLArenaPool *arena, SFTKObject *object,
                               SFTKItemTemplate *itemTemplate, int itemTemplateCount)
{

    CK_RV crv = CKR_OK;
    CK_ATTRIBUTE templateSpace[SFTK_MAX_ITEM_TEMPLATE];
    CK_ATTRIBUTE *template;
    SFTKTokenObject *tokObject;
    SFTKDBHandle *dbHandle = NULL;
    int i;

    tokObject = sftk_narrowToTokenObject(object);

    /* session objects, just loop through the list */
    if (tokObject == NULL) {
        for (i = 0; i < itemTemplateCount; i++) {
            crv = sftk_Attribute2SecItem(arena, itemTemplate[i].item, object,
                                         itemTemplate[i].type);
            if (crv != CKR_OK) {
                return crv;
            }
        }
        return CKR_OK;
    }

    /* don't do any work if none is required */
    if (itemTemplateCount == 0) {
        return CKR_OK;
    }

    /* don't allocate the template unless we need it */
    if (itemTemplateCount > SFTK_MAX_ITEM_TEMPLATE) {
        template = PORT_NewArray(CK_ATTRIBUTE, itemTemplateCount);
    } else {
        template = templateSpace;
    }

    if (template == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    dbHandle = sftk_getDBForTokenObject(object->slot, object->handle);
    if (dbHandle == NULL) {
        crv = CKR_OBJECT_HANDLE_INVALID;
        goto loser;
    }

    /* set up the PKCS #11 template */
    for (i = 0; i < itemTemplateCount; i++) {
        template[i].type = itemTemplate[i].type;
        template[i].pValue = NULL;
        template[i].ulValueLen = 0;
    }

    /* fetch the attribute lengths */
    crv = sftkdb_GetAttributeValue(dbHandle, object->handle,
                                   template, itemTemplateCount);
    if (crv != CKR_OK) {
        goto loser;
    }

    /* allocate space for the attributes */
    for (i = 0; i < itemTemplateCount; i++) {
        template[i].pValue = PORT_ArenaAlloc(arena, template[i].ulValueLen);
        if (template[i].pValue == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
    }

    /* fetch the attributes */
    crv = sftkdb_GetAttributeValue(dbHandle, object->handle,
                                   template, itemTemplateCount);
    if (crv != CKR_OK) {
        goto loser;
    }

    /* Fill in the items */
    for (i = 0; i < itemTemplateCount; i++) {
        itemTemplate[i].item->data = template[i].pValue;
        itemTemplate[i].item->len = template[i].ulValueLen;
    }

loser:
    if (template != templateSpace) {
        PORT_Free(template);
    }
    if (dbHandle) {
        sftk_freeDB(dbHandle);
    }

    return crv;
}

/*
 * delete an attribute from an object
 */
static void
sftk_DeleteAttribute(SFTKObject *object, SFTKAttribute *attribute)
{
    SFTKSessionObject *sessObject = sftk_narrowToSessionObject(object);

    if (sessObject == NULL) {
        return;
    }
    PZ_Lock(sessObject->attributeLock);
    if (sftkqueue_is_queued(attribute, attribute->handle,
                            sessObject->head, sessObject->hashSize)) {
        sftkqueue_delete(attribute, attribute->handle,
                         sessObject->head, sessObject->hashSize);
    }
    PZ_Unlock(sessObject->attributeLock);
}

/*
 * this is only valid for CK_BBOOL type attributes. Return the state
 * of that attribute.
 */
PRBool
sftk_isTrue(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;
    PRBool tok = PR_FALSE;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL) {
        return PR_FALSE;
    }
    tok = (PRBool)(*(CK_BBOOL *)attribute->attrib.pValue);
    sftk_FreeAttribute(attribute);

    return tok;
}

/*
 * force an attribute to null.
 * this is for sensitive keys which are stored in the database, we don't
 * want to keep this info around in memory in the clear.
 */
void
sftk_nullAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return;

    if (attribute->attrib.pValue != NULL) {
        PORT_Memset(attribute->attrib.pValue, 0, attribute->attrib.ulValueLen);
        if (attribute->freeData) {
            PORT_Free(attribute->attrib.pValue);
        }
        attribute->freeData = PR_FALSE;
        attribute->attrib.pValue = NULL;
        attribute->attrib.ulValueLen = 0;
    }
    sftk_FreeAttribute(attribute);
}

static CK_RV
sftk_forceTokenAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type,
                         const void *value, unsigned int len)
{
    CK_ATTRIBUTE attribute;
    SFTKDBHandle *dbHandle = NULL;
    SFTKTokenObject *to = sftk_narrowToTokenObject(object);
    CK_RV crv;

    PORT_Assert(to);
    if (to == NULL) {
        return CKR_DEVICE_ERROR;
    }

    dbHandle = sftk_getDBForTokenObject(object->slot, object->handle);

    attribute.type = type;
    attribute.pValue = (void *)value;
    attribute.ulValueLen = len;

    crv = sftkdb_SetAttributeValue(dbHandle, object, &attribute, 1);
    sftk_freeDB(dbHandle);
    return crv;
}

/*
 * force an attribute to a specifc value.
 */
CK_RV
sftk_forceAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type,
                    const void *value, unsigned int len)
{
    SFTKAttribute *attribute;
    void *att_val = NULL;
    PRBool freeData = PR_FALSE;

    PORT_Assert(object);
    PORT_Assert(object->refCount);
    PORT_Assert(object->slot);
    if (!object ||
        !object->refCount ||
        !object->slot) {
        return CKR_DEVICE_ERROR;
    }
    if (sftk_isToken(object->handle)) {
        return sftk_forceTokenAttribute(object, type, value, len);
    }
    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return sftk_AddAttributeType(object, type, value, len);

    if (value) {
        if (len <= ATTR_SPACE) {
            att_val = attribute->space;
        } else {
            att_val = PORT_Alloc(len);
            freeData = PR_TRUE;
        }
        if (att_val == NULL) {
            return CKR_HOST_MEMORY;
        }
        if (attribute->attrib.pValue == att_val) {
            PORT_Memset(attribute->attrib.pValue, 0,
                        attribute->attrib.ulValueLen);
        }
        PORT_Memcpy(att_val, value, len);
    }
    if (attribute->attrib.pValue != NULL) {
        if (attribute->attrib.pValue != att_val) {
            PORT_Memset(attribute->attrib.pValue, 0,
                        attribute->attrib.ulValueLen);
        }
        if (attribute->freeData) {
            PORT_Free(attribute->attrib.pValue);
        }
        attribute->freeData = PR_FALSE;
        attribute->attrib.pValue = NULL;
        attribute->attrib.ulValueLen = 0;
    }
    if (att_val) {
        attribute->attrib.pValue = att_val;
        attribute->attrib.ulValueLen = len;
        attribute->freeData = freeData;
    }
    sftk_FreeAttribute(attribute);
    return CKR_OK;
}

/*
 * return a null terminated string from attribute 'type'. This string
 * is allocated and needs to be freed with PORT_Free() When complete.
 */
char *
sftk_getString(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;
    char *label = NULL;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return NULL;

    if (attribute->attrib.pValue != NULL) {
        label = (char *)PORT_Alloc(attribute->attrib.ulValueLen + 1);
        if (label == NULL) {
            sftk_FreeAttribute(attribute);
            return NULL;
        }

        PORT_Memcpy(label, attribute->attrib.pValue,
                    attribute->attrib.ulValueLen);
        label[attribute->attrib.ulValueLen] = 0;
    }
    sftk_FreeAttribute(attribute);
    return label;
}

/*
 * decode when a particular attribute may be modified
 *  SFTK_NEVER: This attribute must be set at object creation time and
 *  can never be modified.
 *  SFTK_ONCOPY: This attribute may be modified only when you copy the
 *  object.
 *  SFTK_SENSITIVE: The CKA_SENSITIVE attribute can only be changed from
 *  CK_FALSE to CK_TRUE.
 *  SFTK_ALWAYS: This attribute can always be modified.
 * Some attributes vary their modification type based on the class of the
 *   object.
 */
SFTKModifyType
sftk_modifyType(CK_ATTRIBUTE_TYPE type, CK_OBJECT_CLASS inClass)
{
    /* if we don't know about it, user user defined, always allow modify */
    SFTKModifyType mtype = SFTK_ALWAYS;

    switch (type) {
        /* NEVER */
        case CKA_CLASS:
        case CKA_CERTIFICATE_TYPE:
        case CKA_KEY_TYPE:
        case CKA_MODULUS:
        case CKA_MODULUS_BITS:
        case CKA_PUBLIC_EXPONENT:
        case CKA_PRIVATE_EXPONENT:
        case CKA_PRIME:
        case CKA_SUBPRIME:
        case CKA_BASE:
        case CKA_PRIME_1:
        case CKA_PRIME_2:
        case CKA_EXPONENT_1:
        case CKA_EXPONENT_2:
        case CKA_COEFFICIENT:
        case CKA_VALUE_LEN:
        case CKA_ALWAYS_SENSITIVE:
        case CKA_NEVER_EXTRACTABLE:
        case CKA_NETSCAPE_DB:
            mtype = SFTK_NEVER;
            break;

        /* ONCOPY */
        case CKA_TOKEN:
        case CKA_PRIVATE:
        case CKA_MODIFIABLE:
            mtype = SFTK_ONCOPY;
            break;

        /* SENSITIVE */
        case CKA_SENSITIVE:
        case CKA_EXTRACTABLE:
            mtype = SFTK_SENSITIVE;
            break;

        /* ALWAYS */
        case CKA_LABEL:
        case CKA_APPLICATION:
        case CKA_ID:
        case CKA_SERIAL_NUMBER:
        case CKA_START_DATE:
        case CKA_END_DATE:
        case CKA_DERIVE:
        case CKA_ENCRYPT:
        case CKA_DECRYPT:
        case CKA_SIGN:
        case CKA_VERIFY:
        case CKA_SIGN_RECOVER:
        case CKA_VERIFY_RECOVER:
        case CKA_WRAP:
        case CKA_UNWRAP:
            mtype = SFTK_ALWAYS;
            break;

        /* DEPENDS ON CLASS */
        case CKA_VALUE:
            mtype = (inClass == CKO_DATA) ? SFTK_ALWAYS : SFTK_NEVER;
            break;

        case CKA_SUBJECT:
            mtype = (inClass == CKO_CERTIFICATE) ? SFTK_NEVER : SFTK_ALWAYS;
            break;
        default:
            break;
    }
    return mtype;
}

/* decode if a particular attribute is sensitive (cannot be read
 * back to the user of if the object is set to SENSITIVE) */
PRBool
sftk_isSensitive(CK_ATTRIBUTE_TYPE type, CK_OBJECT_CLASS inClass)
{
    switch (type) {
        /* ALWAYS */
        case CKA_PRIVATE_EXPONENT:
        case CKA_PRIME_1:
        case CKA_PRIME_2:
        case CKA_EXPONENT_1:
        case CKA_EXPONENT_2:
        case CKA_COEFFICIENT:
            return PR_TRUE;

        /* DEPENDS ON CLASS */
        case CKA_VALUE:
            /* PRIVATE and SECRET KEYS have SENSITIVE values */
            return (PRBool)((inClass == CKO_PRIVATE_KEY) || (inClass == CKO_SECRET_KEY));

        default:
            break;
    }
    return PR_FALSE;
}

/*
 * copy an attribute into a SECItem. Secitem is allocated in the specified
 * arena.
 */
CK_RV
sftk_Attribute2SecItem(PLArenaPool *arena, SECItem *item, SFTKObject *object,
                       CK_ATTRIBUTE_TYPE type)
{
    int len;
    SFTKAttribute *attribute;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return CKR_TEMPLATE_INCOMPLETE;
    len = attribute->attrib.ulValueLen;

    if (arena) {
        item->data = (unsigned char *)PORT_ArenaAlloc(arena, len);
    } else {
        item->data = (unsigned char *)PORT_Alloc(len);
    }
    if (item->data == NULL) {
        sftk_FreeAttribute(attribute);
        return CKR_HOST_MEMORY;
    }
    item->len = len;
    PORT_Memcpy(item->data, attribute->attrib.pValue, len);
    sftk_FreeAttribute(attribute);
    return CKR_OK;
}

CK_RV
sftk_GetULongAttribute(SFTKObject *object, CK_ATTRIBUTE_TYPE type,
                       CK_ULONG *longData)
{
    SFTKAttribute *attribute;

    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return CKR_TEMPLATE_INCOMPLETE;

    if (attribute->attrib.ulValueLen != sizeof(CK_ULONG)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    *longData = *(CK_ULONG *)attribute->attrib.pValue;
    sftk_FreeAttribute(attribute);
    return CKR_OK;
}

void
sftk_DeleteAttributeType(SFTKObject *object, CK_ATTRIBUTE_TYPE type)
{
    SFTKAttribute *attribute;
    attribute = sftk_FindAttribute(object, type);
    if (attribute == NULL)
        return;
    sftk_DeleteAttribute(object, attribute);
    sftk_FreeAttribute(attribute);
}

CK_RV
sftk_AddAttributeType(SFTKObject *object, CK_ATTRIBUTE_TYPE type,
                      const void *valPtr, CK_ULONG length)
{
    SFTKAttribute *attribute;
    attribute = sftk_NewAttribute(object, type, valPtr, length);
    if (attribute == NULL) {
        return CKR_HOST_MEMORY;
    }
    sftk_AddAttribute(object, attribute);
    return CKR_OK;
}

/*
 * ******************** Object Utilities *******************************
 */

/* must be called holding sftk_tokenKeyLock(slot) */
static SECItem *
sftk_lookupTokenKeyByHandle(SFTKSlot *slot, CK_OBJECT_HANDLE handle)
{
    return (SECItem *)PL_HashTableLookup(slot->tokObjHashTable, (void *)handle);
}

/*
 * use the refLock. This operations should be very rare, so the added
 * contention on the ref lock should be lower than the overhead of adding
 * a new lock. We use separate functions for this just in case I'm wrong.
 */
static void
sftk_tokenKeyLock(SFTKSlot *slot)
{
    SKIP_AFTER_FORK(PZ_Lock(slot->objectLock));
}

static void
sftk_tokenKeyUnlock(SFTKSlot *slot)
{
    SKIP_AFTER_FORK(PZ_Unlock(slot->objectLock));
}

static PRIntn
sftk_freeHashItem(PLHashEntry *entry, PRIntn index, void *arg)
{
    SECItem *item = (SECItem *)entry->value;

    SECITEM_FreeItem(item, PR_TRUE);
    return HT_ENUMERATE_NEXT;
}

CK_RV
SFTK_ClearTokenKeyHashTable(SFTKSlot *slot)
{
    sftk_tokenKeyLock(slot);
    PORT_Assert(!slot->present);
    PL_HashTableEnumerateEntries(slot->tokObjHashTable, sftk_freeHashItem, NULL);
    sftk_tokenKeyUnlock(slot);
    return CKR_OK;
}

/* allocation hooks that allow us to recycle old object structures */
static SFTKObjectFreeList sessionObjectList = { NULL, NULL, 0 };
static SFTKObjectFreeList tokenObjectList = { NULL, NULL, 0 };

SFTKObject *
sftk_GetObjectFromList(PRBool *hasLocks, PRBool optimizeSpace,
                       SFTKObjectFreeList *list, unsigned int hashSize, PRBool isSessionObject)
{
    SFTKObject *object;
    int size = 0;

    if (!optimizeSpace) {
        PZ_Lock(list->lock);
        object = list->head;
        if (object) {
            list->head = object->next;
            list->count--;
        }
        PZ_Unlock(list->lock);
        if (object) {
            object->next = object->prev = NULL;
            *hasLocks = PR_TRUE;
            return object;
        }
    }
    size = isSessionObject ? sizeof(SFTKSessionObject) + hashSize * sizeof(SFTKAttribute *) : sizeof(SFTKTokenObject);

    object = (SFTKObject *)PORT_ZAlloc(size);
    if (isSessionObject && object) {
        ((SFTKSessionObject *)object)->hashSize = hashSize;
    }
    *hasLocks = PR_FALSE;
    return object;
}

static void
sftk_PutObjectToList(SFTKObject *object, SFTKObjectFreeList *list,
                     PRBool isSessionObject)
{

    /* the code below is equivalent to :
     *     optimizeSpace = isSessionObject ? object->optimizeSpace : PR_FALSE;
     * just faster.
     */
    PRBool optimizeSpace = isSessionObject &&
                           ((SFTKSessionObject *)object)->optimizeSpace;
    if (object->refLock && !optimizeSpace && (list->count < MAX_OBJECT_LIST_SIZE)) {
        PZ_Lock(list->lock);
        object->next = list->head;
        list->head = object;
        list->count++;
        PZ_Unlock(list->lock);
        return;
    }
    if (isSessionObject) {
        SFTKSessionObject *so = (SFTKSessionObject *)object;
        PZ_DestroyLock(so->attributeLock);
        so->attributeLock = NULL;
    }
    if (object->refLock) {
        PZ_DestroyLock(object->refLock);
        object->refLock = NULL;
    }
    PORT_Free(object);
}

static SFTKObject *
sftk_freeObjectData(SFTKObject *object)
{
    SFTKObject *next = object->next;

    PORT_Free(object);
    return next;
}

static void
sftk_InitFreeList(SFTKObjectFreeList *list)
{
    if (!list->lock) {
        list->lock = PZ_NewLock(nssILockObject);
    }
}

void
sftk_InitFreeLists(void)
{
    sftk_InitFreeList(&sessionObjectList);
    sftk_InitFreeList(&tokenObjectList);
}

static void
sftk_CleanupFreeList(SFTKObjectFreeList *list, PRBool isSessionList)
{
    SFTKObject *object;

    if (!list->lock) {
        return;
    }
    SKIP_AFTER_FORK(PZ_Lock(list->lock));
    for (object = list->head; object != NULL;
         object = sftk_freeObjectData(object)) {
        PZ_DestroyLock(object->refLock);
        if (isSessionList) {
            PZ_DestroyLock(((SFTKSessionObject *)object)->attributeLock);
        }
    }
    list->count = 0;
    list->head = NULL;
    SKIP_AFTER_FORK(PZ_Unlock(list->lock));
    SKIP_AFTER_FORK(PZ_DestroyLock(list->lock));
    list->lock = NULL;
}

void
sftk_CleanupFreeLists(void)
{
    sftk_CleanupFreeList(&sessionObjectList, PR_TRUE);
    sftk_CleanupFreeList(&tokenObjectList, PR_FALSE);
}

/*
 * Create a new object
 */
SFTKObject *
sftk_NewObject(SFTKSlot *slot)
{
    SFTKObject *object;
    SFTKSessionObject *sessObject;
    PRBool hasLocks = PR_FALSE;
    unsigned int i;
    unsigned int hashSize = 0;

    hashSize = (slot->optimizeSpace) ? SPACE_ATTRIBUTE_HASH_SIZE : TIME_ATTRIBUTE_HASH_SIZE;

    object = sftk_GetObjectFromList(&hasLocks, slot->optimizeSpace,
                                    &sessionObjectList, hashSize, PR_TRUE);
    if (object == NULL) {
        return NULL;
    }
    sessObject = (SFTKSessionObject *)object;
    sessObject->nextAttr = 0;

    for (i = 0; i < MAX_OBJS_ATTRS; i++) {
        sessObject->attrList[i].attrib.pValue = NULL;
        sessObject->attrList[i].freeData = PR_FALSE;
    }
    sessObject->optimizeSpace = slot->optimizeSpace;

    object->handle = 0;
    object->next = object->prev = NULL;
    object->slot = slot;

    object->refCount = 1;
    sessObject->sessionList.next = NULL;
    sessObject->sessionList.prev = NULL;
    sessObject->sessionList.parent = object;
    sessObject->session = NULL;
    sessObject->wasDerived = PR_FALSE;
    if (!hasLocks)
        object->refLock = PZ_NewLock(nssILockRefLock);
    if (object->refLock == NULL) {
        PORT_Free(object);
        return NULL;
    }
    if (!hasLocks)
        sessObject->attributeLock = PZ_NewLock(nssILockAttribute);
    if (sessObject->attributeLock == NULL) {
        PZ_DestroyLock(object->refLock);
        PORT_Free(object);
        return NULL;
    }
    for (i = 0; i < sessObject->hashSize; i++) {
        sessObject->head[i] = NULL;
    }
    object->objectInfo = NULL;
    object->infoFree = NULL;
    return object;
}

static CK_RV
sftk_DestroySessionObjectData(SFTKSessionObject *so)
{
    int i;

    for (i = 0; i < MAX_OBJS_ATTRS; i++) {
        unsigned char *value = so->attrList[i].attrib.pValue;
        if (value) {
            PORT_Memset(value, 0, so->attrList[i].attrib.ulValueLen);
            if (so->attrList[i].freeData) {
                PORT_Free(value);
            }
            so->attrList[i].attrib.pValue = NULL;
            so->attrList[i].freeData = PR_FALSE;
        }
    }
    /*  PZ_DestroyLock(so->attributeLock);*/
    return CKR_OK;
}

/*
 * free all the data associated with an object. Object reference count must
 * be 'zero'.
 */
static CK_RV
sftk_DestroyObject(SFTKObject *object)
{
    CK_RV crv = CKR_OK;
    SFTKSessionObject *so = sftk_narrowToSessionObject(object);
    SFTKTokenObject *to = sftk_narrowToTokenObject(object);

    PORT_Assert(object->refCount == 0);

    /* delete the database value */
    if (to) {
        if (to->dbKey.data) {
            PORT_Free(to->dbKey.data);
            to->dbKey.data = NULL;
        }
    }
    if (so) {
        sftk_DestroySessionObjectData(so);
    }
    if (object->objectInfo) {
        (*object->infoFree)(object->objectInfo);
        object->objectInfo = NULL;
        object->infoFree = NULL;
    }
    if (so) {
        sftk_PutObjectToList(object, &sessionObjectList, PR_TRUE);
    } else {
        sftk_PutObjectToList(object, &tokenObjectList, PR_FALSE);
    }
    return crv;
}

void
sftk_ReferenceObject(SFTKObject *object)
{
    PZ_Lock(object->refLock);
    object->refCount++;
    PZ_Unlock(object->refLock);
}

static SFTKObject *
sftk_ObjectFromHandleOnSlot(CK_OBJECT_HANDLE handle, SFTKSlot *slot)
{
    SFTKObject *object;
    PRUint32 index = sftk_hash(handle, slot->sessObjHashSize);

    if (sftk_isToken(handle)) {
        return sftk_NewTokenObject(slot, NULL, handle);
    }

    PZ_Lock(slot->objectLock);
    sftkqueue_find2(object, handle, index, slot->sessObjHashTable);
    if (object) {
        sftk_ReferenceObject(object);
    }
    PZ_Unlock(slot->objectLock);

    return (object);
}
/*
 * look up and object structure from a handle. OBJECT_Handles only make
 * sense in terms of a given session.  make a reference to that object
 * structure returned.
 */
SFTKObject *
sftk_ObjectFromHandle(CK_OBJECT_HANDLE handle, SFTKSession *session)
{
    SFTKSlot *slot = sftk_SlotFromSession(session);

    return sftk_ObjectFromHandleOnSlot(handle, slot);
}

/*
 * release a reference to an object handle
 */
SFTKFreeStatus
sftk_FreeObject(SFTKObject *object)
{
    PRBool destroy = PR_FALSE;
    CK_RV crv;

    PZ_Lock(object->refLock);
    if (object->refCount == 1)
        destroy = PR_TRUE;
    object->refCount--;
    PZ_Unlock(object->refLock);

    if (destroy) {
        crv = sftk_DestroyObject(object);
        if (crv != CKR_OK) {
            return SFTK_DestroyFailure;
        }
        return SFTK_Destroyed;
    }
    return SFTK_Busy;
}

/*
 * add an object to a slot and session queue. These two functions
 * adopt the object.
 */
void
sftk_AddSlotObject(SFTKSlot *slot, SFTKObject *object)
{
    PRUint32 index = sftk_hash(object->handle, slot->sessObjHashSize);
    sftkqueue_init_element(object);
    PZ_Lock(slot->objectLock);
    sftkqueue_add2(object, object->handle, index, slot->sessObjHashTable);
    PZ_Unlock(slot->objectLock);
}

void
sftk_AddObject(SFTKSession *session, SFTKObject *object)
{
    SFTKSlot *slot = sftk_SlotFromSession(session);
    SFTKSessionObject *so = sftk_narrowToSessionObject(object);

    if (so) {
        PZ_Lock(session->objectLock);
        sftkqueue_add(&so->sessionList, 0, session->objects, 0);
        so->session = session;
        PZ_Unlock(session->objectLock);
    }
    sftk_AddSlotObject(slot, object);
    sftk_ReferenceObject(object);
}

/*
 * delete an object from a slot and session queue
 */
CK_RV
sftk_DeleteObject(SFTKSession *session, SFTKObject *object)
{
    SFTKSlot *slot = sftk_SlotFromSession(session);
    SFTKSessionObject *so = sftk_narrowToSessionObject(object);
    CK_RV crv = CKR_OK;
    PRUint32 index = sftk_hash(object->handle, slot->sessObjHashSize);

    /* Handle Token case */
    if (so && so->session) {
        SFTKSession *session = so->session;
        PZ_Lock(session->objectLock);
        sftkqueue_delete(&so->sessionList, 0, session->objects, 0);
        PZ_Unlock(session->objectLock);
        PZ_Lock(slot->objectLock);
        sftkqueue_delete2(object, object->handle, index, slot->sessObjHashTable);
        PZ_Unlock(slot->objectLock);
        sftkqueue_clear_deleted_element(object);
        sftk_FreeObject(object); /* free the reference owned by the queue */
    } else {
        SFTKDBHandle *handle = sftk_getDBForTokenObject(slot, object->handle);
#ifdef DEBUG
        SFTKTokenObject *to = sftk_narrowToTokenObject(object);
        PORT_Assert(to);
#endif
        crv = sftkdb_DestroyObject(handle, object->handle);
        sftk_freeDB(handle);
    }
    return crv;
}

/*
 * Token objects don't explicitly store their attributes, so we need to know
 * what attributes make up a particular token object before we can copy it.
 * below are the tables by object type.
 */
static const CK_ATTRIBUTE_TYPE commonAttrs[] = {
    CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_MODIFIABLE
};
static const CK_ULONG commonAttrsCount =
    sizeof(commonAttrs) / sizeof(commonAttrs[0]);

static const CK_ATTRIBUTE_TYPE commonKeyAttrs[] = {
    CKA_ID, CKA_START_DATE, CKA_END_DATE, CKA_DERIVE, CKA_LOCAL, CKA_KEY_TYPE
};
static const CK_ULONG commonKeyAttrsCount =
    sizeof(commonKeyAttrs) / sizeof(commonKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE secretKeyAttrs[] = {
    CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_ENCRYPT, CKA_DECRYPT, CKA_SIGN,
    CKA_VERIFY, CKA_WRAP, CKA_UNWRAP, CKA_VALUE
};
static const CK_ULONG secretKeyAttrsCount =
    sizeof(secretKeyAttrs) / sizeof(secretKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE commonPubKeyAttrs[] = {
    CKA_ENCRYPT, CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_WRAP, CKA_SUBJECT
};
static const CK_ULONG commonPubKeyAttrsCount =
    sizeof(commonPubKeyAttrs) / sizeof(commonPubKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE rsaPubKeyAttrs[] = {
    CKA_MODULUS, CKA_PUBLIC_EXPONENT
};
static const CK_ULONG rsaPubKeyAttrsCount =
    sizeof(rsaPubKeyAttrs) / sizeof(rsaPubKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE dsaPubKeyAttrs[] = {
    CKA_SUBPRIME, CKA_PRIME, CKA_BASE, CKA_VALUE
};
static const CK_ULONG dsaPubKeyAttrsCount =
    sizeof(dsaPubKeyAttrs) / sizeof(dsaPubKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE dhPubKeyAttrs[] = {
    CKA_PRIME, CKA_BASE, CKA_VALUE
};
static const CK_ULONG dhPubKeyAttrsCount =
    sizeof(dhPubKeyAttrs) / sizeof(dhPubKeyAttrs[0]);
#ifndef NSS_DISABLE_ECC
static const CK_ATTRIBUTE_TYPE ecPubKeyAttrs[] = {
    CKA_EC_PARAMS, CKA_EC_POINT
};
static const CK_ULONG ecPubKeyAttrsCount =
    sizeof(ecPubKeyAttrs) / sizeof(ecPubKeyAttrs[0]);
#endif

static const CK_ATTRIBUTE_TYPE commonPrivKeyAttrs[] = {
    CKA_DECRYPT, CKA_SIGN, CKA_SIGN_RECOVER, CKA_UNWRAP, CKA_SUBJECT,
    CKA_SENSITIVE, CKA_EXTRACTABLE, CKA_NETSCAPE_DB
};
static const CK_ULONG commonPrivKeyAttrsCount =
    sizeof(commonPrivKeyAttrs) / sizeof(commonPrivKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE rsaPrivKeyAttrs[] = {
    CKA_MODULUS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT,
    CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT
};
static const CK_ULONG rsaPrivKeyAttrsCount =
    sizeof(rsaPrivKeyAttrs) / sizeof(rsaPrivKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE dsaPrivKeyAttrs[] = {
    CKA_SUBPRIME, CKA_PRIME, CKA_BASE, CKA_VALUE
};
static const CK_ULONG dsaPrivKeyAttrsCount =
    sizeof(dsaPrivKeyAttrs) / sizeof(dsaPrivKeyAttrs[0]);

static const CK_ATTRIBUTE_TYPE dhPrivKeyAttrs[] = {
    CKA_PRIME, CKA_BASE, CKA_VALUE
};
static const CK_ULONG dhPrivKeyAttrsCount =
    sizeof(dhPrivKeyAttrs) / sizeof(dhPrivKeyAttrs[0]);
#ifndef NSS_DISABLE_ECC
static const CK_ATTRIBUTE_TYPE ecPrivKeyAttrs[] = {
    CKA_EC_PARAMS, CKA_VALUE
};
static const CK_ULONG ecPrivKeyAttrsCount =
    sizeof(ecPrivKeyAttrs) / sizeof(ecPrivKeyAttrs[0]);
#endif

static const CK_ATTRIBUTE_TYPE certAttrs[] = {
    CKA_CERTIFICATE_TYPE, CKA_VALUE, CKA_SUBJECT, CKA_ISSUER, CKA_SERIAL_NUMBER
};
static const CK_ULONG certAttrsCount =
    sizeof(certAttrs) / sizeof(certAttrs[0]);

static const CK_ATTRIBUTE_TYPE trustAttrs[] = {
    CKA_ISSUER, CKA_SERIAL_NUMBER, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH,
    CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_EMAIL_PROTECTION,
    CKA_TRUST_CODE_SIGNING, CKA_TRUST_STEP_UP_APPROVED
};
static const CK_ULONG trustAttrsCount =
    sizeof(trustAttrs) / sizeof(trustAttrs[0]);

static const CK_ATTRIBUTE_TYPE smimeAttrs[] = {
    CKA_SUBJECT, CKA_NETSCAPE_EMAIL, CKA_NETSCAPE_SMIME_TIMESTAMP, CKA_VALUE
};
static const CK_ULONG smimeAttrsCount =
    sizeof(smimeAttrs) / sizeof(smimeAttrs[0]);

static const CK_ATTRIBUTE_TYPE crlAttrs[] = {
    CKA_SUBJECT, CKA_VALUE, CKA_NETSCAPE_URL, CKA_NETSCAPE_KRL
};
static const CK_ULONG crlAttrsCount =
    sizeof(crlAttrs) / sizeof(crlAttrs[0]);

/* copy an object based on it's table */
CK_RV
stfk_CopyTokenAttributes(SFTKObject *destObject, SFTKTokenObject *src_to,
                         const CK_ATTRIBUTE_TYPE *attrArray, CK_ULONG attrCount)
{
    SFTKAttribute *attribute;
    SFTKAttribute *newAttribute;
    CK_RV crv = CKR_OK;
    unsigned int i;

    for (i = 0; i < attrCount; i++) {
        if (!sftk_hasAttribute(destObject, attrArray[i])) {
            attribute = sftk_FindAttribute(&src_to->obj, attrArray[i]);
            if (!attribute) {
                continue; /* return CKR_ATTRIBUTE_VALUE_INVALID; */
            }
            /* we need to copy the attribute since each attribute
             * only has one set of link list pointers */
            newAttribute = sftk_NewAttribute(destObject,
                                             sftk_attr_expand(&attribute->attrib));
            sftk_FreeAttribute(attribute); /* free the old attribute */
            if (!newAttribute) {
                return CKR_HOST_MEMORY;
            }
            sftk_AddAttribute(destObject, newAttribute);
        }
    }
    return crv;
}

CK_RV
stfk_CopyTokenPrivateKey(SFTKObject *destObject, SFTKTokenObject *src_to)
{
    CK_RV crv;
    CK_KEY_TYPE key_type;
    SFTKAttribute *attribute;

    /* copy the common attributes for all keys first */
    crv = stfk_CopyTokenAttributes(destObject, src_to, commonKeyAttrs,
                                   commonKeyAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }
    /* copy the common attributes for all private keys next */
    crv = stfk_CopyTokenAttributes(destObject, src_to, commonPrivKeyAttrs,
                                   commonPrivKeyAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }
    attribute = sftk_FindAttribute(&src_to->obj, CKA_KEY_TYPE);
    PORT_Assert(attribute); /* if it wasn't here, ww should have failed
                 * copying the common attributes */
    if (!attribute) {
        /* OK, so CKR_ATTRIBUTE_VALUE_INVALID is the immediate error, but
         * the fact is, the only reason we couldn't get the attribute would
         * be a memory error or database error (an error in the 'device').
         * if we have a database error code, we could return it here */
        crv = CKR_DEVICE_ERROR;
        goto fail;
    }
    key_type = *(CK_KEY_TYPE *)attribute->attrib.pValue;
    sftk_FreeAttribute(attribute);

    /* finally copy the attributes for various private key types */
    switch (key_type) {
        case CKK_RSA:
            crv = stfk_CopyTokenAttributes(destObject, src_to, rsaPrivKeyAttrs,
                                           rsaPrivKeyAttrsCount);
            break;
        case CKK_DSA:
            crv = stfk_CopyTokenAttributes(destObject, src_to, dsaPrivKeyAttrs,
                                           dsaPrivKeyAttrsCount);
            break;
        case CKK_DH:
            crv = stfk_CopyTokenAttributes(destObject, src_to, dhPrivKeyAttrs,
                                           dhPrivKeyAttrsCount);
            break;
#ifndef NSS_DISABLE_ECC
        case CKK_EC:
            crv = stfk_CopyTokenAttributes(destObject, src_to, ecPrivKeyAttrs,
                                           ecPrivKeyAttrsCount);
            break;
#endif
        default:
            crv = CKR_DEVICE_ERROR; /* shouldn't happen unless we store more types
                                     * of token keys into our database. */
    }
fail:
    return crv;
}

CK_RV
stfk_CopyTokenPublicKey(SFTKObject *destObject, SFTKTokenObject *src_to)
{
    CK_RV crv;
    CK_KEY_TYPE key_type;
    SFTKAttribute *attribute;

    /* copy the common attributes for all keys first */
    crv = stfk_CopyTokenAttributes(destObject, src_to, commonKeyAttrs,
                                   commonKeyAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }

    /* copy the common attributes for all public keys next */
    crv = stfk_CopyTokenAttributes(destObject, src_to, commonPubKeyAttrs,
                                   commonPubKeyAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }
    attribute = sftk_FindAttribute(&src_to->obj, CKA_KEY_TYPE);
    PORT_Assert(attribute); /* if it wasn't here, ww should have failed
                 * copying the common attributes */
    if (!attribute) {
        /* OK, so CKR_ATTRIBUTE_VALUE_INVALID is the immediate error, but
         * the fact is, the only reason we couldn't get the attribute would
         * be a memory error or database error (an error in the 'device').
         * if we have a database error code, we could return it here */
        crv = CKR_DEVICE_ERROR;
        goto fail;
    }
    key_type = *(CK_KEY_TYPE *)attribute->attrib.pValue;
    sftk_FreeAttribute(attribute);

    /* finally copy the attributes for various public key types */
    switch (key_type) {
        case CKK_RSA:
            crv = stfk_CopyTokenAttributes(destObject, src_to, rsaPubKeyAttrs,
                                           rsaPubKeyAttrsCount);
            break;
        case CKK_DSA:
            crv = stfk_CopyTokenAttributes(destObject, src_to, dsaPubKeyAttrs,
                                           dsaPubKeyAttrsCount);
            break;
        case CKK_DH:
            crv = stfk_CopyTokenAttributes(destObject, src_to, dhPubKeyAttrs,
                                           dhPubKeyAttrsCount);
            break;
#ifndef NSS_DISABLE_ECC
        case CKK_EC:
            crv = stfk_CopyTokenAttributes(destObject, src_to, ecPubKeyAttrs,
                                           ecPubKeyAttrsCount);
            break;
#endif
        default:
            crv = CKR_DEVICE_ERROR; /* shouldn't happen unless we store more types
                                     * of token keys into our database. */
    }
fail:
    return crv;
}
CK_RV
stfk_CopyTokenSecretKey(SFTKObject *destObject, SFTKTokenObject *src_to)
{
    CK_RV crv;
    crv = stfk_CopyTokenAttributes(destObject, src_to, commonKeyAttrs,
                                   commonKeyAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }
    crv = stfk_CopyTokenAttributes(destObject, src_to, secretKeyAttrs,
                                   secretKeyAttrsCount);
fail:
    return crv;
}

/*
 * Copy a token object. We need to explicitly copy the relevant
 * attributes since token objects don't store those attributes in
 * the token itself.
 */
CK_RV
sftk_CopyTokenObject(SFTKObject *destObject, SFTKObject *srcObject)
{
    SFTKTokenObject *src_to = sftk_narrowToTokenObject(srcObject);
    CK_RV crv;

    PORT_Assert(src_to);
    if (src_to == NULL) {
        return CKR_DEVICE_ERROR; /* internal state inconsistant */
    }

    crv = stfk_CopyTokenAttributes(destObject, src_to, commonAttrs,
                                   commonAttrsCount);
    if (crv != CKR_OK) {
        goto fail;
    }
    switch (src_to->obj.objclass) {
        case CKO_CERTIFICATE:
            crv = stfk_CopyTokenAttributes(destObject, src_to, certAttrs,
                                           certAttrsCount);
            break;
        case CKO_NETSCAPE_TRUST:
            crv = stfk_CopyTokenAttributes(destObject, src_to, trustAttrs,
                                           trustAttrsCount);
            break;
        case CKO_NETSCAPE_SMIME:
            crv = stfk_CopyTokenAttributes(destObject, src_to, smimeAttrs,
                                           smimeAttrsCount);
            break;
        case CKO_NETSCAPE_CRL:
            crv = stfk_CopyTokenAttributes(destObject, src_to, crlAttrs,
                                           crlAttrsCount);
            break;
        case CKO_PRIVATE_KEY:
            crv = stfk_CopyTokenPrivateKey(destObject, src_to);
            break;
        case CKO_PUBLIC_KEY:
            crv = stfk_CopyTokenPublicKey(destObject, src_to);
            break;
        case CKO_SECRET_KEY:
            crv = stfk_CopyTokenSecretKey(destObject, src_to);
            break;
        default:
            crv = CKR_DEVICE_ERROR; /* shouldn't happen unless we store more types
                                     * of token keys into our database. */
    }
fail:
    return crv;
}

/*
 * copy the attributes from one object to another. Don't overwrite existing
 * attributes. NOTE: This is a pretty expensive operation since it
 * grabs the attribute locks for the src object for a *long* time.
 */
CK_RV
sftk_CopyObject(SFTKObject *destObject, SFTKObject *srcObject)
{
    SFTKAttribute *attribute;
    SFTKSessionObject *src_so = sftk_narrowToSessionObject(srcObject);
    unsigned int i;

    if (src_so == NULL) {
        return sftk_CopyTokenObject(destObject, srcObject);
    }

    PZ_Lock(src_so->attributeLock);
    for (i = 0; i < src_so->hashSize; i++) {
        attribute = src_so->head[i];
        do {
            if (attribute) {
                if (!sftk_hasAttribute(destObject, attribute->handle)) {
                    /* we need to copy the attribute since each attribute
                     * only has one set of link list pointers */
                    SFTKAttribute *newAttribute = sftk_NewAttribute(
                        destObject, sftk_attr_expand(&attribute->attrib));
                    if (newAttribute == NULL) {
                        PZ_Unlock(src_so->attributeLock);
                        return CKR_HOST_MEMORY;
                    }
                    sftk_AddAttribute(destObject, newAttribute);
                }
                attribute = attribute->next;
            }
        } while (attribute != NULL);
    }
    PZ_Unlock(src_so->attributeLock);

    return CKR_OK;
}

/*
 * ******************** Search Utilities *******************************
 */

/* add an object to a search list */
CK_RV
AddToList(SFTKObjectListElement **list, SFTKObject *object)
{
    SFTKObjectListElement *newElem =
        (SFTKObjectListElement *)PORT_Alloc(sizeof(SFTKObjectListElement));

    if (newElem == NULL)
        return CKR_HOST_MEMORY;

    newElem->next = *list;
    newElem->object = object;
    sftk_ReferenceObject(object);

    *list = newElem;
    return CKR_OK;
}

/* return true if the object matches the template */
PRBool
sftk_objectMatch(SFTKObject *object, CK_ATTRIBUTE_PTR theTemplate, int count)
{
    int i;

    for (i = 0; i < count; i++) {
        SFTKAttribute *attribute = sftk_FindAttribute(object, theTemplate[i].type);
        if (attribute == NULL) {
            return PR_FALSE;
        }
        if (attribute->attrib.ulValueLen == theTemplate[i].ulValueLen) {
            if (PORT_Memcmp(attribute->attrib.pValue, theTemplate[i].pValue,
                            theTemplate[i].ulValueLen) == 0) {
                sftk_FreeAttribute(attribute);
                continue;
            }
        }
        sftk_FreeAttribute(attribute);
        return PR_FALSE;
    }
    return PR_TRUE;
}

/* search through all the objects in the queue and return the template matches
 * in the object list.
 */
CK_RV
sftk_searchObjectList(SFTKSearchResults *search, SFTKObject **head,
                      unsigned int size, PZLock *lock, CK_ATTRIBUTE_PTR theTemplate,
                      int count, PRBool isLoggedIn)
{
    unsigned int i;
    SFTKObject *object;
    CK_RV crv = CKR_OK;

    PZ_Lock(lock);
    for (i = 0; i < size; i++) {
        for (object = head[i]; object != NULL; object = object->next) {
            if (sftk_objectMatch(object, theTemplate, count)) {
                /* don't return objects that aren't yet visible */
                if ((!isLoggedIn) && sftk_isTrue(object, CKA_PRIVATE))
                    continue;
                sftk_addHandle(search, object->handle);
            }
        }
    }
    PZ_Unlock(lock);
    return crv;
}

/*
 * free a single list element. Return the Next object in the list.
 */
SFTKObjectListElement *
sftk_FreeObjectListElement(SFTKObjectListElement *objectList)
{
    SFTKObjectListElement *ol = objectList->next;

    sftk_FreeObject(objectList->object);
    PORT_Free(objectList);
    return ol;
}

/* free an entire object list */
void
sftk_FreeObjectList(SFTKObjectListElement *objectList)
{
    SFTKObjectListElement *ol;

    for (ol = objectList; ol != NULL; ol = sftk_FreeObjectListElement(ol)) {
    }
}

/*
 * free a search structure
 */
void
sftk_FreeSearch(SFTKSearchResults *search)
{
    if (search->handles) {
        PORT_Free(search->handles);
    }
    PORT_Free(search);
}

/*
 * ******************** Session Utilities *******************************
 */

/* update the sessions state based in it's flags and wether or not it's
 * logged in */
void
sftk_update_state(SFTKSlot *slot, SFTKSession *session)
{
    if (slot->isLoggedIn) {
        if (slot->ssoLoggedIn) {
            session->info.state = CKS_RW_SO_FUNCTIONS;
        } else if (session->info.flags & CKF_RW_SESSION) {
            session->info.state = CKS_RW_USER_FUNCTIONS;
        } else {
            session->info.state = CKS_RO_USER_FUNCTIONS;
        }
    } else {
        if (session->info.flags & CKF_RW_SESSION) {
            session->info.state = CKS_RW_PUBLIC_SESSION;
        } else {
            session->info.state = CKS_RO_PUBLIC_SESSION;
        }
    }
}

/* update the state of all the sessions on a slot */
void
sftk_update_all_states(SFTKSlot *slot)
{
    unsigned int i;
    SFTKSession *session;

    for (i = 0; i < slot->sessHashSize; i++) {
        PZLock *lock = SFTK_SESSION_LOCK(slot, i);
        PZ_Lock(lock);
        for (session = slot->head[i]; session; session = session->next) {
            sftk_update_state(slot, session);
        }
        PZ_Unlock(lock);
    }
}

/*
 * context are cipher and digest contexts that are associated with a session
 */
void
sftk_FreeContext(SFTKSessionContext *context)
{
    if (context->cipherInfo) {
        (*context->destroy)(context->cipherInfo, PR_TRUE);
    }
    if (context->hashInfo) {
        (*context->hashdestroy)(context->hashInfo, PR_TRUE);
    }
    if (context->key) {
        sftk_FreeObject(context->key);
        context->key = NULL;
    }
    PORT_Free(context);
}

/*
 * create a new nession. NOTE: The session handle is not set, and the
 * session is not added to the slot's session queue.
 */
SFTKSession *
sftk_NewSession(CK_SLOT_ID slotID, CK_NOTIFY notify, CK_VOID_PTR pApplication,
                CK_FLAGS flags)
{
    SFTKSession *session;
    SFTKSlot *slot = sftk_SlotFromID(slotID, PR_FALSE);

    if (slot == NULL)
        return NULL;

    session = (SFTKSession *)PORT_Alloc(sizeof(SFTKSession));
    if (session == NULL)
        return NULL;

    session->next = session->prev = NULL;
    session->refCount = 1;
    session->enc_context = NULL;
    session->hash_context = NULL;
    session->sign_context = NULL;
    session->search = NULL;
    session->objectIDCount = 1;
    session->objectLock = PZ_NewLock(nssILockObject);
    if (session->objectLock == NULL) {
        PORT_Free(session);
        return NULL;
    }
    session->objects[0] = NULL;

    session->slot = slot;
    session->notify = notify;
    session->appData = pApplication;
    session->info.flags = flags;
    session->info.slotID = slotID;
    session->info.ulDeviceError = 0;
    sftk_update_state(slot, session);
    return session;
}

/* free all the data associated with a session. */
static void
sftk_DestroySession(SFTKSession *session)
{
    SFTKObjectList *op, *next;
    PORT_Assert(session->refCount == 0);

    /* clean out the attributes */
    /* since no one is referencing us, it's safe to walk the chain
     * without a lock */
    for (op = session->objects[0]; op != NULL; op = next) {
        next = op->next;
        /* paranoia */
        op->next = op->prev = NULL;
        sftk_DeleteObject(session, op->parent);
    }
    PZ_DestroyLock(session->objectLock);
    if (session->enc_context) {
        sftk_FreeContext(session->enc_context);
    }
    if (session->hash_context) {
        sftk_FreeContext(session->hash_context);
    }
    if (session->sign_context) {
        sftk_FreeContext(session->sign_context);
    }
    if (session->search) {
        sftk_FreeSearch(session->search);
    }
    PORT_Free(session);
}

/*
 * look up a session structure from a session handle
 * generate a reference to it.
 */
SFTKSession *
sftk_SessionFromHandle(CK_SESSION_HANDLE handle)
{
    SFTKSlot *slot = sftk_SlotFromSessionHandle(handle);
    SFTKSession *session;
    PZLock *lock;

    if (!slot)
        return NULL;
    lock = SFTK_SESSION_LOCK(slot, handle);

    PZ_Lock(lock);
    sftkqueue_find(session, handle, slot->head, slot->sessHashSize);
    if (session)
        session->refCount++;
    PZ_Unlock(lock);

    return (session);
}

/*
 * release a reference to a session handle
 */
void
sftk_FreeSession(SFTKSession *session)
{
    PRBool destroy = PR_FALSE;
    SFTKSlot *slot = sftk_SlotFromSession(session);
    PZLock *lock = SFTK_SESSION_LOCK(slot, session->handle);

    PZ_Lock(lock);
    if (session->refCount == 1)
        destroy = PR_TRUE;
    session->refCount--;
    PZ_Unlock(lock);

    if (destroy)
        sftk_DestroySession(session);
}

void
sftk_addHandle(SFTKSearchResults *search, CK_OBJECT_HANDLE handle)
{
    if (search->handles == NULL) {
        return;
    }
    if (search->size >= search->array_size) {
        search->array_size += NSC_SEARCH_BLOCK_SIZE;
        search->handles = (CK_OBJECT_HANDLE *)PORT_Realloc(search->handles,
                                                           sizeof(CK_OBJECT_HANDLE) * search->array_size);
        if (search->handles == NULL) {
            return;
        }
    }
    search->handles[search->size] = handle;
    search->size++;
}

static CK_RV
handleToClass(SFTKSlot *slot, CK_OBJECT_HANDLE handle,
              CK_OBJECT_CLASS *objClass)
{
    SFTKDBHandle *dbHandle = sftk_getDBForTokenObject(slot, handle);
    CK_ATTRIBUTE objClassTemplate;
    CK_RV crv;

    *objClass = CKO_DATA;
    objClassTemplate.type = CKA_CLASS;
    objClassTemplate.pValue = objClass;
    objClassTemplate.ulValueLen = sizeof(*objClass);
    crv = sftkdb_GetAttributeValue(dbHandle, handle, &objClassTemplate, 1);
    sftk_freeDB(dbHandle);
    return crv;
}

SFTKObject *
sftk_NewTokenObject(SFTKSlot *slot, SECItem *dbKey, CK_OBJECT_HANDLE handle)
{
    SFTKObject *object = NULL;
    PRBool hasLocks = PR_FALSE;
    CK_RV crv;

    object = sftk_GetObjectFromList(&hasLocks, PR_FALSE, &tokenObjectList, 0,
                                    PR_FALSE);
    if (object == NULL) {
        return NULL;
    }

    object->handle = handle;
    /* every object must have a class, if we can't get it, the object
     * doesn't exist */
    crv = handleToClass(slot, handle, &object->objclass);
    if (crv != CKR_OK) {
        goto loser;
    }
    object->slot = slot;
    object->objectInfo = NULL;
    object->infoFree = NULL;
    if (!hasLocks) {
        object->refLock = PZ_NewLock(nssILockRefLock);
    }
    if (object->refLock == NULL) {
        goto loser;
    }
    object->refCount = 1;

    return object;
loser:
    (void)sftk_DestroyObject(object);
    return NULL;
}

SFTKTokenObject *
sftk_convertSessionToToken(SFTKObject *obj)
{
    SECItem *key;
    SFTKSessionObject *so = (SFTKSessionObject *)obj;
    SFTKTokenObject *to = sftk_narrowToTokenObject(obj);
    SECStatus rv;

    sftk_DestroySessionObjectData(so);
    PZ_DestroyLock(so->attributeLock);
    if (to == NULL) {
        return NULL;
    }
    sftk_tokenKeyLock(so->obj.slot);
    key = sftk_lookupTokenKeyByHandle(so->obj.slot, so->obj.handle);
    if (key == NULL) {
        sftk_tokenKeyUnlock(so->obj.slot);
        return NULL;
    }
    rv = SECITEM_CopyItem(NULL, &to->dbKey, key);
    sftk_tokenKeyUnlock(so->obj.slot);
    if (rv == SECFailure) {
        return NULL;
    }

    return to;
}

SFTKSessionObject *
sftk_narrowToSessionObject(SFTKObject *obj)
{
    return !sftk_isToken(obj->handle) ? (SFTKSessionObject *)obj : NULL;
}

SFTKTokenObject *
sftk_narrowToTokenObject(SFTKObject *obj)
{
    return sftk_isToken(obj->handle) ? (SFTKTokenObject *)obj : NULL;
}
