/* 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 DEV_H
#include "dev.h"
#endif /* DEV_H */

#ifndef PKIM_H
#include "pkim.h"
#endif /* PKIM_H */

#include "pki3hack.h"

extern const NSSError NSS_ERROR_NOT_FOUND;

NSS_IMPLEMENT void
nssPKIObject_Lock(nssPKIObject *object)
{
    switch (object->lockType) {
        case nssPKIMonitor:
            PR_EnterMonitor(object->sync.mlock);
            break;
        case nssPKILock:
            PR_Lock(object->sync.lock);
            break;
        default:
            PORT_Assert(0);
    }
}

NSS_IMPLEMENT void
nssPKIObject_Unlock(nssPKIObject *object)
{
    switch (object->lockType) {
        case nssPKIMonitor:
            PR_ExitMonitor(object->sync.mlock);
            break;
        case nssPKILock:
            PR_Unlock(object->sync.lock);
            break;
        default:
            PORT_Assert(0);
    }
}

NSS_IMPLEMENT PRStatus
nssPKIObject_NewLock(nssPKIObject *object, nssPKILockType lockType)
{
    object->lockType = lockType;
    switch (lockType) {
        case nssPKIMonitor:
            object->sync.mlock = PR_NewMonitor();
            return (object->sync.mlock ? PR_SUCCESS : PR_FAILURE);
        case nssPKILock:
            object->sync.lock = PR_NewLock();
            return (object->sync.lock ? PR_SUCCESS : PR_FAILURE);
        default:
            PORT_Assert(0);
            return PR_FAILURE;
    }
}

NSS_IMPLEMENT void
nssPKIObject_DestroyLock(nssPKIObject *object)
{
    switch (object->lockType) {
        case nssPKIMonitor:
            PR_DestroyMonitor(object->sync.mlock);
            object->sync.mlock = NULL;
            break;
        case nssPKILock:
            PR_DestroyLock(object->sync.lock);
            object->sync.lock = NULL;
            break;
        default:
            PORT_Assert(0);
    }
}

NSS_IMPLEMENT nssPKIObject *
nssPKIObject_Create(
    NSSArena *arenaOpt,
    nssCryptokiObject *instanceOpt,
    NSSTrustDomain *td,
    NSSCryptoContext *cc,
    nssPKILockType lockType)
{
    NSSArena *arena;
    nssArenaMark *mark = NULL;
    nssPKIObject *object;
    if (arenaOpt) {
        arena = arenaOpt;
        mark = nssArena_Mark(arena);
    } else {
        arena = nssArena_Create();
        if (!arena) {
            return (nssPKIObject *)NULL;
        }
    }
    object = nss_ZNEW(arena, nssPKIObject);
    if (!object) {
        goto loser;
    }
    object->arena = arena;
    object->trustDomain = td; /* XXX */
    object->cryptoContext = cc;
    if (PR_SUCCESS != nssPKIObject_NewLock(object, lockType)) {
        goto loser;
    }
    if (instanceOpt) {
        if (nssPKIObject_AddInstance(object, instanceOpt) != PR_SUCCESS) {
            goto loser;
        }
    }
    PR_ATOMIC_INCREMENT(&object->refCount);
    if (mark) {
        nssArena_Unmark(arena, mark);
    }
    return object;
loser:
    if (mark) {
        nssArena_Release(arena, mark);
    } else {
        nssArena_Destroy(arena);
    }
    return (nssPKIObject *)NULL;
}

NSS_IMPLEMENT PRBool
nssPKIObject_Destroy(
    nssPKIObject *object)
{
    PRUint32 i;
    PR_ASSERT(object->refCount > 0);
    if (PR_ATOMIC_DECREMENT(&object->refCount) == 0) {
        for (i = 0; i < object->numInstances; i++) {
            nssCryptokiObject_Destroy(object->instances[i]);
        }
        nssPKIObject_DestroyLock(object);
        nssArena_Destroy(object->arena);
        return PR_TRUE;
    }
    return PR_FALSE;
}

NSS_IMPLEMENT nssPKIObject *
nssPKIObject_AddRef(
    nssPKIObject *object)
{
    PR_ATOMIC_INCREMENT(&object->refCount);
    return object;
}

