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

#ifndef DEVM_H
#include "devm.h"
#endif /* DEVM_H */

#ifndef CKHELPER_H
#include "ckhelper.h"
#endif /* CKHELPER_H */

#include "pk11pub.h"
#include "dev3hack.h"
#include "secerr.h"

NSS_IMPLEMENT nssCryptokiObject *
nssCryptokiObject_Create(
    NSSToken *t,
    nssSession *session,
    CK_OBJECT_HANDLE h)
{
    PRStatus status;
    NSSSlot *slot;
    nssCryptokiObject *object;
    CK_BBOOL *isTokenObject;
    CK_ATTRIBUTE cert_template[] = {
        { CKA_TOKEN, NULL, 0 },
        { CKA_LABEL, NULL, 0 }
    };
    slot = nssToken_GetSlot(t);
    status = nssCKObject_GetAttributes(h, cert_template, 2,
                                       NULL, session, slot);
    nssSlot_Destroy(slot);
    if (status != PR_SUCCESS) {
        /* a failure here indicates a device error */
        return (nssCryptokiObject *)NULL;
    }
    if (cert_template[0].ulValueLen == 0 || !cert_template[0].pValue) {
        nss_ZFreeIf(cert_template[1].pValue);
        return (nssCryptokiObject *)NULL;
    }
    object = nss_ZNEW(NULL, nssCryptokiObject);
    if (!object) {
        nss_ZFreeIf(cert_template[0].pValue);
        nss_ZFreeIf(cert_template[1].pValue);
        return (nssCryptokiObject *)NULL;
    }
    object->handle = h;
    object->token = nssToken_AddRef(t);
    isTokenObject = (CK_BBOOL *)cert_template[0].pValue;
    object->isTokenObject = *isTokenObject;
    object->trustType = CKM_INVALID_MECHANISM;
    nss_ZFreeIf(cert_template[0].pValue);
    NSS_CK_ATTRIBUTE_TO_UTF8(&cert_template[1], object->label);
    return object;
}

NSS_IMPLEMENT void
nssCryptokiObject_Destroy(
    nssCryptokiObject *object)
{
    if (object) {
        (void)nssToken_Destroy(object->token);
        nss_ZFreeIf(object->label);
        nss_ZFreeIf(object);
    }
}

NSS_IMPLEMENT nssCryptokiObject *
nssCryptokiObject_Clone(
    nssCryptokiObject *object)
{
    nssCryptokiObject *rvObject;
    rvObject = nss_ZNEW(NULL, nssCryptokiObject);
    if (rvObject) {
        rvObject->handle = object->handle;
        rvObject->token = nssToken_AddRef(object->token);
        rvObject->isTokenObject = object->isTokenObject;
        if (object->label) {
            rvObject->label = nssUTF8_Duplicate(object->label, NULL);
        }
    }
    return rvObject;
}

NSS_EXTERN PRBool
nssCryptokiObject_Equal(
    nssCryptokiObject *o1,
    nssCryptokiObject *o2)
{
    return (o1->token == o2->token && o1->handle == o2->handle);
}

NSS_IMPLEMENT PRUint32
nssPKCS11String_Length(CK_CHAR *pkcs11Str, PRUint32 bufLen)
{
    PRInt32 i;
    for (i = bufLen - 1; i >= 0;) {
        if (pkcs11Str[i] != ' ' && pkcs11Str[i] != '\0')
            break;
        --i;
    }
    return (PRUint32)(i + 1);
}

/*
 * Slot arrays
 */

NSS_IMPLEMENT NSSSlot **
nssSlotArray_Clone(
    NSSSlot **slots)
{
    NSSSlot **rvSlots = NULL;
    NSSSlot **sp = slots;
    PRUint32 count = 0;
    while (sp && *sp)
        count++;
    if (count > 0) {
        rvSlots = nss_ZNEWARRAY(NULL, NSSSlot *, count + 1);
        if (rvSlots) {
            for (sp = slots, count = 0; *sp; sp++) {
                rvSlots[count++] = nssSlot_AddRef(*sp);
            }
        }
    }
    return rvSlots;
}

NSS_IMPLEMENT void
nssSlotArray_Destroy(
    NSSSlot **slots)
{
    if (slots) {
        NSSSlot **slotp;
        for (slotp = slots; *slotp; slotp++) {
            nssSlot_Destroy(*slotp);
        }
        nss_ZFreeIf(slots);
    }
}

