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

/*
 * find.c
 *
 * This file implements the nssCKFWFindObjects type and methods.
 */

#ifndef CK_H
#include "ck.h"
#endif /* CK_H */

/*
 * NSSCKFWFindObjects
 *
 *  -- create/destroy --
 *  nssCKFWFindObjects_Create
 *  nssCKFWFindObjects_Destroy
 *
 *  -- public accessors --
 *  NSSCKFWFindObjects_GetMDFindObjects
 *
 *  -- implement public accessors --
 *  nssCKFWFindObjects_GetMDFindObjects
 *
 *  -- private accessors --
 *
 *  -- module fronts --
 *  nssCKFWFindObjects_Next
 */

struct NSSCKFWFindObjectsStr {
    NSSCKFWMutex *mutex; /* merely to serialise the MDObject calls */
    NSSCKMDFindObjects *mdfo1;
    NSSCKMDFindObjects *mdfo2;
    NSSCKFWSession *fwSession;
    NSSCKMDSession *mdSession;
    NSSCKFWToken *fwToken;
    NSSCKMDToken *mdToken;
    NSSCKFWInstance *fwInstance;
    NSSCKMDInstance *mdInstance;

    NSSCKMDFindObjects *mdFindObjects; /* varies */
};

#ifdef DEBUG
/*
 * But first, the pointer-tracking stuff.
 *
 * NOTE: the pointer-tracking support in NSS/base currently relies
 * upon NSPR's CallOnce support.  That, however, relies upon NSPR's
 * locking, which is tied into the runtime.  We need a pointer-tracker
 * implementation that uses the locks supplied through C_Initialize.
 * That support, however, can be filled in later.  So for now, I'll
 * just do these routines as no-ops.
 */

static CK_RV
findObjects_add_pointer(
    const NSSCKFWFindObjects *fwFindObjects)
{
    return CKR_OK;
}

static CK_RV
findObjects_remove_pointer(
    const NSSCKFWFindObjects *fwFindObjects)
{
    return CKR_OK;
}

NSS_IMPLEMENT CK_RV
nssCKFWFindObjects_verifyPointer(
    const NSSCKFWFindObjects *fwFindObjects)
{
    return CKR_OK;
}

#endif /* DEBUG */

/*
 * nssCKFWFindObjects_Create
 *
 */
NSS_EXTERN NSSCKFWFindObjects *
nssCKFWFindObjects_Create(
    NSSCKFWSession *fwSession,
    NSSCKFWToken *fwToken,
    NSSCKFWInstance *fwInstance,
    NSSCKMDFindObjects *mdFindObjects1,
    NSSCKMDFindObjects *mdFindObjects2,
    CK_RV *pError)
{
    NSSCKFWFindObjects *fwFindObjects = NULL;
    NSSCKMDSession *mdSession;
    NSSCKMDToken *mdToken;
    NSSCKMDInstance *mdInstance;

    mdSession = nssCKFWSession_GetMDSession(fwSession);
    mdToken = nssCKFWToken_GetMDToken(fwToken);
    mdInstance = nssCKFWInstance_GetMDInstance(fwInstance);

    fwFindObjects = nss_ZNEW(NULL, NSSCKFWFindObjects);
    if (!fwFindObjects) {
        *pError = CKR_HOST_MEMORY;
        goto loser;
    }

    fwFindObjects->mdfo1 = mdFindObjects1;
    fwFindObjects->mdfo2 = mdFindObjects2;
    fwFindObjects->fwSession = fwSession;
    fwFindObjects->mdSession = mdSession;
    fwFindObjects->fwToken = fwToken;
    fwFindObjects->mdToken = mdToken;
    fwFindObjects->fwInstance = fwInstance;
    fwFindObjects->mdInstance = mdInstance;

    fwFindObjects->mutex = nssCKFWInstance_CreateMutex(fwInstance, NULL, pError);
    if (!fwFindObjects->mutex) {
        goto loser;
    }

#ifdef DEBUG
    *pError = findObjects_add_pointer(fwFindObjects);
    if (CKR_OK != *pError) {
        goto loser;
    }
#endif /* DEBUG */

    return fwFindObjects;

loser:
    if (fwFindObjects) {
        if (NULL != mdFindObjects1) {
            if (NULL != mdFindObjects1->Final) {
                fwFindObjects->mdFindObjects = mdFindObjects1;
                mdFindObjects1->Final(mdFindObjects1, fwFindObjects, mdSession,
                                      fwSession, mdToken, fwToken, mdInstance, fwInstance);
            }
        }

        if (NULL != mdFindObjects2) {
            if (NULL != mdFindObjects2->Final) {
                fwFindObjects->mdFindObjects = mdFindObjects2;
                mdFindObjects2->Final(mdFindObjects2, fwFindObjects, mdSession,
                                      fwSession, mdToken, fwToken, mdInstance, fwInstance);
            }
        }

        nss_ZFreeIf(fwFindObjects);
    }

    if (CKR_OK == *pError) {
        *pError = CKR_GENERAL_ERROR;
    }

    return (NSSCKFWFindObjects *)NULL;
}