NSS_IMPLEMENT PRStatus
nssPKIObject_AddInstance(
    nssPKIObject *object,
    nssCryptokiObject *instance)
{
    nssCryptokiObject **newInstances = NULL;

    nssPKIObject_Lock(object);
    if (object->numInstances == 0) {
        newInstances = nss_ZNEWARRAY(object->arena,
                                     nssCryptokiObject *,
                                     object->numInstances + 1);
    } else {
        PRBool found = PR_FALSE;
        PRUint32 i;
        for (i = 0; i < object->numInstances; i++) {
            if (nssCryptokiObject_Equal(object->instances[i], instance)) {
                found = PR_TRUE;
                break;
            }
        }
        if (found) {
            /* The new instance is identical to one in the array, except
             * perhaps that the label may be different.  So replace
             * the label in the array instance with the label from the
             * new instance, and discard the new instance.
             */
            nss_ZFreeIf(object->instances[i]->label);
            object->instances[i]->label = instance->label;
            nssPKIObject_Unlock(object);
            instance->label = NULL;
            nssCryptokiObject_Destroy(instance);
            return PR_SUCCESS;
        }
        newInstances = nss_ZREALLOCARRAY(object->instances,
                                         nssCryptokiObject *,
                                         object->numInstances + 1);
    }
    if (newInstances) {
        object->instances = newInstances;
        newInstances[object->numInstances++] = instance;
    }
    nssPKIObject_Unlock(object);
    return (newInstances ? PR_SUCCESS : PR_FAILURE);
}

NSS_IMPLEMENT PRBool
nssPKIObject_HasInstance(
    nssPKIObject *object,
    nssCryptokiObject *instance)
{
    PRUint32 i;
    PRBool hasIt = PR_FALSE;
    ;
    nssPKIObject_Lock(object);
    for (i = 0; i < object->numInstances; i++) {
        if (nssCryptokiObject_Equal(object->instances[i], instance)) {
            hasIt = PR_TRUE;
            break;
        }
    }
    nssPKIObject_Unlock(object);
    return hasIt;
}

NSS_IMPLEMENT PRStatus
nssPKIObject_RemoveInstanceForToken(
    nssPKIObject *object,
    NSSToken *token)
{
    PRUint32 i;
    nssCryptokiObject *instanceToRemove = NULL;
    nssPKIObject_Lock(object);
    if (object->numInstances == 0) {
        nssPKIObject_Unlock(object);
        return PR_SUCCESS;
    }
    for (i = 0; i < object->numInstances; i++) {
        if (object->instances[i]->token == token) {
            instanceToRemove = object->instances[i];
            object->instances[i] = object->instances[object->numInstances - 1];
            object->instances[object->numInstances - 1] = NULL;
            break;
        }
    }
    if (--object->numInstances > 0) {
        nssCryptokiObject **instances = nss_ZREALLOCARRAY(object->instances,
                                                          nssCryptokiObject *,
                                                          object->numInstances);
        if (instances) {
            object->instances = instances;
        }
    } else {
        nss_ZFreeIf(object->instances);
    }
    nssCryptokiObject_Destroy(instanceToRemove);
    nssPKIObject_Unlock(object);
    return PR_SUCCESS;
}

/* this needs more thought on what will happen when there are multiple
 * instances
 */
NSS_IMPLEMENT PRStatus
nssPKIObject_DeleteStoredObject(
    nssPKIObject *object,
    NSSCallback *uhh,
    PRBool isFriendly)
{
    PRUint32 i, numNotDestroyed;
    PRStatus status = PR_SUCCESS;
    numNotDestroyed = 0;
    nssPKIObject_Lock(object);
    for (i = 0; i < object->numInstances; i++) {
        nssCryptokiObject *instance = object->instances[i];
        status = nssToken_DeleteStoredObject(instance);
        object->instances[i] = NULL;
        if (status == PR_SUCCESS) {
            nssCryptokiObject_Destroy(instance);
        } else {
            object->instances[numNotDestroyed++] = instance;
        }
    }
    if (numNotDestroyed == 0) {
        nss_ZFreeIf(object->instances);
        object->numInstances = 0;
    } else {
        object->numInstances = numNotDestroyed;
    }
    nssPKIObject_Unlock(object);
    return status;
}

NSS_IMPLEMENT NSSToken **
nssPKIObject_GetTokens(
    nssPKIObject *object,
    PRStatus *statusOpt)
{
    NSSToken **tokens = NULL;
    nssPKIObject_Lock(object);
    if (object->numInstances > 0) {
        tokens = nss_ZNEWARRAY(NULL, NSSToken *, object->numInstances + 1);
        if (tokens) {
            PRUint32 i;
            for (i = 0; i < object->numInstances; i++) {
                tokens[i] = nssToken_AddRef(object->instances[i]->token);
            }
        }
    }
    nssPKIObject_Unlock(object);
    if (statusOpt)
        *statusOpt = PR_SUCCESS; /* until more logic here */
    return tokens;
}

NSS_IMPLEMENT NSSUTF8 *
nssPKIObject_GetNicknameForToken(
    nssPKIObject *object,
    NSSToken *tokenOpt)
{
    PRUint32 i;
    NSSUTF8 *nickname = NULL;
    nssPKIObject_Lock(object);
    for (i = 0; i < object->numInstances; i++) {
        if ((!tokenOpt && object->instances[i]->label) ||
            (object->instances[i]->token == tokenOpt)) {
            /* Must copy, see bug 745548 */
            nickname = nssUTF8_Duplicate(object->instances[i]->label, NULL);
            break;
        }
    }
    nssPKIObject_Unlock(object);
    return nickname;
}