NSS_IMPLEMENT void
NSSSlotArray_Destroy(
    NSSSlot **slots)
{
    nssSlotArray_Destroy(slots);
}

NSS_IMPLEMENT void
nssTokenArray_Destroy(
    NSSToken **tokens)
{
    if (tokens) {
        NSSToken **tokenp;
        for (tokenp = tokens; *tokenp; tokenp++) {
            (void)nssToken_Destroy(*tokenp);
        }
        nss_ZFreeIf(tokens);
    }
}

NSS_IMPLEMENT void
nssCryptokiObjectArray_Destroy(
    nssCryptokiObject **objects)
{
    if (objects) {
        nssCryptokiObject **op;
        for (op = objects; *op; op++) {
            nssCryptokiObject_Destroy(*op);
        }
        nss_ZFreeIf(objects);
    }
}

/* object cache for token */

typedef struct
{
    NSSArena *arena;
    nssCryptokiObject *object;
    CK_ATTRIBUTE_PTR attributes;
    CK_ULONG numAttributes;
} nssCryptokiObjectAndAttributes;

enum {
    cachedCerts = 0,
    cachedTrust = 1,
    cachedCRLs = 2
} cachedObjectType;

struct nssTokenObjectCacheStr {
    NSSToken *token;
    PRLock *lock;
    PRBool loggedIn;
    PRBool doObjectType[3];
    PRBool searchedObjectType[3];
    nssCryptokiObjectAndAttributes **objects[3];
};

NSS_IMPLEMENT nssTokenObjectCache *
nssTokenObjectCache_Create(
    NSSToken *token,
    PRBool cacheCerts,
    PRBool cacheTrust,
    PRBool cacheCRLs)
{
    nssTokenObjectCache *rvCache;
    rvCache = nss_ZNEW(NULL, nssTokenObjectCache);
    if (!rvCache) {
        goto loser;
    }
    rvCache->lock = PR_NewLock(); /* XXX */
    if (!rvCache->lock) {
        goto loser;
    }
    rvCache->doObjectType[cachedCerts] = cacheCerts;
    rvCache->doObjectType[cachedTrust] = cacheTrust;
    rvCache->doObjectType[cachedCRLs] = cacheCRLs;
    rvCache->token = token; /* cache goes away with token */
    return rvCache;
loser:
    nssTokenObjectCache_Destroy(rvCache);
    return (nssTokenObjectCache *)NULL;
}

static void
clear_cache(
    nssTokenObjectCache *cache)
{
    nssCryptokiObjectAndAttributes **oa;
    PRUint32 objectType;
    for (objectType = cachedCerts; objectType <= cachedCRLs; objectType++) {
        cache->searchedObjectType[objectType] = PR_FALSE;
        if (!cache->objects[objectType]) {
            continue;
        }
        for (oa = cache->objects[objectType]; *oa; oa++) {
            /* prevent the token from being destroyed */
            (*oa)->object->token = NULL;
            nssCryptokiObject_Destroy((*oa)->object);
            nssArena_Destroy((*oa)->arena);
        }
        nss_ZFreeIf(cache->objects[objectType]);
        cache->objects[objectType] = NULL;
    }
}

NSS_IMPLEMENT void
nssTokenObjectCache_Clear(
    nssTokenObjectCache *cache)
{
    if (cache) {
        PR_Lock(cache->lock);
        clear_cache(cache);
        PR_Unlock(cache->lock);
    }
}

NSS_IMPLEMENT void
nssTokenObjectCache_Destroy(
    nssTokenObjectCache *cache)
{
    if (cache) {
        clear_cache(cache);
        if (cache->lock) {
            PR_DestroyLock(cache->lock);
        }
        nss_ZFreeIf(cache);
    }
}

NSS_IMPLEMENT PRBool
nssTokenObjectCache_HaveObjectClass(
    nssTokenObjectCache *cache,
    CK_OBJECT_CLASS objclass)
{
    PRBool haveIt;
    PR_Lock(cache->lock);
    switch (objclass) {
        case CKO_CERTIFICATE:
            haveIt = cache->doObjectType[cachedCerts];
            break;
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            haveIt = cache->doObjectType[cachedTrust];
            break;
        case CKO_NSS_CRL:
            haveIt = cache->doObjectType[cachedCRLs];
            break;
        default:
            haveIt = PR_FALSE;
    }
    PR_Unlock(cache->lock);
    return haveIt;
}