/*
 * nssCKFWFindObjects_Destroy
 *
 */
NSS_EXTERN void
nssCKFWFindObjects_Destroy(
    NSSCKFWFindObjects *fwFindObjects)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWFindObjects_verifyPointer(fwFindObjects)) {
        return;
    }
#endif /* NSSDEBUG */

    (void)nssCKFWMutex_Destroy(fwFindObjects->mutex);

    if (fwFindObjects->mdfo1) {
        if (fwFindObjects->mdfo1->Final) {
            fwFindObjects->mdFindObjects = fwFindObjects->mdfo1;
            fwFindObjects->mdfo1->Final(fwFindObjects->mdfo1, fwFindObjects,
                                        fwFindObjects->mdSession, fwFindObjects->fwSession,
                                        fwFindObjects->mdToken, fwFindObjects->fwToken,
                                        fwFindObjects->mdInstance, fwFindObjects->fwInstance);
        }
    }

    if (fwFindObjects->mdfo2) {
        if (fwFindObjects->mdfo2->Final) {
            fwFindObjects->mdFindObjects = fwFindObjects->mdfo2;
            fwFindObjects->mdfo2->Final(fwFindObjects->mdfo2, fwFindObjects,
                                        fwFindObjects->mdSession, fwFindObjects->fwSession,
                                        fwFindObjects->mdToken, fwFindObjects->fwToken,
                                        fwFindObjects->mdInstance, fwFindObjects->fwInstance);
        }
    }

    nss_ZFreeIf(fwFindObjects);

#ifdef DEBUG
    (void)findObjects_remove_pointer(fwFindObjects);
#endif /* DEBUG */

    return;
}

/*
 * nssCKFWFindObjects_GetMDFindObjects
 *
 */
NSS_EXTERN NSSCKMDFindObjects *
nssCKFWFindObjects_GetMDFindObjects(
    NSSCKFWFindObjects *fwFindObjects)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWFindObjects_verifyPointer(fwFindObjects)) {
        return (NSSCKMDFindObjects *)NULL;
    }
#endif /* NSSDEBUG */

    return fwFindObjects->mdFindObjects;
}

/*
 * nssCKFWFindObjects_Next
 *
 */