NSS_IMPLEMENT nssCryptokiObject **
nssPKIObject_GetInstances(
    nssPKIObject *object)
{
    nssCryptokiObject **instances = NULL;
    PRUint32 i;

    nssPKIObject_Lock(object);
    if (object->numInstances == 0) {
        nssPKIObject_Unlock(object);
        return (nssCryptokiObject **)NULL;
    }
    instances = nss_ZNEWARRAY(NULL, nssCryptokiObject *,
                              object->numInstances + 1);
    if (instances) {
        for (i = 0; i < object->numInstances; i++) {
            instances[i] = nssCryptokiObject_Clone(object->instances[i]);
        }
    }
    nssPKIObject_Unlock(object);
    return instances;
}

NSS_IMPLEMENT void
nssCertificateArray_Destroy(
    NSSCertificate **certs)
{
    if (certs) {
        NSSCertificate **certp;
        for (certp = certs; *certp; certp++) {
            if ((*certp)->decoding) {
                CERTCertificate *cc = STAN_GetCERTCertificate(*certp);
                if (cc) {
                    CERT_DestroyCertificate(cc);
                }
                continue;
            }
            nssCertificate_Destroy(*certp);
        }
        nss_ZFreeIf(certs);
    }
}

NSS_IMPLEMENT void
NSSCertificateArray_Destroy(
    NSSCertificate **certs)
{
    nssCertificateArray_Destroy(certs);
}

NSS_IMPLEMENT NSSCertificate **
nssCertificateArray_Join(
    NSSCertificate **certs1,
    NSSCertificate **certs2)
{
    if (certs1 && certs2) {
        NSSCertificate **certs, **cp;
        PRUint32 count = 0;
        PRUint32 count1 = 0;
        cp = certs1;
        while (*cp++)
            count1++;
        count = count1;
        cp = certs2;
        while (*cp++)
            count++;
        certs = nss_ZREALLOCARRAY(certs1, NSSCertificate *, count + 1);
        if (!certs) {
            nss_ZFreeIf(certs1);
            nss_ZFreeIf(certs2);
            return (NSSCertificate **)NULL;
        }
        for (cp = certs2; *cp; cp++, count1++) {
            certs[count1] = *cp;
        }
        nss_ZFreeIf(certs2);
        return certs;
    } else if (certs1) {
        return certs1;
    } else {
        return certs2;
    }
}

NSS_IMPLEMENT NSSCertificate *
nssCertificateArray_FindBestCertificate(
    NSSCertificate **certs,
    NSSTime *timeOpt,
    const NSSUsage *usage,
    NSSPolicies *policiesOpt)
{
    NSSCertificate *bestCert = NULL;
    nssDecodedCert *bestdc = NULL;
    NSSTime *time, sTime;
    PRBool bestCertMatches = PR_FALSE;
    PRBool thisCertMatches;
    PRBool bestCertIsValidAtTime = PR_FALSE;
    PRBool bestCertIsTrusted = PR_FALSE;

    if (timeOpt) {
        time = timeOpt;
    } else {
        NSSTime_Now(&sTime);
        time = &sTime;
    }
    if (!certs) {
        return (NSSCertificate *)NULL;
    }
    for (; *certs; certs++) {
        nssDecodedCert *dc;
        NSSCertificate *c = *certs;
        dc = nssCertificate_GetDecoding(c);
        if (!dc)
            continue;
        thisCertMatches = dc->matchUsage(dc, usage);
        if (!bestCert) {
            /* always take the first cert, but remember whether or not
             * the usage matched
             */
            bestCert = nssCertificate_AddRef(c);
            bestCertMatches = thisCertMatches;
            bestdc = dc;
            continue;
        } else {
            if (bestCertMatches && !thisCertMatches) {
                /* if already have a cert for this usage, and if this cert
                 * doesn't have the correct usage, continue
                 */
                continue;
            } else if (!bestCertMatches && thisCertMatches) {
                /* this one does match usage, replace the other */
                nssCertificate_Destroy(bestCert);
                bestCert = nssCertificate_AddRef(c);
                bestCertMatches = thisCertMatches;
                bestdc = dc;
                continue;
            }
            /* this cert match as well as any cert we've found so far,
             * defer to time/policies
             * */
        }
        /* time */
        if (bestCertIsValidAtTime || bestdc->isValidAtTime(bestdc, time)) {
            /* The current best cert is valid at time */
            bestCertIsValidAtTime = PR_TRUE;
            if (!dc->isValidAtTime(dc, time)) {
                /* If the new cert isn't valid at time, it's not better */
                continue;
            }
        } else {
            /* The current best cert is not valid at time */
            if (dc->isValidAtTime(dc, time)) {
                /* If the new cert is valid at time, it's better */
                nssCertificate_Destroy(bestCert);
                bestCert = nssCertificate_AddRef(c);
                bestdc = dc;
                bestCertIsValidAtTime = PR_TRUE;
                continue;
            }
        }
        /* Either they are both valid at time, or neither valid.
         * If only one is trusted for this usage, take it.
         */
        if (bestCertIsTrusted || bestdc->isTrustedForUsage(bestdc, usage)) {
            bestCertIsTrusted = PR_TRUE;
            if (!dc->isTrustedForUsage(dc, usage)) {
                continue;
            }
        } else {
            /* The current best cert is not trusted */
            if (dc->isTrustedForUsage(dc, usage)) {
                /* If the new cert is trusted, it's better */
                nssCertificate_Destroy(bestCert);
                bestCert = nssCertificate_AddRef(c);
                bestdc = dc;
                bestCertIsTrusted = PR_TRUE;
                continue;
            }
        }
        /* Otherwise, take the newer one. */
        if (!bestdc->isNewerThan(bestdc, dc)) {
            nssCertificate_Destroy(bestCert);
            bestCert = nssCertificate_AddRef(c);
            bestdc = dc;
            continue;
        }
        /* policies */
        /* XXX later -- defer to policies */
    }
    return bestCert;
}