static nssCryptokiObjectAndAttributes **
create_object_array(
    nssCryptokiObject **objects,
    PRBool *doObjects,
    PRUint32 *numObjects,
    PRStatus *status)
{
    nssCryptokiObjectAndAttributes **rvOandA = NULL;
    *numObjects = 0;
    /* There are no objects for this type */
    if (!objects || !*objects) {
        *status = PR_SUCCESS;
        return rvOandA;
    }
    while (*objects++)
        (*numObjects)++;
    if (*numObjects >= MAX_LOCAL_CACHE_OBJECTS) {
        /* Hit the maximum allowed, so don't use a cache (there are
         * too many objects to make caching worthwhile, presumably, if
         * the token can handle that many objects, it can handle searching.
         */
        *doObjects = PR_FALSE;
        *status = PR_FAILURE;
        *numObjects = 0;
    } else {
        rvOandA = nss_ZNEWARRAY(NULL,
                                nssCryptokiObjectAndAttributes *,
                                *numObjects + 1);
        *status = rvOandA ? PR_SUCCESS : PR_FAILURE;
    }
    return rvOandA;
}

static nssCryptokiObjectAndAttributes *
create_object(
    nssCryptokiObject *object,
    const CK_ATTRIBUTE_TYPE *types,
    PRUint32 numTypes,
    PRStatus *status)
{
    PRUint32 j;
    NSSArena *arena = NULL;
    NSSSlot *slot = NULL;
    nssSession *session = NULL;
    nssCryptokiObjectAndAttributes *rvCachedObject = NULL;

    slot = nssToken_GetSlot(object->token);
    if (!slot) {
        nss_SetError(NSS_ERROR_INVALID_POINTER);
        goto loser;
    }
    session = nssToken_GetDefaultSession(object->token);
    if (!session) {
        nss_SetError(NSS_ERROR_INVALID_POINTER);
        goto loser;
    }
    arena = nssArena_Create();
    if (!arena) {
        goto loser;
    }
    rvCachedObject = nss_ZNEW(arena, nssCryptokiObjectAndAttributes);
    if (!rvCachedObject) {
        goto loser;
    }
    rvCachedObject->arena = arena;
    rvCachedObject->object = object;
    rvCachedObject->attributes = nss_ZNEWARRAY(arena, CK_ATTRIBUTE, numTypes);
    if (!rvCachedObject->attributes) {
        goto loser;
    }
    for (j = 0; j < numTypes; j++) {
        rvCachedObject->attributes[j].type = types[j];
    }
    *status = nssCKObject_GetAttributes(object->handle,
                                        rvCachedObject->attributes,
                                        numTypes,
                                        arena,
                                        session,
                                        slot);
    if (*status != PR_SUCCESS) {
        goto loser;
    }
    rvCachedObject->numAttributes = numTypes;
    *status = PR_SUCCESS;
    nssSlot_Destroy(slot);

    /* The cache is tied to the token, and therefore the objects in it should
     * not hold references to the token. Drop the object's token reference
     * only after success so that on failure the caller can still safely
     * destroy the object (and its token reference) exactly once.
     */
    (void)nssToken_Destroy(object->token);
    return rvCachedObject;
loser:
    *status = PR_FAILURE;
    if (slot) {
        nssSlot_Destroy(slot);
    }
    if (arena)
        nssArena_Destroy(arena);
    return (nssCryptokiObjectAndAttributes *)NULL;
}

/*
 *
 * State diagram for cache:
 *
 *            token !present            token removed
 *        +-------------------------+<----------------------+
 *        |                         ^                       |
 *        v                         |                       |
 *  +----------+   slot friendly    |  token present   +----------+
 *  |   cache  | -----------------> % ---------------> |   cache  |
 *  | unloaded |                                       |  loaded  |
 *  +----------+                                       +----------+
 *    ^   |                                                 ^   |
 *    |   |   slot !friendly           slot logged in       |   |
 *    |   +-----------------------> % ----------------------+   |
 *    |                             |                           |
 *    | slot logged out             v  slot !friendly           |
 *    +-----------------------------+<--------------------------+
 *
 */