NSS_EXTERN NSSCKFWObject *
nssCKFWFindObjects_Next(
    NSSCKFWFindObjects *fwFindObjects,
    NSSArena *arenaOpt,
    CK_RV *pError)
{
    NSSCKMDObject *mdObject;
    NSSCKFWObject *fwObject = (NSSCKFWObject *)NULL;
    NSSArena *objArena;

#ifdef NSSDEBUG
    if (!pError) {
        return (NSSCKFWObject *)NULL;
    }

    *pError = nssCKFWFindObjects_verifyPointer(fwFindObjects);
    if (CKR_OK != *pError) {
        return (NSSCKFWObject *)NULL;
    }
#endif /* NSSDEBUG */

    *pError = nssCKFWMutex_Lock(fwFindObjects->mutex);
    if (CKR_OK != *pError) {
        return (NSSCKFWObject *)NULL;
    }

    if (fwFindObjects->mdfo1) {
        if (fwFindObjects->mdfo1->Next) {
            fwFindObjects->mdFindObjects = fwFindObjects->mdfo1;
            mdObject = fwFindObjects->mdfo1->Next(fwFindObjects->mdfo1,
                                                  fwFindObjects, fwFindObjects->mdSession, fwFindObjects->fwSession,
                                                  fwFindObjects->mdToken, fwFindObjects->fwToken,
                                                  fwFindObjects->mdInstance, fwFindObjects->fwInstance,
                                                  arenaOpt, pError);
            if (!mdObject) {
                if (CKR_OK != *pError) {
                    goto done;
                }

                /* All done. */
                fwFindObjects->mdfo1->Final(fwFindObjects->mdfo1, fwFindObjects,
                                            fwFindObjects->mdSession, fwFindObjects->fwSession,
                                            fwFindObjects->mdToken, fwFindObjects->fwToken,
                                            fwFindObjects->mdInstance, fwFindObjects->fwInstance);
                fwFindObjects->mdfo1 = (NSSCKMDFindObjects *)NULL;
            } else {
                goto wrap;
            }
        }
    }

    if (fwFindObjects->mdfo2) {
        if (fwFindObjects->mdfo2->Next) {
            fwFindObjects->mdFindObjects = fwFindObjects->mdfo2;
            mdObject = fwFindObjects->mdfo2->Next(fwFindObjects->mdfo2,
                                                  fwFindObjects, fwFindObjects->mdSession, fwFindObjects->fwSession,
                                                  fwFindObjects->mdToken, fwFindObjects->fwToken,
                                                  fwFindObjects->mdInstance, fwFindObjects->fwInstance,
                                                  arenaOpt, pError);
            if (!mdObject) {
                if (CKR_OK != *pError) {
                    goto done;
                }

                /* All done. */
                fwFindObjects->mdfo2->Final(fwFindObjects->mdfo2, fwFindObjects,
                                            fwFindObjects->mdSession, fwFindObjects->fwSession,
                                            fwFindObjects->mdToken, fwFindObjects->fwToken,
                                            fwFindObjects->mdInstance, fwFindObjects->fwInstance);
                fwFindObjects->mdfo2 = (NSSCKMDFindObjects *)NULL;
            } else {
                goto wrap;
            }
        }
    }

    /* No more objects */
    *pError = CKR_OK;
    goto done;

wrap:
    /*
     * This seems is less than ideal-- we should determine if it's a token
     * object or a session object, and use the appropriate arena.
     * But that duplicates logic in nssCKFWObject_IsTokenObject.
     * Also we should lookup the real session the object was created on
     * if the object was a session object... however this code is actually
     * correct because nssCKFWObject_Create will return a cached version of
     * the object from it's hash. This is necessary because 1) we don't want
     * to create an arena style leak (where our arena grows with every search),
     * and 2) we want the same object to always have the same ID. This means
     * the only case the nssCKFWObject_Create() will need the objArena and the
     * Session is in the case of token objects (session objects should already
     * exist in the cache from their initial creation). So this code is correct,
     * but it depends on nssCKFWObject_Create caching all objects.
     */
    objArena = nssCKFWToken_GetArena(fwFindObjects->fwToken, pError);
    if (!objArena) {
        if (CKR_OK == *pError) {
            *pError = CKR_HOST_MEMORY;
        }
        goto done;
    }

    fwObject = nssCKFWObject_Create(objArena, mdObject,
                                    NULL, fwFindObjects->fwToken,
                                    fwFindObjects->fwInstance, pError);
    if (!fwObject) {
        if (CKR_OK == *pError) {
            *pError = CKR_GENERAL_ERROR;
        }
    }

done:
    (void)nssCKFWMutex_Unlock(fwFindObjects->mutex);
    return fwObject;
}

/*
 * NSSCKFWFindObjects_GetMDFindObjects
 *
 */

NSS_EXTERN NSSCKMDFindObjects *
NSSCKFWFindObjects_GetMDFindObjects(
    NSSCKFWFindObjects *fwFindObjects)
{
#ifdef DEBUG
    if (CKR_OK != nssCKFWFindObjects_verifyPointer(fwFindObjects)) {
        return (NSSCKMDFindObjects *)NULL;
    }
#endif /* DEBUG */

    return nssCKFWFindObjects_GetMDFindObjects(fwFindObjects);
}