NSS_IMPLEMENT PRStatus
nssCertificateArray_Traverse(
    NSSCertificate **certs,
    PRStatus (*callback)(NSSCertificate *c, void *arg),
    void *arg)
{
    PRStatus status = PR_SUCCESS;
    if (certs) {
        NSSCertificate **certp;
        for (certp = certs; *certp; certp++) {
            status = (*callback)(*certp, arg);
            if (status != PR_SUCCESS) {
                break;
            }
        }
    }
    return status;
}

NSS_IMPLEMENT void
nssCRLArray_Destroy(
    NSSCRL **crls)
{
    if (crls) {
        NSSCRL **crlp;
        for (crlp = crls; *crlp; crlp++) {
            nssCRL_Destroy(*crlp);
        }
        nss_ZFreeIf(crls);
    }
}

/*
 * Object collections
 */

typedef enum {
    pkiObjectType_Certificate = 0,
    pkiObjectType_CRL = 1,
    pkiObjectType_PrivateKey = 2,
    pkiObjectType_PublicKey = 3
} pkiObjectType;

/* Each object is defined by a set of items that uniquely identify it.
 * Here are the uid sets:
 *
 * NSSCertificate ==>  { issuer, serial }
 * NSSPrivateKey
 *         (RSA) ==> { modulus, public exponent }
 *
 */
#define MAX_ITEMS_FOR_UID 2

/* pkiObjectCollectionNode
 *
 * A node in the collection is the set of unique identifiers for a single
 * object, along with either the actual object or a proto-object.
 */
typedef struct
{
    PRCList link;
    PRBool haveObject;
    nssPKIObject *object;
    NSSItem uid[MAX_ITEMS_FOR_UID];
} pkiObjectCollectionNode;

/* nssPKIObjectCollection
 *
 * The collection is the set of all objects, plus the interfaces needed
 * to manage the objects.
 *
 */
struct nssPKIObjectCollectionStr {
    NSSArena *arena;
    NSSTrustDomain *td;
    NSSCryptoContext *cc;
    PRCList head; /* list of pkiObjectCollectionNode's */
    PRUint32 size;
    pkiObjectType objectType;
    void (*destroyObject)(nssPKIObject *o);
    PRStatus (*getUIDFromObject)(nssPKIObject *o, NSSItem *uid);
    PRStatus (*getUIDFromInstance)(nssCryptokiObject *co, NSSItem *uid,
                                   NSSArena *arena);
    nssPKIObject *(*createObject)(nssPKIObject *o);
    nssPKILockType lockType; /* type of lock to use for new proto-objects */
};

static nssPKIObjectCollection *
nssPKIObjectCollection_Create(
    NSSTrustDomain *td,
    NSSCryptoContext *ccOpt,
    nssPKILockType lockType)
{
    NSSArena *arena;
    nssPKIObjectCollection *rvCollection = NULL;
    arena = nssArena_Create();
    if (!arena) {
        return (nssPKIObjectCollection *)NULL;
    }
    rvCollection = nss_ZNEW(arena, nssPKIObjectCollection);
    if (!rvCollection) {
        goto loser;
    }
    PR_INIT_CLIST(&rvCollection->head);
    rvCollection->arena = arena;
    rvCollection->td = td; /* XXX */
    rvCollection->cc = ccOpt;
    rvCollection->lockType = lockType;
    return rvCollection;
loser:
    nssArena_Destroy(arena);
    return (nssPKIObjectCollection *)NULL;
}