/* This function must not be called with cache->lock locked. */
static PRBool
token_is_present(
    nssTokenObjectCache *cache)
{
    NSSSlot *slot = nssToken_GetSlot(cache->token);
    PRBool tokenPresent = nssSlot_IsTokenPresent(slot);
    nssSlot_Destroy(slot);
    return tokenPresent;
}

static PRBool
search_for_objects(
    nssTokenObjectCache *cache)
{
    PRBool doSearch = PR_FALSE;
    NSSSlot *slot = nssToken_GetSlot(cache->token);
    /* Handle non-friendly slots (slots which require login for objects) */
    if (!nssSlot_IsFriendly(slot)) {
        if (nssSlot_IsLoggedIn(slot)) {
            /* Either no state change, or went from !logged in -> logged in */
            cache->loggedIn = PR_TRUE;
            doSearch = PR_TRUE;
        } else {
            if (cache->loggedIn) {
                /* went from logged in -> !logged in, destroy cached objects */
                clear_cache(cache);
                cache->loggedIn = PR_FALSE;
            } /* else no state change, still not logged in, so exit */
        }
    } else {
        /* slot is friendly, thus always available for search */
        doSearch = PR_TRUE;
    }
    nssSlot_Destroy(slot);
    return doSearch;
}

static nssCryptokiObjectAndAttributes *
create_cert(
    nssCryptokiObject *object,
    PRStatus *status)
{
    static const CK_ATTRIBUTE_TYPE certAttr[] = {
        CKA_CLASS,
        CKA_TOKEN,
        CKA_LABEL,
        CKA_CERTIFICATE_TYPE,
        CKA_ID,
        CKA_VALUE,
        CKA_ISSUER,
        CKA_SERIAL_NUMBER,
        CKA_SUBJECT,
        CKA_NSS_EMAIL
    };
    static const PRUint32 numCertAttr = sizeof(certAttr) / sizeof(certAttr[0]);
    return create_object(object, certAttr, numCertAttr, status);
}

static nssCryptokiObjectAndAttributes *
create_trust(
    nssCryptokiObject *object,
    PRStatus *status)
{
    static const CK_ATTRIBUTE_TYPE nssTrustAttr[] = {
        CKA_CLASS,
        CKA_TOKEN,
        CKA_LABEL,
        CKA_NSS_CERT_SHA1_HASH,
        CKA_NSS_CERT_MD5_HASH,
        CKA_ISSUER,
        CKA_SUBJECT,
        CKA_NSS_TRUST_SERVER_AUTH,
        CKA_NSS_TRUST_CLIENT_AUTH,
        CKA_NSS_TRUST_EMAIL_PROTECTION,
        CKA_NSS_TRUST_CODE_SIGNING
    };
    static const CK_ATTRIBUTE_TYPE pkcsTrustAttr[] = {
        CKA_CLASS,
        CKA_TOKEN,
        CKA_LABEL,
        CKA_HASH_OF_CERTIFICATE,
        CKA_NAME_HASH_ALGORITHM,
        CKA_ISSUER,
        CKA_SUBJECT,
        CKA_PKCS_TRUST_SERVER_AUTH,
        CKA_PKCS_TRUST_CLIENT_AUTH,
        CKA_PKCS_TRUST_EMAIL_PROTECTION,
        CKA_PKCS_TRUST_CODE_SIGNING
    };
    static const PRUint32 numNSSTrustAttr = PR_ARRAY_SIZE(nssTrustAttr);
    static const PRUint32 numPKCSTrustAttr = PR_ARRAY_SIZE(pkcsTrustAttr);
    const CK_ATTRIBUTE_TYPE *trustAttr;
    PRUint32 numTrustAttr;

    trustAttr = (object->trustType == CKO_TRUST) ? pkcsTrustAttr
                                                 : nssTrustAttr;
    numTrustAttr = (object->trustType == CKO_TRUST) ? numPKCSTrustAttr
                                                    : numNSSTrustAttr;
    return create_object(object, trustAttr, numTrustAttr, status);
}

static nssCryptokiObjectAndAttributes *
create_crl(
    nssCryptokiObject *object,
    PRStatus *status)
{
    static const CK_ATTRIBUTE_TYPE crlAttr[] = {
        CKA_CLASS,
        CKA_TOKEN,
        CKA_LABEL,
        CKA_VALUE,
        CKA_SUBJECT,
        CKA_NSS_KRL,
        CKA_NSS_URL
    };
    static const PRUint32 numCRLAttr = sizeof(crlAttr) / sizeof(crlAttr[0]);
    return create_object(object, crlAttr, numCRLAttr, status);
}

/* Dispatch to the create function for the object type */
static nssCryptokiObjectAndAttributes *
create_object_of_type(
    nssCryptokiObject *object,
    PRUint32 objectType,
    PRStatus *status)
{
    if (objectType == cachedCerts) {
        return create_cert(object, status);
    }
    if (objectType == cachedTrust) {
        return create_trust(object, status);
    }
    if (objectType == cachedCRLs) {
        return create_crl(object, status);
    }
    return (nssCryptokiObjectAndAttributes *)NULL;
}

static PRStatus
get_token_objects_for_cache(
    nssTokenObjectCache *cache,
    PRUint32 objectType,
    CK_OBJECT_CLASS objclass)
{
    PRStatus status;
    nssCryptokiObject **objects;
    PRBool *doIt = &cache->doObjectType[objectType];
    PRUint32 i, numObjects;

    if (!search_for_objects(cache) ||
        cache->searchedObjectType[objectType] ||
        !cache->doObjectType[objectType]) {
        /* Either there was a state change that prevents a search
         * (token logged out), or the search was already done,
         * or objects of this type are not being cached.
         */
        return PR_SUCCESS;
    }
    objects = nssToken_FindObjects(cache->token, NULL, objclass,
                                   nssTokenSearchType_TokenForced,
                                   MAX_LOCAL_CACHE_OBJECTS, &status);
    if (status != PR_SUCCESS) {
        return status;
    }
    cache->objects[objectType] = create_object_array(objects,
                                                     doIt,
                                                     &numObjects,
                                                     &status);
    if (status != PR_SUCCESS) {
        nssCryptokiObjectArray_Destroy(objects);
        return status;
    }
    for (i = 0; i < numObjects; i++) {
        cache->objects[objectType][i] = create_object_of_type(objects[i],
                                                              objectType,
                                                              &status);
        if (status != PR_SUCCESS) {
            break;
        }
    }
    if (status == PR_SUCCESS) {
        nss_ZFreeIf(objects);
    } else {
        PRUint32 j;
        for (j = 0; j < i; j++) {
            /* Objects that were successfully added to the cache do not own a
             * token reference (they share a reference with the cache itself).
             * Nulling out the pointer here prevents the token's refcount
             * from being decremented in nssCryptokiObject_Destroy */
            cache->objects[objectType][j]->object->token = NULL;
            nssArena_Destroy(cache->objects[objectType][j]->arena);
        }
        nss_ZFreeIf(cache->objects[objectType]);
        cache->objects[objectType] = NULL;
        nssCryptokiObjectArray_Destroy(objects);
    }
    cache->searchedObjectType[objectType] = PR_TRUE;
    return status;
}

static CK_ATTRIBUTE_PTR
find_attribute_in_object(
    nssCryptokiObjectAndAttributes *obj,
    CK_ATTRIBUTE_TYPE attrType)
{
    PRUint32 j;
    for (j = 0; j < obj->numAttributes; j++) {
        if (attrType == obj->attributes[j].type) {
            return &obj->attributes[j];
        }
    }
    return (CK_ATTRIBUTE_PTR)NULL;
}