NSS_IMPLEMENT void
nssPKIObjectCollection_Destroy(
    nssPKIObjectCollection *collection)
{
    if (collection) {
        PRCList *link;
        pkiObjectCollectionNode *node;
        /* first destroy any objects in the collection */
        link = PR_NEXT_LINK(&collection->head);
        while (link != &collection->head) {
            node = (pkiObjectCollectionNode *)link;
            if (node->haveObject) {
                (*collection->destroyObject)(node->object);
            } else {
                nssPKIObject_Destroy(node->object);
            }
            link = PR_NEXT_LINK(link);
        }
        /* then destroy it */
        nssArena_Destroy(collection->arena);
    }
}

NSS_IMPLEMENT PRUint32
nssPKIObjectCollection_Count(
    nssPKIObjectCollection *collection)
{
    return collection->size;
}

NSS_IMPLEMENT PRStatus
nssPKIObjectCollection_AddObject(
    nssPKIObjectCollection *collection,
    nssPKIObject *object)
{
    pkiObjectCollectionNode *node;
    node = nss_ZNEW(collection->arena, pkiObjectCollectionNode);
    if (!node) {
        return PR_FAILURE;
    }
    node->haveObject = PR_TRUE;
    node->object = nssPKIObject_AddRef(object);
    (*collection->getUIDFromObject)(object, node->uid);
    PR_INIT_CLIST(&node->link);
    PR_INSERT_BEFORE(&node->link, &collection->head);
    collection->size++;
    return PR_SUCCESS;
}

static pkiObjectCollectionNode *
find_instance_in_collection(
    nssPKIObjectCollection *collection,
    nssCryptokiObject *instance)
{
    PRCList *link;
    pkiObjectCollectionNode *node;
    link = PR_NEXT_LINK(&collection->head);
    while (link != &collection->head) {
        node = (pkiObjectCollectionNode *)link;
        if (nssPKIObject_HasInstance(node->object, instance)) {
            return node;
        }
        link = PR_NEXT_LINK(link);
    }
    return (pkiObjectCollectionNode *)NULL;
}

static pkiObjectCollectionNode *
find_object_in_collection(
    nssPKIObjectCollection *collection,
    NSSItem *uid)
{
    PRUint32 i;
    PRStatus status;
    PRCList *link;
    pkiObjectCollectionNode *node;
    link = PR_NEXT_LINK(&collection->head);
    while (link != &collection->head) {
        node = (pkiObjectCollectionNode *)link;
        for (i = 0; i < MAX_ITEMS_FOR_UID; i++) {
            if (!nssItem_Equal(&node->uid[i], &uid[i], &status)) {
                break;
            }
        }
        if (i == MAX_ITEMS_FOR_UID) {
            return node;
        }
        link = PR_NEXT_LINK(link);
    }
    return (pkiObjectCollectionNode *)NULL;
}

static pkiObjectCollectionNode *
add_object_instance(
    nssPKIObjectCollection *collection,
    nssCryptokiObject *instance,
    PRBool *foundIt)
{
    PRUint32 i;
    PRStatus status;
    pkiObjectCollectionNode *node;
    nssArenaMark *mark = NULL;
    NSSItem uid[MAX_ITEMS_FOR_UID];
    nsslibc_memset(uid, 0, sizeof uid);
    /* The list is traversed twice, first (here) looking to match the
     * { token, handle } tuple, and if that is not found, below a search
     * for unique identifier is done.  Here, a match means this exact object
     * instance is already in the collection, and we have nothing to do.
     */
    *foundIt = PR_FALSE;
    node = find_instance_in_collection(collection, instance);
    if (node) {
        /* The collection is assumed to take over the instance.  Since we
         * are not using it, it must be destroyed.
         */
        nssCryptokiObject_Destroy(instance);
        *foundIt = PR_TRUE;
        return node;
    }
    mark = nssArena_Mark(collection->arena);
    if (!mark) {
        goto loser;
    }
    status = (*collection->getUIDFromInstance)(instance, uid,
                                               collection->arena);
    if (status != PR_SUCCESS) {
        goto loser;
    }
    /* Search for unique identifier.  A match here means the object exists
     * in the collection, but does not have this instance, so the instance
     * needs to be added.
     */
    node = find_object_in_collection(collection, uid);
    if (node) {
        /* This is an object with multiple instances */
        status = nssPKIObject_AddInstance(node->object, instance);
    } else {
        /* This is a completely new object.  Create a node for it. */
        node = nss_ZNEW(collection->arena, pkiObjectCollectionNode);
        if (!node) {
            goto loser;
        }
        node->object = nssPKIObject_Create(NULL, instance,
                                           collection->td, collection->cc,
                                           collection->lockType);
        if (!node->object) {
            goto loser;
        }
        for (i = 0; i < MAX_ITEMS_FOR_UID; i++) {
            node->uid[i] = uid[i];
        }
        node->haveObject = PR_FALSE;
        PR_INIT_CLIST(&node->link);
        PR_INSERT_BEFORE(&node->link, &collection->head);
        collection->size++;
        status = PR_SUCCESS;
    }
    nssArena_Unmark(collection->arena, mark);
    return node;
loser:
    if (mark) {
        nssArena_Release(collection->arena, mark);
    }
    nssCryptokiObject_Destroy(instance);
    return (pkiObjectCollectionNode *)NULL;
}