/* Find all objects in the array that match the supplied template */
static nssCryptokiObject **
find_objects_in_array(
    nssCryptokiObjectAndAttributes **objArray,
    CK_ATTRIBUTE_PTR ot,
    CK_ULONG otlen,
    PRUint32 maximumOpt)
{
    PRIntn oi = 0;
    PRUint32 i;
    NSSArena *arena;
    PRUint32 size = 8;
    PRUint32 numMatches = 0;
    nssCryptokiObject **objects = NULL;
    nssCryptokiObjectAndAttributes **matches = NULL;
    CK_ATTRIBUTE_PTR attr;

    if (!objArray) {
        return (nssCryptokiObject **)NULL;
    }
    arena = nssArena_Create();
    if (!arena) {
        return (nssCryptokiObject **)NULL;
    }
    matches = nss_ZNEWARRAY(arena, nssCryptokiObjectAndAttributes *, size);
    if (!matches) {
        goto loser;
    }
    if (maximumOpt == 0)
        maximumOpt = ~0;
    /* loop over the cached objects */
    for (; *objArray && numMatches < maximumOpt; objArray++) {
        nssCryptokiObjectAndAttributes *obj = *objArray;
        /* loop over the test template */
        for (i = 0; i < otlen; i++) {
            /* see if the object has the attribute */
            attr = find_attribute_in_object(obj, ot[i].type);
            if (!attr) {
                /* nope, match failed */
                break;
            }
            /* compare the attribute against the test value */
            if (ot[i].ulValueLen != attr->ulValueLen ||
                !nsslibc_memequal(ot[i].pValue,
                                  attr->pValue,
                                  attr->ulValueLen, NULL)) {
                /* nope, match failed */
                break;
            }
        }
        if (i == otlen) {
            /* all of the attributes in the test template were found
             * in the object's template, and they all matched
             */
            matches[numMatches++] = obj;
            if (numMatches == size) {
                size *= 2;
                matches = nss_ZREALLOCARRAY(matches,
                                            nssCryptokiObjectAndAttributes *,
                                            size);
                if (!matches) {
                    goto loser;
                }
            }
        }
    }
    if (numMatches > 0) {
        objects = nss_ZNEWARRAY(NULL, nssCryptokiObject *, numMatches + 1);
        if (!objects) {
            goto loser;
        }
        for (oi = 0; oi < (PRIntn)numMatches; oi++) {
            objects[oi] = nssCryptokiObject_Clone(matches[oi]->object);
            if (!objects[oi]) {
                goto loser;
            }
        }
    }
    nssArena_Destroy(arena);
    return objects;
loser:
    nssCryptokiObjectArray_Destroy(objects);
    nssArena_Destroy(arena);
    return (nssCryptokiObject **)NULL;
}

NSS_IMPLEMENT nssCryptokiObject **
nssTokenObjectCache_FindObjectsByTemplate(
    nssTokenObjectCache *cache,
    CK_OBJECT_CLASS objclass,
    CK_ATTRIBUTE_PTR otemplate,
    CK_ULONG otlen,
    PRUint32 maximumOpt,
    PRStatus *statusOpt)
{
    PRStatus status = PR_FAILURE;
    nssCryptokiObject **rvObjects = NULL;
    PRUint32 objectType;
    if (!token_is_present(cache)) {
        status = PR_SUCCESS;
        goto finish;
    }
    switch (objclass) {
        case CKO_CERTIFICATE:
            objectType = cachedCerts;
            break;
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            objectType = cachedTrust;
            break;
        case CKO_NSS_CRL:
            objectType = cachedCRLs;
            break;
        default:
            goto finish;
    }
    PR_Lock(cache->lock);
    if (cache->doObjectType[objectType]) {
        status = get_token_objects_for_cache(cache, objectType, objclass);
        if (status == PR_SUCCESS) {
            rvObjects = find_objects_in_array(cache->objects[objectType],
                                              otemplate, otlen, maximumOpt);
        }
    }
    PR_Unlock(cache->lock);
finish:
    if (statusOpt) {
        *statusOpt = status;
    }
    return rvObjects;
}

static PRBool
cache_available_for_object_type(
    nssTokenObjectCache *cache,
    PRUint32 objectType)
{
    if (!cache->doObjectType[objectType]) {
        /* not caching this object kind */
        return PR_FALSE;
    }
    if (!cache->searchedObjectType[objectType]) {
        /* objects are not cached yet */
        return PR_FALSE;
    }
    if (!search_for_objects(cache)) {
        /* not logged in */
        return PR_FALSE;
    }
    return PR_TRUE;
}

NSS_IMPLEMENT PRStatus
nssTokenObjectCache_GetObjectAttributes(
    nssTokenObjectCache *cache,
    NSSArena *arenaOpt,
    nssCryptokiObject *object,
    CK_OBJECT_CLASS objclass,
    CK_ATTRIBUTE_PTR atemplate,
    CK_ULONG atlen)
{
    PRUint32 i, j;
    NSSArena *arena = NULL;
    nssArenaMark *mark = NULL;
    nssCryptokiObjectAndAttributes *cachedOA = NULL;
    nssCryptokiObjectAndAttributes **oa = NULL;
    PRUint32 objectType;
    if (!token_is_present(cache)) {
        return PR_FAILURE;
    }
    PR_Lock(cache->lock);
    switch (objclass) {
        case CKO_CERTIFICATE:
            objectType = cachedCerts;
            break;
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            objectType = cachedTrust;
            break;
        case CKO_NSS_CRL:
            objectType = cachedCRLs;
            break;
        default:
            goto loser;
    }
    if (!cache_available_for_object_type(cache, objectType)) {
        goto loser;
    }
    oa = cache->objects[objectType];
    if (!oa) {
        goto loser;
    }
    for (; *oa; oa++) {
        if (nssCryptokiObject_Equal((*oa)->object, object)) {
            cachedOA = *oa;
            break;
        }
    }
    if (!cachedOA) {
        goto loser; /* don't have this object */
    }
    if (arenaOpt) {
        arena = arenaOpt;
        mark = nssArena_Mark(arena);
    }
    for (i = 0; i < atlen; i++) {
        for (j = 0; j < cachedOA->numAttributes; j++) {
            if (atemplate[i].type == cachedOA->attributes[j].type) {
                CK_ATTRIBUTE_PTR attr = &cachedOA->attributes[j];
                if (cachedOA->attributes[j].ulValueLen == 0 ||
                    cachedOA->attributes[j].ulValueLen == (CK_ULONG)-1) {
                    break; /* invalid attribute */
                }
                if (atemplate[i].ulValueLen > 0) {
                    if (atemplate[i].pValue == NULL ||
                        atemplate[i].ulValueLen < attr->ulValueLen) {
                        goto loser;
                    }
                } else {
                    atemplate[i].pValue = nss_ZAlloc(arena, attr->ulValueLen);
                    if (!atemplate[i].pValue) {
                        goto loser;
                    }
                }
                nsslibc_memcpy(atemplate[i].pValue,
                               attr->pValue, attr->ulValueLen);
                atemplate[i].ulValueLen = attr->ulValueLen;
                break;
            }
        }
        if (j == cachedOA->numAttributes) {
            atemplate[i].ulValueLen = (CK_ULONG)-1;
        }
    }
    PR_Unlock(cache->lock);
    if (mark) {
        nssArena_Unmark(arena, mark);
    }
    return PR_SUCCESS;
loser:
    PR_Unlock(cache->lock);
    if (mark) {
        nssArena_Release(arena, mark);
    }
    return PR_FAILURE;
}

NSS_IMPLEMENT PRStatus
nssTokenObjectCache_ImportObject(
    nssTokenObjectCache *cache,
    nssCryptokiObject *object,
    CK_OBJECT_CLASS objclass,
    CK_ATTRIBUTE_PTR ot,
    CK_ULONG otlen)
{
    PRStatus status = PR_SUCCESS;
    PRUint32 count;
    nssCryptokiObjectAndAttributes **oa, ***otype;
    PRUint32 objectType;
    PRBool haveIt = PR_FALSE;

    if (!token_is_present(cache)) {
        return PR_SUCCESS; /* cache not active, ignored */
    }
    PR_Lock(cache->lock);
    switch (objclass) {
        case CKO_CERTIFICATE:
            objectType = cachedCerts;
            break;
        case CKO_NSS_TRUST:
        case CKO_TRUST:
            objectType = cachedTrust;
            object->trustType = objclass;
            break;
        case CKO_NSS_CRL:
            objectType = cachedCRLs;
            break;
        default:
            PR_Unlock(cache->lock);
            return PR_SUCCESS; /* don't need to import it here */
    }
    if (!cache_available_for_object_type(cache, objectType)) {
        PR_Unlock(cache->lock);
        return PR_SUCCESS; /* cache not active, ignored */
    }
    count = 0;
    otype = &cache->objects[objectType]; /* index into array of types */
    oa = *otype;                         /* the array of objects for this type */
    while (oa && *oa) {
        if (nssCryptokiObject_Equal((*oa)->object, object)) {
            haveIt = PR_TRUE;
            break;
        }
        count++;
        oa++;
    }
    if (haveIt) {
        /* Destroy the old entry */
        (*oa)->object->token = NULL;
        nssCryptokiObject_Destroy((*oa)->object);
        nssArena_Destroy((*oa)->arena);
    } else {
        /* Create space for a new entry */
        if (count > 0) {
            *otype = nss_ZREALLOCARRAY(*otype,
                                       nssCryptokiObjectAndAttributes *,
                                       count + 2);
        } else {
            *otype = nss_ZNEWARRAY(NULL, nssCryptokiObjectAndAttributes *, 2);
        }
    }
    if (*otype) {
        nssCryptokiObject *copyObject = nssCryptokiObject_Clone(object);
        (*otype)[count] = create_object_of_type(copyObject, objectType,
                                                &status);
    } else {
        status = PR_FAILURE;
    }
    PR_Unlock(cache->lock);
    return status;
}