NSS_IMPLEMENT PRStatus
nssPKIObjectCollection_AddInstances(
    nssPKIObjectCollection *collection,
    nssCryptokiObject **instances,
    PRUint32 numInstances)
{
    PRStatus status = PR_SUCCESS;
    PRUint32 i = 0;
    PRBool foundIt;
    pkiObjectCollectionNode *node;
    if (instances) {
        while ((!numInstances || i < numInstances) && *instances) {
            if (status == PR_SUCCESS) {
                node = add_object_instance(collection, *instances, &foundIt);
                if (node == NULL) {
                    /* add_object_instance freed the current instance */
                    /* free the remaining instances */
                    status = PR_FAILURE;
                }
            } else {
                nssCryptokiObject_Destroy(*instances);
            }
            instances++;
            i++;
        }
    }
    return status;
}

static void
nssPKIObjectCollection_RemoveNode(
    nssPKIObjectCollection *collection,
    pkiObjectCollectionNode *node)
{
    PR_REMOVE_LINK(&node->link);
    collection->size--;
}

static PRStatus
nssPKIObjectCollection_GetObjects(
    nssPKIObjectCollection *collection,
    nssPKIObject **rvObjects,
    PRUint32 rvSize)
{
    PRUint32 i = 0;
    PRCList *link = PR_NEXT_LINK(&collection->head);
    pkiObjectCollectionNode *node;
    int error = 0;
    while ((i < rvSize) && (link != &collection->head)) {
        node = (pkiObjectCollectionNode *)link;
        if (!node->haveObject) {
            /* Convert the proto-object to an object */
            node->object = (*collection->createObject)(node->object);
            if (!node->object) {
                link = PR_NEXT_LINK(link);
                /*remove bogus object from list*/
                nssPKIObjectCollection_RemoveNode(collection, node);
                error++;
                continue;
            }
            node->haveObject = PR_TRUE;
        }
        rvObjects[i++] = nssPKIObject_AddRef(node->object);
        link = PR_NEXT_LINK(link);
    }
    if (!error && *rvObjects == NULL) {
        nss_SetError(NSS_ERROR_NOT_FOUND);
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssPKIObjectCollection_Traverse(
    nssPKIObjectCollection *collection,
    nssPKIObjectCallback *callback)
{
    PRCList *link = PR_NEXT_LINK(&collection->head);
    pkiObjectCollectionNode *node;
    while (link != &collection->head) {
        node = (pkiObjectCollectionNode *)link;
        if (!node->haveObject) {
            node->object = (*collection->createObject)(node->object);
            if (!node->object) {
                link = PR_NEXT_LINK(link);
                /*remove bogus object from list*/
                nssPKIObjectCollection_RemoveNode(collection, node);
                continue;
            }
            node->haveObject = PR_TRUE;
        }
        switch (collection->objectType) {
            case pkiObjectType_Certificate:
                (void)(*callback->func.cert)((NSSCertificate *)node->object,
                                             callback->arg);
                break;
            case pkiObjectType_CRL:
                (void)(*callback->func.crl)((NSSCRL *)node->object,
                                            callback->arg);
                break;
            case pkiObjectType_PrivateKey:
                (void)(*callback->func.pvkey)((NSSPrivateKey *)node->object,
                                              callback->arg);
                break;
            case pkiObjectType_PublicKey:
                (void)(*callback->func.pbkey)((NSSPublicKey *)node->object,
                                              callback->arg);
                break;
        }
        link = PR_NEXT_LINK(link);
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssPKIObjectCollection_AddInstanceAsObject(
    nssPKIObjectCollection *collection,
    nssCryptokiObject *instance)
{
    pkiObjectCollectionNode *node;
    PRBool foundIt;
    node = add_object_instance(collection, instance, &foundIt);
    if (node == NULL) {
        return PR_FAILURE;
    }
    if (!node->haveObject) {
        nssPKIObject *original = node->object;
        node->object = (*collection->createObject)(node->object);
        if (!node->object) {
            /*remove bogus object from list*/
            nssPKIObject_Destroy(original);
            nssPKIObjectCollection_RemoveNode(collection, node);
            return PR_FAILURE;
        }
        node->haveObject = PR_TRUE;
    } else if (!foundIt) {
        /* The instance was added to a pre-existing node.  This
         * function is *only* being used for certificates, and having
         * multiple instances of certs in 3.X requires updating the
         * CERTCertificate.
         * But only do it if it was a new instance!!!  If the same instance
         * is encountered, we set *foundIt to true.  Detect that here and
         * ignore it.
         */
        STAN_ForceCERTCertificateUpdate((NSSCertificate *)node->object);
    }
    return PR_SUCCESS;
}

/*
 * Certificate collections
 */

static void
cert_destroyObject(nssPKIObject *o)
{
    NSSCertificate *c = (NSSCertificate *)o;
    if (c->decoding) {
        CERTCertificate *cc = STAN_GetCERTCertificate(c);
        if (cc) {
            CERT_DestroyCertificate(cc);
            return;
        } /* else destroy it as NSSCertificate below */
    }
    nssCertificate_Destroy(c);
}

static PRStatus
cert_getUIDFromObject(nssPKIObject *o, NSSItem *uid)
{
    NSSCertificate *c = (NSSCertificate *)o;
    /* The builtins are still returning decoded serial numbers.  Until
     * this compatibility issue is resolved, use the full DER of the
     * cert to uniquely identify it.
     */
    NSSDER *derCert;
    derCert = nssCertificate_GetEncoding(c);
    uid[0].data = NULL;
    uid[0].size = 0;
    uid[1].data = NULL;
    uid[1].size = 0;
    if (derCert != NULL) {
        uid[0] = *derCert;
    }
    return PR_SUCCESS;
}

static PRStatus
cert_getUIDFromInstance(nssCryptokiObject *instance, NSSItem *uid,
                        NSSArena *arena)
{
    /* The builtins are still returning decoded serial numbers.  Until
     * this compatibility issue is resolved, use the full DER of the
     * cert to uniquely identify it.
     */
    uid[1].data = NULL;
    uid[1].size = 0;
    return nssCryptokiCertificate_GetAttributes(instance,
                                                NULL,    /* XXX sessionOpt */
                                                arena,   /* arena    */
                                                NULL,    /* type     */
                                                NULL,    /* id       */
                                                &uid[0], /* encoding */
                                                NULL,    /* issuer   */
                                                NULL,    /* serial   */
                                                NULL);   /* subject  */
}

static nssPKIObject *
cert_createObject(nssPKIObject *o)
{
    NSSCertificate *cert = nssCertificate_Create(o);
    if (!cert) {
        return NULL;
    }
    NSSTrustDomain *td = o->trustDomain;
    /* nssTrustDomain_AddCertToCache takes ownership of the reference to cert,
     * so first increase the refcount so it doesn't go away. */
    nssCertificate_AddRef(cert);
    NSSCertificate *certInCache = nssTrustDomain_AddCertToCache(td, cert);
    if (certInCache) {
        // This code should probably use the certificate returned from the cache,
        // but currently the merge tests rely on not doing so. Discard it
        // instead.
        nssCertificate_Destroy(certInCache);
    }
    return (nssPKIObject *)cert;
}

NSS_IMPLEMENT nssPKIObjectCollection *
nssCertificateCollection_Create(
    NSSTrustDomain *td,
    NSSCertificate **certsOpt)
{
    nssPKIObjectCollection *collection;
    collection = nssPKIObjectCollection_Create(td, NULL, nssPKIMonitor);
    if (!collection) {
        return NULL;
    }
    collection->objectType = pkiObjectType_Certificate;
    collection->destroyObject = cert_destroyObject;
    collection->getUIDFromObject = cert_getUIDFromObject;
    collection->getUIDFromInstance = cert_getUIDFromInstance;
    collection->createObject = cert_createObject;
    if (certsOpt) {
        for (; *certsOpt; certsOpt++) {
            nssPKIObject *object = (nssPKIObject *)(*certsOpt);
            (void)nssPKIObjectCollection_AddObject(collection, object);
        }
    }
    return collection;
}

NSS_IMPLEMENT NSSCertificate **
nssPKIObjectCollection_GetCertificates(
    nssPKIObjectCollection *collection,
    NSSCertificate **rvOpt,
    PRUint32 maximumOpt,
    NSSArena *arenaOpt)
{
    PRStatus status;
    PRUint32 rvSize;
    PRBool allocated = PR_FALSE;
    if (collection->size == 0) {
        return (NSSCertificate **)NULL;
    }
    if (maximumOpt == 0) {
        rvSize = collection->size;
    } else {
        rvSize = PR_MIN(collection->size, maximumOpt);
    }
    if (!rvOpt) {
        rvOpt = nss_ZNEWARRAY(arenaOpt, NSSCertificate *, rvSize + 1);
        if (!rvOpt) {
            return (NSSCertificate **)NULL;
        }
        allocated = PR_TRUE;
    }
    status = nssPKIObjectCollection_GetObjects(collection,
                                               (nssPKIObject **)rvOpt,
                                               rvSize);
    if (status != PR_SUCCESS) {
        if (allocated) {
            nss_ZFreeIf(rvOpt);
        }
        return (NSSCertificate **)NULL;
    }
    return rvOpt;
}

/*
 * CRL/KRL collections
 */

static void
crl_destroyObject(nssPKIObject *o)
{
    NSSCRL *crl = (NSSCRL *)o;
    nssCRL_Destroy(crl);
}

static PRStatus
crl_getUIDFromObject(nssPKIObject *o, NSSItem *uid)
{
    NSSCRL *crl = (NSSCRL *)o;
    NSSDER *encoding;
    encoding = nssCRL_GetEncoding(crl);
    if (!encoding) {
        nss_SetError(NSS_ERROR_INVALID_ARGUMENT);
        return PR_FALSE;
    }
    uid[0] = *encoding;
    uid[1].data = NULL;
    uid[1].size = 0;
    return PR_SUCCESS;
}

static PRStatus
crl_getUIDFromInstance(nssCryptokiObject *instance, NSSItem *uid,
                       NSSArena *arena)
{
    return nssCryptokiCRL_GetAttributes(instance,
                                        NULL,    /* XXX sessionOpt */
                                        arena,   /* arena    */
                                        &uid[0], /* encoding */
                                        NULL,    /* subject  */
                                        NULL,    /* class    */
                                        NULL,    /* url      */
                                        NULL);   /* isKRL    */
}

static nssPKIObject *
crl_createObject(nssPKIObject *o)
{
    return (nssPKIObject *)nssCRL_Create(o);
}

NSS_IMPLEMENT nssPKIObjectCollection *
nssCRLCollection_Create(
    NSSTrustDomain *td,
    NSSCRL **crlsOpt)
{
    nssPKIObjectCollection *collection;
    collection = nssPKIObjectCollection_Create(td, NULL, nssPKILock);
    if (!collection) {
        return NULL;
    }
    collection->objectType = pkiObjectType_CRL;
    collection->destroyObject = crl_destroyObject;
    collection->getUIDFromObject = crl_getUIDFromObject;
    collection->getUIDFromInstance = crl_getUIDFromInstance;
    collection->createObject = crl_createObject;
    if (crlsOpt) {
        for (; *crlsOpt; crlsOpt++) {
            nssPKIObject *object = (nssPKIObject *)(*crlsOpt);
            (void)nssPKIObjectCollection_AddObject(collection, object);
        }
    }
    return collection;
}

NSS_IMPLEMENT NSSCRL **
nssPKIObjectCollection_GetCRLs(
    nssPKIObjectCollection *collection,
    NSSCRL **rvOpt,
    PRUint32 maximumOpt,
    NSSArena *arenaOpt)
{
    PRStatus status;
    PRUint32 rvSize;
    PRBool allocated = PR_FALSE;
    if (collection->size == 0) {
        return (NSSCRL **)NULL;
    }
    if (maximumOpt == 0) {
        rvSize = collection->size;
    } else {
        rvSize = PR_MIN(collection->size, maximumOpt);
    }
    if (!rvOpt) {
        rvOpt = nss_ZNEWARRAY(arenaOpt, NSSCRL *, rvSize + 1);
        if (!rvOpt) {
            return (NSSCRL **)NULL;
        }
        allocated = PR_TRUE;
    }
    status = nssPKIObjectCollection_GetObjects(collection,
                                               (nssPKIObject **)rvOpt,
                                               rvSize);
    if (status != PR_SUCCESS) {
        if (allocated) {
            nss_ZFreeIf(rvOpt);
        }
        return (NSSCRL **)NULL;
    }
    return rvOpt;
}

/* how bad would it be to have a static now sitting around, updated whenever
 * this was called?  would avoid repeated allocs...
 */
NSS_IMPLEMENT NSSTime *
NSSTime_Now(NSSTime *timeOpt)
{
    return NSSTime_SetPRTime(timeOpt, PR_Now());
}

NSS_IMPLEMENT NSSTime *
NSSTime_SetPRTime(
    NSSTime *timeOpt,
    PRTime prTime)
{
    NSSTime *rvTime;
    rvTime = (timeOpt) ? timeOpt : nss_ZNEW(NULL, NSSTime);
    if (rvTime) {
        rvTime->prTime = prTime;
    }
    return rvTime;
}

NSS_IMPLEMENT PRTime
NSSTime_GetPRTime(
    NSSTime *time)
{
    return time->prTime;
}