NSS_IMPLEMENT void
nssTokenObjectCache_RemoveObject(
    nssTokenObjectCache *cache,
    nssCryptokiObject *object)
{
    PRUint32 oType;
    nssCryptokiObjectAndAttributes **oa, **swp = NULL;
    if (!token_is_present(cache)) {
        return;
    }
    PR_Lock(cache->lock);
    for (oType = 0; oType < 3; oType++) {
        if (!cache_available_for_object_type(cache, oType) ||
            !cache->objects[oType]) {
            continue;
        }
        for (oa = cache->objects[oType]; *oa; oa++) {
            if (nssCryptokiObject_Equal((*oa)->object, object)) {
                swp = oa; /* the entry to remove */
                while (oa[1])
                    oa++; /* go to the tail */
                (*swp)->object->token = NULL;
                nssCryptokiObject_Destroy((*swp)->object);
                nssArena_Destroy((*swp)->arena); /* destroy it */
                *swp = *oa;                      /* swap the last with the removed */
                *oa = NULL;                      /* null-terminate the array */
                break;
            }
        }
        if (swp) {
            break;
        }
    }
    if ((oType < 3) &&
        cache->objects[oType] && cache->objects[oType][0] == NULL) {
        nss_ZFreeIf(cache->objects[oType]); /* no entries remaining */
        cache->objects[oType] = NULL;
    }
    PR_Unlock(cache->lock);
}

/* We need a general hash to support CKO_TRUST
** Replace the algorithm specific version used for
** CKA_CERT_SHA1_HASH and CKA_CERT_MD5_HASH attributes.
** CKA_HASH_OF_CERTIFICATE uses the mechanism specified
** in CKA_NAME_HASH_ALGORITHM, with can be passed to this
** function.
*/
/* XXX of course this doesn't belong here */
NSS_IMPLEMENT NSSAlgorithmAndParameters *
NSSAlgorithmAndParameters_CreateDigest(
    NSSArena *arenaOpt, CK_MECHANISM_TYPE hashMech)
{
    NSSAlgorithmAndParameters *rvAP = NULL;
    rvAP = nss_ZNEW(arenaOpt, NSSAlgorithmAndParameters);
    if (rvAP) {
        rvAP->mechanism.mechanism = hashMech;
        rvAP->mechanism.pParameter = NULL;
        rvAP->mechanism.ulParameterLen = 0;
    }
    return rvAP;
}

/*
 * we need a DigestBuf for both create and verify trust objects.
 * So put it here for now. We don't have any Stan based crypto operations
 * yet.
 */
NSS_IMPLEMENT PRStatus
NSSAlgorithm_DigestBuf(CK_MECHANISM_TYPE type, NSSItem *input, NSSItem *output)
{
    PRStatus ret = PR_FAILURE;
    NSSAlgorithmAndParameters *ap = NULL;
    PK11SlotInfo *internal = NULL;
    NSSToken *token = NULL;
    NSSItem *dummy = NULL;

    internal = PK11_GetInternalSlot();
    if (!internal) {
        goto done;
    }
    token = PK11Slot_GetNSSToken(internal);
    if (!token) {
        goto done;
    }
    ap = NSSAlgorithmAndParameters_CreateDigest(NULL, type);
    if (!token) {
        goto done;
    }

    dummy = nssToken_Digest(token, NULL, ap, input, output, NULL);
    if (dummy) {
        ret = PR_SUCCESS;
    }

done:
    nss_ZFreeIf(ap);
    if (token) {
        (void)nssToken_Destroy(token);
    }
    if (internal) {
        PK11_FreeSlot(internal);
    }
    return ret;
}
