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

/*
 * session.c
 *
 * This file implements the NSSCKFWSession type and methods.
 */

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

/*
 * NSSCKFWSession
 *
 *  -- create/destroy --
 *  nssCKFWSession_Create
 *  nssCKFWSession_Destroy
 *
 *  -- public accessors --
 *  NSSCKFWSession_GetMDSession
 *  NSSCKFWSession_GetArena
 *  NSSCKFWSession_CallNotification
 *  NSSCKFWSession_IsRWSession
 *  NSSCKFWSession_IsSO
 *  NSSCKFWSession_GetFWSlot
 *
 *  -- implement public accessors --
 *  nssCKFWSession_GetMDSession
 *  nssCKFWSession_GetArena
 *  nssCKFWSession_CallNotification
 *  nssCKFWSession_IsRWSession
 *  nssCKFWSession_IsSO
 *  nssCKFWSession_GetFWSlot
 *
 *  -- private accessors --
 *  nssCKFWSession_GetSessionState
 *  nssCKFWSession_SetFWFindObjects
 *  nssCKFWSession_GetFWFindObjects
 *  nssCKFWSession_SetMDSession
 *  nssCKFWSession_SetHandle
 *  nssCKFWSession_GetHandle
 *  nssCKFWSession_RegisterSessionObject
 *  nssCKFWSession_DeegisterSessionObject
 *
 *  -- module fronts --
 *  nssCKFWSession_GetDeviceError
 *  nssCKFWSession_Login
 *  nssCKFWSession_Logout
 *  nssCKFWSession_InitPIN
 *  nssCKFWSession_SetPIN
 *  nssCKFWSession_GetOperationStateLen
 *  nssCKFWSession_GetOperationState
 *  nssCKFWSession_SetOperationState
 *  nssCKFWSession_CreateObject
 *  nssCKFWSession_CopyObject
 *  nssCKFWSession_FindObjectsInit
 *  nssCKFWSession_SeedRandom
 *  nssCKFWSession_GetRandom
 */

struct NSSCKFWSessionStr {
    NSSArena *arena;
    NSSCKMDSession *mdSession;
    NSSCKFWToken *fwToken;
    NSSCKMDToken *mdToken;
    NSSCKFWInstance *fwInstance;
    NSSCKMDInstance *mdInstance;
    CK_VOID_PTR pApplication;
    CK_NOTIFY Notify;

    /*
     * Everything above is set at creation time, and then not modified.
     * The items below are atomic.  No locking required.  If we fear
     * about pointer-copies being nonatomic, we'll lock fwFindObjects.
     */

    CK_BBOOL rw;
    NSSCKFWFindObjects *fwFindObjects;
    NSSCKFWCryptoOperation *fwOperationArray[NSSCKFWCryptoOperationState_Max];
    nssCKFWHash *sessionObjectHash;
    CK_SESSION_HANDLE hSession;
};

#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 this routines as no-ops.
 */

static CK_RV
session_add_pointer(
    const NSSCKFWSession *fwSession)
{
    return CKR_OK;
}

static CK_RV
session_remove_pointer(
    const NSSCKFWSession *fwSession)
{
    return CKR_OK;
}

NSS_IMPLEMENT CK_RV
nssCKFWSession_verifyPointer(
    const NSSCKFWSession *fwSession)
{
    return CKR_OK;
}

#endif /* DEBUG */

/*
 * nssCKFWSession_Create
 *
 */
NSS_IMPLEMENT NSSCKFWSession *
nssCKFWSession_Create(
    NSSCKFWToken *fwToken,
    CK_BBOOL rw,
    CK_VOID_PTR pApplication,
    CK_NOTIFY Notify,
    CK_RV *pError)
{
    NSSArena *arena = (NSSArena *)NULL;
    NSSCKFWSession *fwSession;
    NSSCKFWSlot *fwSlot;

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

    *pError = nssCKFWToken_verifyPointer(fwToken);
    if (CKR_OK != *pError) {
        return (NSSCKFWSession *)NULL;
    }
#endif /* NSSDEBUG */

    arena = NSSArena_Create();
    if (!arena) {
        *pError = CKR_HOST_MEMORY;
        return (NSSCKFWSession *)NULL;
    }

    fwSession = nss_ZNEW(arena, NSSCKFWSession);
    if (!fwSession) {
        *pError = CKR_HOST_MEMORY;
        goto loser;
    }

    fwSession->arena = arena;
    fwSession->mdSession = (NSSCKMDSession *)NULL; /* set later */
    fwSession->fwToken = fwToken;
    fwSession->mdToken = nssCKFWToken_GetMDToken(fwToken);

    fwSlot = nssCKFWToken_GetFWSlot(fwToken);
    fwSession->fwInstance = nssCKFWSlot_GetFWInstance(fwSlot);
    fwSession->mdInstance = nssCKFWSlot_GetMDInstance(fwSlot);

    fwSession->rw = rw;
    fwSession->pApplication = pApplication;
    fwSession->Notify = Notify;

    fwSession->fwFindObjects = (NSSCKFWFindObjects *)NULL;

    fwSession->sessionObjectHash = nssCKFWHash_Create(fwSession->fwInstance, arena, pError);
    if (!fwSession->sessionObjectHash) {
        if (CKR_OK == *pError) {
            *pError = CKR_GENERAL_ERROR;
        }
        goto loser;
    }

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

    return fwSession;

loser:
    if (arena) {
        if (fwSession && fwSession->sessionObjectHash) {
            (void)nssCKFWHash_Destroy(fwSession->sessionObjectHash);
        }
        NSSArena_Destroy(arena);
    }

    return (NSSCKFWSession *)NULL;
}

static void
nss_ckfw_session_object_destroy_iterator(
    const void *key,
    void *value,
    void *closure)
{
    NSSCKFWObject *fwObject = (NSSCKFWObject *)value;
    nssCKFWObject_Finalize(fwObject, PR_TRUE);
}

/*
 * nssCKFWSession_Destroy
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_Destroy(
    NSSCKFWSession *fwSession,
    CK_BBOOL removeFromTokenHash)
{
    CK_RV error = CKR_OK;
    nssCKFWHash *sessionObjectHash;
    NSSCKFWCryptoOperationState i;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }
#endif /* NSSDEBUG */

    if (removeFromTokenHash) {
        error = nssCKFWToken_RemoveSession(fwSession->fwToken, fwSession);
    }

    /*
     * Invalidate session objects
     */

    sessionObjectHash = fwSession->sessionObjectHash;
    fwSession->sessionObjectHash = (nssCKFWHash *)NULL;

    nssCKFWHash_Iterate(sessionObjectHash,
                        nss_ckfw_session_object_destroy_iterator,
                        (void *)NULL);

    for (i = 0; i < NSSCKFWCryptoOperationState_Max; i++) {
        if (fwSession->fwOperationArray[i]) {
            nssCKFWCryptoOperation_Destroy(fwSession->fwOperationArray[i]);
        }
    }

#ifdef DEBUG
    (void)session_remove_pointer(fwSession);
#endif /* DEBUG */
    (void)nssCKFWHash_Destroy(sessionObjectHash);
    NSSArena_Destroy(fwSession->arena);

    return error;
}

/*
 * nssCKFWSession_GetMDSession
 *
 */
NSS_IMPLEMENT NSSCKMDSession *
nssCKFWSession_GetMDSession(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return (NSSCKMDSession *)NULL;
    }
#endif /* NSSDEBUG */

    return fwSession->mdSession;
}

/*
 * nssCKFWSession_GetArena
 *
 */
NSS_IMPLEMENT NSSArena *
nssCKFWSession_GetArena(
    NSSCKFWSession *fwSession,
    CK_RV *pError)
{
#ifdef NSSDEBUG
    if (!pError) {
        return (NSSArena *)NULL;
    }

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (NSSArena *)NULL;
    }
#endif /* NSSDEBUG */

    return fwSession->arena;
}

/*
 * nssCKFWSession_CallNotification
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_CallNotification(
    NSSCKFWSession *fwSession,
    CK_NOTIFICATION event)
{
    CK_RV error = CKR_OK;
    CK_SESSION_HANDLE handle;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }
#endif /* NSSDEBUG */

    if ((CK_NOTIFY)NULL == fwSession->Notify) {
        return CKR_OK;
    }

    handle = nssCKFWInstance_FindSessionHandle(fwSession->fwInstance, fwSession);
    if ((CK_SESSION_HANDLE)0 == handle) {
        return CKR_GENERAL_ERROR;
    }

    error = fwSession->Notify(handle, event, fwSession->pApplication);

    return error;
}

/*
 * nssCKFWSession_IsRWSession
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWSession_IsRWSession(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CK_FALSE;
    }
#endif /* NSSDEBUG */

    return fwSession->rw;
}

/*
 * nssCKFWSession_IsSO
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWSession_IsSO(
    NSSCKFWSession *fwSession)
{
    CK_STATE state;

#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CK_FALSE;
    }
#endif /* NSSDEBUG */

    state = nssCKFWToken_GetSessionState(fwSession->fwToken);
    switch (state) {
        case CKS_RO_PUBLIC_SESSION:
        case CKS_RO_USER_FUNCTIONS:
        case CKS_RW_PUBLIC_SESSION:
        case CKS_RW_USER_FUNCTIONS:
            return CK_FALSE;
        case CKS_RW_SO_FUNCTIONS:
            return CK_TRUE;
        default:
            return CK_FALSE;
    }
}

/*
 * nssCKFWSession_GetFWSlot
 *
 */
NSS_IMPLEMENT NSSCKFWSlot *
nssCKFWSession_GetFWSlot(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return (NSSCKFWSlot *)NULL;
    }
#endif /* NSSDEBUG */

    return nssCKFWToken_GetFWSlot(fwSession->fwToken);
}

/*
 * nssCFKWSession_GetSessionState
 *
 */
NSS_IMPLEMENT CK_STATE
nssCKFWSession_GetSessionState(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CKS_RO_PUBLIC_SESSION; /* whatever */
    }
#endif /* NSSDEBUG */

    return nssCKFWToken_GetSessionState(fwSession->fwToken);
}

/*
 * nssCKFWSession_SetFWFindObjects
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SetFWFindObjects(
    NSSCKFWSession *fwSession,
    NSSCKFWFindObjects *fwFindObjects)
{
#ifdef NSSDEBUG
    CK_RV error = CKR_OK;
#endif /* NSSDEBUG */

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

/* fwFindObjects may be null */
#endif /* NSSDEBUG */

    if ((fwSession->fwFindObjects) &&
        (fwFindObjects)) {
        return CKR_OPERATION_ACTIVE;
    }

    fwSession->fwFindObjects = fwFindObjects;

    return CKR_OK;
}

/*
 * nssCKFWSession_GetFWFindObjects
 *
 */
NSS_IMPLEMENT NSSCKFWFindObjects *
nssCKFWSession_GetFWFindObjects(
    NSSCKFWSession *fwSession,
    CK_RV *pError)
{
#ifdef NSSDEBUG
    if (!pError) {
        return (NSSCKFWFindObjects *)NULL;
    }

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (NSSCKFWFindObjects *)NULL;
    }
#endif /* NSSDEBUG */

    if (!fwSession->fwFindObjects) {
        *pError = CKR_OPERATION_NOT_INITIALIZED;
        return (NSSCKFWFindObjects *)NULL;
    }

    return fwSession->fwFindObjects;
}

/*
 * nssCKFWSession_SetMDSession
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SetMDSession(
    NSSCKFWSession *fwSession,
    NSSCKMDSession *mdSession)
{
#ifdef NSSDEBUG
    CK_RV error = CKR_OK;
#endif /* NSSDEBUG */

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!mdSession) {
        return CKR_ARGUMENTS_BAD;
    }
#endif /* NSSDEBUG */

    if (fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }

    fwSession->mdSession = mdSession;

    return CKR_OK;
}

/*
 * nssCKFWSession_SetHandle
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SetHandle(
    NSSCKFWSession *fwSession,
    CK_SESSION_HANDLE hSession)
{
#ifdef NSSDEBUG
    CK_RV error = CKR_OK;
#endif /* NSSDEBUG */

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }
#endif /* NSSDEBUG */

    if ((CK_SESSION_HANDLE)0 != fwSession->hSession) {
        return CKR_GENERAL_ERROR;
    }

    fwSession->hSession = hSession;

    return CKR_OK;
}

/*
 * nssCKFWSession_GetHandle
 *
 */
NSS_IMPLEMENT CK_SESSION_HANDLE
nssCKFWSession_GetHandle(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return NULL;
    }
#endif /* NSSDEBUG */

    return fwSession->hSession;
}

/*
 * nssCKFWSession_RegisterSessionObject
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_RegisterSessionObject(
    NSSCKFWSession *fwSession,
    NSSCKFWObject *fwObject)
{
    CK_RV rv = CKR_OK;

#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (fwSession->sessionObjectHash) {
        rv = nssCKFWHash_Add(fwSession->sessionObjectHash, fwObject, fwObject);
    }

    return rv;
}

/*
 * nssCKFWSession_DeregisterSessionObject
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_DeregisterSessionObject(
    NSSCKFWSession *fwSession,
    NSSCKFWObject *fwObject)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (fwSession->sessionObjectHash) {
        nssCKFWHash_Remove(fwSession->sessionObjectHash, fwObject);
    }

    return CKR_OK;
}

/*
 * nssCKFWSession_GetDeviceError
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWSession_GetDeviceError(
    NSSCKFWSession *fwSession)
{
#ifdef NSSDEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return (CK_ULONG)0;
    }

    if (!fwSession->mdSession) {
        return (CK_ULONG)0;
    }
#endif /* NSSDEBUG */

    if (!fwSession->mdSession->GetDeviceError) {
        return (CK_ULONG)0;
    }

    return fwSession->mdSession->GetDeviceError(fwSession->mdSession,
                                                fwSession, fwSession->mdToken, fwSession->fwToken,
                                                fwSession->mdInstance, fwSession->fwInstance);
}

/*
 * nssCKFWSession_Login
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_Login(
    NSSCKFWSession *fwSession,
    CK_USER_TYPE userType,
    NSSItem *pin)
{
    CK_RV error = CKR_OK;
    CK_STATE oldState;
    CK_STATE newState;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    switch (userType) {
        case CKU_SO:
        case CKU_USER:
            break;
        default:
            return CKR_USER_TYPE_INVALID;
    }

    if (!pin) {
        if (CK_TRUE != nssCKFWToken_GetHasProtectedAuthenticationPath(fwSession->fwToken)) {
            return CKR_ARGUMENTS_BAD;
        }
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    oldState = nssCKFWToken_GetSessionState(fwSession->fwToken);

    /*
     * It's not clear what happens when you're already logged in.
     * I'll just fail; but if we decide to change, the logic is
     * all right here.
     */

    if (CKU_SO == userType) {
        switch (oldState) {
            case CKS_RO_PUBLIC_SESSION:
                /*
                 * There's no such thing as a read-only security officer
                 * session, so fail.  The error should be CKR_SESSION_READ_ONLY,
                 * except that C_Login isn't defined to return that.  So we'll
                 * do CKR_SESSION_READ_ONLY_EXISTS, which is what is documented.
                 */
                return CKR_SESSION_READ_ONLY_EXISTS;
            case CKS_RO_USER_FUNCTIONS:
                return CKR_USER_ANOTHER_ALREADY_LOGGED_IN;
            case CKS_RW_PUBLIC_SESSION:
                newState =
                    CKS_RW_SO_FUNCTIONS;
                break;
            case CKS_RW_USER_FUNCTIONS:
                return CKR_USER_ANOTHER_ALREADY_LOGGED_IN;
            case CKS_RW_SO_FUNCTIONS:
                return CKR_USER_ALREADY_LOGGED_IN;
            default:
                return CKR_GENERAL_ERROR;
        }
    } else /* CKU_USER == userType */ {
        switch (oldState) {
            case CKS_RO_PUBLIC_SESSION:
                newState =
                    CKS_RO_USER_FUNCTIONS;
                break;
            case CKS_RO_USER_FUNCTIONS:
                return CKR_USER_ALREADY_LOGGED_IN;
            case CKS_RW_PUBLIC_SESSION:
                newState =
                    CKS_RW_USER_FUNCTIONS;
                break;
            case CKS_RW_USER_FUNCTIONS:
                return CKR_USER_ALREADY_LOGGED_IN;
            case CKS_RW_SO_FUNCTIONS:
                return CKR_USER_ANOTHER_ALREADY_LOGGED_IN;
            default:
                return CKR_GENERAL_ERROR;
        }
    }

    /*
     * So now we're in one of three cases:
     *
     * Old == CKS_RW_PUBLIC_SESSION, New == CKS_RW_SO_FUNCTIONS;
     * Old == CKS_RW_PUBLIC_SESSION, New == CKS_RW_USER_FUNCTIONS;
     * Old == CKS_RO_PUBLIC_SESSION, New == CKS_RO_USER_FUNCTIONS;
     */

    if (!fwSession->mdSession->Login) {
        /*
         * The Module doesn't want to be informed (or check the pin)
         * it'll just rely on the Framework as needed.
         */
        ;
    } else {
        error = fwSession->mdSession->Login(fwSession->mdSession, fwSession,
                                            fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                            fwSession->fwInstance, userType, pin, oldState, newState);
        if (CKR_OK != error) {
            return error;
        }
    }

    (void)nssCKFWToken_SetSessionState(fwSession->fwToken, newState);
    return CKR_OK;
}

/*
 * nssCKFWSession_Logout
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_Logout(
    NSSCKFWSession *fwSession)
{
    CK_RV error = CKR_OK;
    CK_STATE oldState;
    CK_STATE newState;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    oldState = nssCKFWToken_GetSessionState(fwSession->fwToken);

    switch (oldState) {
        case CKS_RO_PUBLIC_SESSION:
            return CKR_USER_NOT_LOGGED_IN;
        case CKS_RO_USER_FUNCTIONS:
            newState = CKS_RO_PUBLIC_SESSION;
            break;
        case CKS_RW_PUBLIC_SESSION:
            return CKR_USER_NOT_LOGGED_IN;
        case CKS_RW_USER_FUNCTIONS:
            newState = CKS_RW_PUBLIC_SESSION;
            break;
        case CKS_RW_SO_FUNCTIONS:
            newState = CKS_RW_PUBLIC_SESSION;
            break;
        default:
            return CKR_GENERAL_ERROR;
    }

    /*
     * So now we're in one of three cases:
     *
     * Old == CKS_RW_SO_FUNCTIONS,   New == CKS_RW_PUBLIC_SESSION;
     * Old == CKS_RW_USER_FUNCTIONS, New == CKS_RW_PUBLIC_SESSION;
     * Old == CKS_RO_USER_FUNCTIONS, New == CKS_RO_PUBLIC_SESSION;
     */

    if (!fwSession->mdSession->Logout) {
        /*
         * The Module doesn't want to be informed.  Okay.
         */
        ;
    } else {
        error = fwSession->mdSession->Logout(fwSession->mdSession, fwSession,
                                             fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                             fwSession->fwInstance, oldState, newState);
        if (CKR_OK != error) {
            /*
             * Now what?!  A failure really should end up with the Framework
             * considering it logged out, right?
             */
            ;
        }
    }

    (void)nssCKFWToken_SetSessionState(fwSession->fwToken, newState);
    return error;
}

/*
 * nssCKFWSession_InitPIN
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_InitPIN(
    NSSCKFWSession *fwSession,
    NSSItem *pin)
{
    CK_RV error = CKR_OK;
    CK_STATE state;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    state = nssCKFWToken_GetSessionState(fwSession->fwToken);
    if (CKS_RW_SO_FUNCTIONS != state) {
        return CKR_USER_NOT_LOGGED_IN;
    }

    if (!pin) {
        CK_BBOOL has = nssCKFWToken_GetHasProtectedAuthenticationPath(fwSession->fwToken);
        if (CK_TRUE != has) {
            return CKR_ARGUMENTS_BAD;
        }
    }

    if (!fwSession->mdSession->InitPIN) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    error = fwSession->mdSession->InitPIN(fwSession->mdSession, fwSession,
                                          fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                          fwSession->fwInstance, pin);

    return error;
}

/*
 * nssCKFWSession_SetPIN
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SetPIN(
    NSSCKFWSession *fwSession,
    const NSSItem *oldPin,
    NSSItem *newPin)
{
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (!newPin) {
        CK_BBOOL has = nssCKFWToken_GetHasProtectedAuthenticationPath(fwSession->fwToken);
        if (CK_TRUE != has) {
            return CKR_ARGUMENTS_BAD;
        }
    }

    if (!oldPin) {
        CK_BBOOL has = nssCKFWToken_GetHasProtectedAuthenticationPath(fwSession->fwToken);
        if (CK_TRUE != has) {
            return CKR_ARGUMENTS_BAD;
        }
    }

    if (!fwSession->mdSession->SetPIN) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    error = fwSession->mdSession->SetPIN(fwSession->mdSession, fwSession,
                                         fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                         fwSession->fwInstance, (NSSItem *)oldPin, newPin);

    return error;
}

/*
 * nssCKFWSession_GetOperationStateLen
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWSession_GetOperationStateLen(
    NSSCKFWSession *fwSession,
    CK_RV *pError)
{
    CK_ULONG mdAmt;
    CK_ULONG fwAmt;

#ifdef NSSDEBUG
    if (!pError) {
        return (CK_ULONG)0;
    }

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (CK_ULONG)0;
    }

    if (!fwSession->mdSession) {
        *pError = CKR_GENERAL_ERROR;
        return (CK_ULONG)0;
    }
#endif /* NSSDEBUG */

    if (!fwSession->mdSession->GetOperationStateLen) {
        *pError = CKR_STATE_UNSAVEABLE;
        return (CK_ULONG)0;
    }

    /*
     * We could check that the session is actually in some state..
     */

    mdAmt = fwSession->mdSession->GetOperationStateLen(fwSession->mdSession,
                                                       fwSession, fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                                       fwSession->fwInstance, pError);

    if (((CK_ULONG)0 == mdAmt) && (CKR_OK != *pError)) {
        return (CK_ULONG)0;
    }

    /*
     * Add a bit of sanity-checking
     */
    fwAmt = mdAmt + 2 * sizeof(CK_ULONG);

    return fwAmt;
}

/*
 * nssCKFWSession_GetOperationState
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_GetOperationState(
    NSSCKFWSession *fwSession,
    NSSItem *buffer)
{
    CK_RV error = CKR_OK;
    CK_ULONG fwAmt;
    CK_ULONG *ulBuffer;
    NSSItem i2;
    CK_ULONG n, i;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!buffer) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!buffer->data) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (!fwSession->mdSession->GetOperationState) {
        return CKR_STATE_UNSAVEABLE;
    }

    /*
     * Sanity-check the caller's buffer.
     */

    error = CKR_OK;
    fwAmt = nssCKFWSession_GetOperationStateLen(fwSession, &error);
    if (((CK_ULONG)0 == fwAmt) && (CKR_OK != error)) {
        return error;
    }

    if (buffer->size < fwAmt) {
        return CKR_BUFFER_TOO_SMALL;
    }

    ulBuffer = (CK_ULONG *)buffer->data;

    i2.size = buffer->size - 2 * sizeof(CK_ULONG);
    i2.data = (void *)&ulBuffer[2];

    error = fwSession->mdSession->GetOperationState(fwSession->mdSession,
                                                    fwSession, fwSession->mdToken, fwSession->fwToken,
                                                    fwSession->mdInstance, fwSession->fwInstance, &i2);

    if (CKR_OK != error) {
        return error;
    }

    /*
     * Add a little integrety/identity check.
     * NOTE: right now, it's pretty stupid.
     * A CRC or something would be better.
     */

    ulBuffer[0] = 0x434b4657; /* CKFW */
    ulBuffer[1] = 0;
    n = i2.size / sizeof(CK_ULONG);
    for (i = 0; i < n; i++) {
        ulBuffer[1] ^= ulBuffer[2 + i];
    }

    return CKR_OK;
}

/*
 * nssCKFWSession_SetOperationState
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SetOperationState(
    NSSCKFWSession *fwSession,
    NSSItem *state,
    NSSCKFWObject *encryptionKey,
    NSSCKFWObject *authenticationKey)
{
    CK_RV error = CKR_OK;
    CK_ULONG *ulBuffer;
    CK_ULONG n, i;
    CK_ULONG x;
    NSSItem s;
    NSSCKMDObject *mdek;
    NSSCKMDObject *mdak;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!state) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!state->data) {
        return CKR_ARGUMENTS_BAD;
    }

    if (encryptionKey) {
        error = nssCKFWObject_verifyPointer(encryptionKey);
        if (CKR_OK != error) {
            return error;
        }
    }

    if (authenticationKey) {
        error = nssCKFWObject_verifyPointer(authenticationKey);
        if (CKR_OK != error) {
            return error;
        }
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    ulBuffer = (CK_ULONG *)state->data;
    if (0x43b4657 != ulBuffer[0]) {
        return CKR_SAVED_STATE_INVALID;
    }
    n = (state->size / sizeof(CK_ULONG)) - 2;
    x = (CK_ULONG)0;
    for (i = 0; i < n; i++) {
        x ^= ulBuffer[2 + i];
    }

    if (x != ulBuffer[1]) {
        return CKR_SAVED_STATE_INVALID;
    }

    if (!fwSession->mdSession->SetOperationState) {
        return CKR_GENERAL_ERROR;
    }

    s.size = state->size - 2 * sizeof(CK_ULONG);
    s.data = (void *)&ulBuffer[2];

    if (encryptionKey) {
        mdek = nssCKFWObject_GetMDObject(encryptionKey);
    } else {
        mdek = (NSSCKMDObject *)NULL;
    }

    if (authenticationKey) {
        mdak = nssCKFWObject_GetMDObject(authenticationKey);
    } else {
        mdak = (NSSCKMDObject *)NULL;
    }

    error = fwSession->mdSession->SetOperationState(fwSession->mdSession,
                                                    fwSession, fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                                    fwSession->fwInstance, &s, mdek, encryptionKey, mdak, authenticationKey);

    if (CKR_OK != error) {
        return error;
    }

    /*
     * Here'd we restore any session data
     */

    return CKR_OK;
}

static CK_BBOOL
nss_attributes_form_token_object(
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount)
{
    CK_ULONG i;
    CK_BBOOL rv;

    for (i = 0; i < ulAttributeCount; i++) {
        if (CKA_TOKEN == pTemplate[i].type) {
            /* If we sanity-check, we can remove this sizeof check */
            if (sizeof(CK_BBOOL) == pTemplate[i].ulValueLen) {
                (void)nsslibc_memcpy(&rv, pTemplate[i].pValue, sizeof(CK_BBOOL));
                return rv;
            } else {
                return CK_FALSE;
            }
        }
    }

    return CK_FALSE;
}

/*
 * nssCKFWSession_CreateObject
 *
 */
NSS_IMPLEMENT NSSCKFWObject *
nssCKFWSession_CreateObject(
    NSSCKFWSession *fwSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    NSSArena *arena;
    NSSCKMDObject *mdObject;
    NSSCKFWObject *fwObject;
    CK_BBOOL isTokenObject;

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

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != pError) {
        return (NSSCKFWObject *)NULL;
    }

    if ((CK_ATTRIBUTE_PTR)NULL == pTemplate) {
        *pError = CKR_ARGUMENTS_BAD;
        return (NSSCKFWObject *)NULL;
    }

    if (!fwSession->mdSession) {
        *pError = CKR_GENERAL_ERROR;
        return (NSSCKFWObject *)NULL;
    }
#endif /* NSSDEBUG */

    /*
     * Here would be an excellent place to sanity-check the object.
     */

    isTokenObject = nss_attributes_form_token_object(pTemplate, ulAttributeCount);
    if (CK_TRUE == isTokenObject) {
        /* === TOKEN OBJECT === */

        if (!fwSession->mdSession->CreateObject) {
            *pError = CKR_TOKEN_WRITE_PROTECTED;
            return (NSSCKFWObject *)NULL;
        }

        arena = nssCKFWToken_GetArena(fwSession->fwToken, pError);
        if (!arena) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            return (NSSCKFWObject *)NULL;
        }

        goto callmdcreateobject;
    } else {
        /* === SESSION OBJECT === */

        arena = nssCKFWSession_GetArena(fwSession, pError);
        if (!arena) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            return (NSSCKFWObject *)NULL;
        }

        if (CK_TRUE == nssCKFWInstance_GetModuleHandlesSessionObjects(
                           fwSession->fwInstance)) {
            /* --- module handles the session object -- */

            if (!fwSession->mdSession->CreateObject) {
                *pError = CKR_GENERAL_ERROR;
                return (NSSCKFWObject *)NULL;
            }

            goto callmdcreateobject;
        } else {
            /* --- framework handles the session object -- */
            mdObject = nssCKMDSessionObject_Create(fwSession->fwToken,
                                                   arena, pTemplate, ulAttributeCount, pError);
            goto gotmdobject;
        }
    }

callmdcreateobject:
    mdObject = fwSession->mdSession->CreateObject(fwSession->mdSession,
                                                  fwSession, fwSession->mdToken, fwSession->fwToken,
                                                  fwSession->mdInstance, fwSession->fwInstance, arena, pTemplate,
                                                  ulAttributeCount, pError);

gotmdobject:
    if (!mdObject) {
        if (CKR_OK == *pError) {
            *pError = CKR_GENERAL_ERROR;
        }
        return (NSSCKFWObject *)NULL;
    }

    fwObject = nssCKFWObject_Create(isTokenObject ? arena : NULL, mdObject,
                                    isTokenObject ? NULL
                                                  : fwSession,
                                    fwSession->fwToken, fwSession->fwInstance, pError);
    if (!fwObject) {
        if (CKR_OK == *pError) {
            *pError = CKR_GENERAL_ERROR;
        }

        if (mdObject->Destroy) {
            (void)mdObject->Destroy(mdObject, (NSSCKFWObject *)NULL,
                                    fwSession->mdSession, fwSession, fwSession->mdToken,
                                    fwSession->fwToken, fwSession->mdInstance, fwSession->fwInstance);
        }

        return (NSSCKFWObject *)NULL;
    }

    if (CK_FALSE == isTokenObject) {
        if (CK_FALSE == nssCKFWHash_Exists(fwSession->sessionObjectHash, fwObject)) {
            *pError = nssCKFWHash_Add(fwSession->sessionObjectHash, fwObject, fwObject);
            if (CKR_OK != *pError) {
                nssCKFWObject_Finalize(fwObject, PR_TRUE);
                return (NSSCKFWObject *)NULL;
            }
        }
    }

    return fwObject;
}

/*
 * nssCKFWSession_CopyObject
 *
 */
NSS_IMPLEMENT NSSCKFWObject *
nssCKFWSession_CopyObject(
    NSSCKFWSession *fwSession,
    NSSCKFWObject *fwObject,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    CK_BBOOL oldIsToken;
    CK_BBOOL newIsToken;
    CK_ULONG i;
    NSSCKFWObject *rv;

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

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (NSSCKFWObject *)NULL;
    }

    *pError = nssCKFWObject_verifyPointer(fwObject);
    if (CKR_OK != *pError) {
        return (NSSCKFWObject *)NULL;
    }

    if (!fwSession->mdSession) {
        *pError = CKR_GENERAL_ERROR;
        return (NSSCKFWObject *)NULL;
    }
#endif /* NSSDEBUG */

    /*
     * Sanity-check object
     */

    if (!fwObject) {
        *pError = CKR_ARGUMENTS_BAD;
        return (NSSCKFWObject *)NULL;
    }

    oldIsToken = nssCKFWObject_IsTokenObject(fwObject);

    newIsToken = oldIsToken;
    for (i = 0; i < ulAttributeCount; i++) {
        if (CKA_TOKEN == pTemplate[i].type) {
            /* Since we sanity-checked the object, we know this is the right size. */
            (void)nsslibc_memcpy(&newIsToken, pTemplate[i].pValue, sizeof(CK_BBOOL));
            break;
        }
    }

    /*
     * If the Module handles its session objects, or if both the new
     * and old object are token objects, use CopyObject if it exists.
     */

    if ((fwSession->mdSession->CopyObject) &&
        (((CK_TRUE == oldIsToken) && (CK_TRUE == newIsToken)) ||
         (CK_TRUE == nssCKFWInstance_GetModuleHandlesSessionObjects(
                         fwSession->fwInstance)))) {
        /* use copy object */
        NSSArena *arena;
        NSSCKMDObject *mdOldObject;
        NSSCKMDObject *mdObject;

        mdOldObject = nssCKFWObject_GetMDObject(fwObject);

        if (CK_TRUE == newIsToken) {
            arena = nssCKFWToken_GetArena(fwSession->fwToken, pError);
        } else {
            arena = nssCKFWSession_GetArena(fwSession, pError);
        }
        if (!arena) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            return (NSSCKFWObject *)NULL;
        }

        mdObject = fwSession->mdSession->CopyObject(fwSession->mdSession,
                                                    fwSession, fwSession->mdToken, fwSession->fwToken,
                                                    fwSession->mdInstance, fwSession->fwInstance, mdOldObject,
                                                    fwObject, arena, pTemplate, ulAttributeCount, pError);
        if (!mdObject) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            return (NSSCKFWObject *)NULL;
        }

        rv = nssCKFWObject_Create(newIsToken ? arena : NULL, mdObject,
                                  newIsToken ? NULL
                                             : fwSession,
                                  fwSession->fwToken, fwSession->fwInstance, pError);

        if (CK_FALSE == newIsToken) {
            if (CK_FALSE == nssCKFWHash_Exists(fwSession->sessionObjectHash, rv)) {
                *pError = nssCKFWHash_Add(fwSession->sessionObjectHash, rv, rv);
                if (CKR_OK != *pError) {
                    nssCKFWObject_Finalize(rv, PR_TRUE);
                    return (NSSCKFWObject *)NULL;
                }
            }
        }

        return rv;
    } else {
        /* use create object */
        NSSArena *tmpArena;
        CK_ATTRIBUTE_PTR newTemplate;
        CK_ULONG j, n, newLength, k;
        CK_ATTRIBUTE_TYPE_PTR oldTypes;

        n = nssCKFWObject_GetAttributeCount(fwObject, pError);
        if ((0 == n) && (CKR_OK != *pError)) {
            return (NSSCKFWObject *)NULL;
        }

        tmpArena = NSSArena_Create();
        if (!tmpArena) {
            *pError = CKR_HOST_MEMORY;
            return (NSSCKFWObject *)NULL;
        }

        oldTypes = nss_ZNEWARRAY(tmpArena, CK_ATTRIBUTE_TYPE, n);
        if ((CK_ATTRIBUTE_TYPE_PTR)NULL == oldTypes) {
            NSSArena_Destroy(tmpArena);
            *pError = CKR_HOST_MEMORY;
            return (NSSCKFWObject *)NULL;
        }

        *pError = nssCKFWObject_GetAttributeTypes(fwObject, oldTypes, n);
        if (CKR_OK != *pError) {
            NSSArena_Destroy(tmpArena);
            return (NSSCKFWObject *)NULL;
        }

        newLength = n;
        for (i = 0; i < ulAttributeCount; i++) {
            for (j = 0; j < n; j++) {
                if (oldTypes[j] == pTemplate[i].type) {
                    if ((CK_VOID_PTR)NULL ==
                        pTemplate[i].pValue) {
                        /* Removing the attribute */
                        newLength--;
                    }
                    break;
                }
            }
            if (j == n) {
                /* Not found */
                newLength++;
            }
        }

        newTemplate = nss_ZNEWARRAY(tmpArena, CK_ATTRIBUTE, newLength);
        if ((CK_ATTRIBUTE_PTR)NULL == newTemplate) {
            NSSArena_Destroy(tmpArena);
            *pError = CKR_HOST_MEMORY;
            return (NSSCKFWObject *)NULL;
        }

        k = 0;
        for (j = 0; j < n; j++) {
            for (i = 0; i < ulAttributeCount; i++) {
                if (oldTypes[j] == pTemplate[i].type) {
                    if ((CK_VOID_PTR)NULL ==
                        pTemplate[i].pValue) {
                        /* This attribute is being deleted */
                        ;
                    } else {
                        /* This attribute is being replaced */
                        newTemplate[k].type =
                            pTemplate[i].type;
                        newTemplate[k].pValue =
                            pTemplate[i].pValue;
                        newTemplate[k].ulValueLen =
                            pTemplate[i].ulValueLen;
                        k++;
                    }
                    break;
                }
            }
            if (i == ulAttributeCount) {
                /* This attribute is being copied over from the old object */
                NSSItem item, *it;
                item.size = 0;
                item.data = (void *)NULL;
                it = nssCKFWObject_GetAttribute(fwObject, oldTypes[j],
                                                &item, tmpArena, pError);
                if (!it) {
                    if (CKR_OK ==
                        *pError) {
                        *pError =
                            CKR_GENERAL_ERROR;
                    }
                    NSSArena_Destroy(tmpArena);
                    return (NSSCKFWObject *)NULL;
                }
                newTemplate[k].type = oldTypes[j];
                newTemplate[k].pValue = it->data;
                newTemplate[k].ulValueLen = it->size;
                k++;
            }
        }
        /* assert that k == newLength */

        rv = nssCKFWSession_CreateObject(fwSession, newTemplate, newLength, pError);
        if (!rv) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            NSSArena_Destroy(tmpArena);
            return (NSSCKFWObject *)NULL;
        }

        NSSArena_Destroy(tmpArena);
        return rv;
    }
}

/*
 * nssCKFWSession_FindObjectsInit
 *
 */
NSS_IMPLEMENT NSSCKFWFindObjects *
nssCKFWSession_FindObjectsInit(
    NSSCKFWSession *fwSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    NSSCKMDFindObjects *mdfo1 = (NSSCKMDFindObjects *)NULL;
    NSSCKMDFindObjects *mdfo2 = (NSSCKMDFindObjects *)NULL;

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

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (NSSCKFWFindObjects *)NULL;
    }

    if (((CK_ATTRIBUTE_PTR)NULL == pTemplate) && (ulAttributeCount != 0)) {
        *pError = CKR_ARGUMENTS_BAD;
        return (NSSCKFWFindObjects *)NULL;
    }

    if (!fwSession->mdSession) {
        *pError = CKR_GENERAL_ERROR;
        return (NSSCKFWFindObjects *)NULL;
    }
#endif /* NSSDEBUG */

    if (CK_TRUE != nssCKFWInstance_GetModuleHandlesSessionObjects(
                       fwSession->fwInstance)) {
        CK_ULONG i;

        /*
         * Does the search criteria restrict us to token or session
         * objects?
         */

        for (i = 0; i < ulAttributeCount; i++) {
            if (CKA_TOKEN == pTemplate[i].type) {
                /* Yes, it does. */
                CK_BBOOL isToken;
                if (sizeof(CK_BBOOL) != pTemplate[i].ulValueLen) {
                    *pError =
                        CKR_ATTRIBUTE_VALUE_INVALID;
                    return (NSSCKFWFindObjects *)NULL;
                }
                (void)nsslibc_memcpy(&isToken, pTemplate[i].pValue, sizeof(CK_BBOOL));

                if (CK_TRUE == isToken) {
                    /* Pass it on to the module's search routine */
                    if (!fwSession->mdSession->FindObjectsInit) {
                        goto wrap;
                    }

                    mdfo1 =
                        fwSession->mdSession->FindObjectsInit(fwSession->mdSession,
                                                              fwSession, fwSession->mdToken, fwSession->fwToken,
                                                              fwSession->mdInstance, fwSession->fwInstance,
                                                              pTemplate, ulAttributeCount, pError);
                } else {
                    /* Do the search ourselves */
                    mdfo1 =
                        nssCKMDFindSessionObjects_Create(fwSession->fwToken,
                                                         pTemplate, ulAttributeCount, pError);
                }

                if (!mdfo1) {
                    if (CKR_OK ==
                        *pError) {
                        *pError =
                            CKR_GENERAL_ERROR;
                    }
                    return (NSSCKFWFindObjects *)NULL;
                }

                goto wrap;
            }
        }

        if (i == ulAttributeCount) {
            /* No, it doesn't.  Do a hybrid search. */
            mdfo1 = fwSession->mdSession->FindObjectsInit(fwSession->mdSession,
                                                          fwSession, fwSession->mdToken, fwSession->fwToken,
                                                          fwSession->mdInstance, fwSession->fwInstance,
                                                          pTemplate, ulAttributeCount, pError);

            if (!mdfo1) {
                if (CKR_OK == *pError) {
                    *pError =
                        CKR_GENERAL_ERROR;
                }
                return (NSSCKFWFindObjects *)NULL;
            }

            mdfo2 = nssCKMDFindSessionObjects_Create(fwSession->fwToken,
                                                     pTemplate, ulAttributeCount, pError);
            if (!mdfo2) {
                if (CKR_OK == *pError) {
                    *pError =
                        CKR_GENERAL_ERROR;
                }
                if (mdfo1->Final) {
                    mdfo1->Final(mdfo1, (NSSCKFWFindObjects *)NULL, fwSession->mdSession,
                                 fwSession, fwSession->mdToken, fwSession->fwToken,
                                 fwSession->mdInstance, fwSession->fwInstance);
                }
                return (NSSCKFWFindObjects *)NULL;
            }

            goto wrap;
        }
        /*NOTREACHED*/
    } else {
        /* Module handles all its own objects.  Pass on to module's search */
        mdfo1 = fwSession->mdSession->FindObjectsInit(fwSession->mdSession,
                                                      fwSession, fwSession->mdToken, fwSession->fwToken,
                                                      fwSession->mdInstance, fwSession->fwInstance,
                                                      pTemplate, ulAttributeCount, pError);

        if (!mdfo1) {
            if (CKR_OK == *pError) {
                *pError = CKR_GENERAL_ERROR;
            }
            return (NSSCKFWFindObjects *)NULL;
        }

        goto wrap;
    }

wrap:
    return nssCKFWFindObjects_Create(fwSession, fwSession->fwToken,
                                     fwSession->fwInstance, mdfo1, mdfo2, pError);
}

/*
 * nssCKFWSession_SeedRandom
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_SeedRandom(
    NSSCKFWSession *fwSession,
    NSSItem *seed)
{
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!seed) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!seed->data) {
        return CKR_ARGUMENTS_BAD;
    }

    if (0 == seed->size) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (!fwSession->mdSession->SeedRandom) {
        return CKR_RANDOM_SEED_NOT_SUPPORTED;
    }

    error = fwSession->mdSession->SeedRandom(fwSession->mdSession, fwSession,
                                             fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                             fwSession->fwInstance, seed);

    return error;
}

/*
 * nssCKFWSession_GetRandom
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_GetRandom(
    NSSCKFWSession *fwSession,
    NSSItem *buffer)
{
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!buffer) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!buffer->data) {
        return CKR_ARGUMENTS_BAD;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    if (!fwSession->mdSession->GetRandom) {
        if (CK_TRUE == nssCKFWToken_GetHasRNG(fwSession->fwToken)) {
            return CKR_GENERAL_ERROR;
        } else {
            return CKR_RANDOM_NO_RNG;
        }
    }

    if (0 == buffer->size) {
        return CKR_OK;
    }

    error = fwSession->mdSession->GetRandom(fwSession->mdSession, fwSession,
                                            fwSession->mdToken, fwSession->fwToken, fwSession->mdInstance,
                                            fwSession->fwInstance, buffer);

    return error;
}

/*
 * nssCKFWSession_SetCurrentCryptoOperation
 */
NSS_IMPLEMENT void
nssCKFWSession_SetCurrentCryptoOperation(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKFWCryptoOperationState state)
{
#ifdef NSSDEBUG
    CK_RV error = CKR_OK;
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return;
    }

    if (state >= NSSCKFWCryptoOperationState_Max) {
        return;
    }

    if (!fwSession->mdSession) {
        return;
    }
#endif /* NSSDEBUG */
    fwSession->fwOperationArray[state] = fwOperation;
    return;
}

/*
 * nssCKFWSession_GetCurrentCryptoOperation
 */
NSS_IMPLEMENT NSSCKFWCryptoOperation *
nssCKFWSession_GetCurrentCryptoOperation(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationState state)
{
#ifdef NSSDEBUG
    CK_RV error = CKR_OK;
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return (NSSCKFWCryptoOperation *)NULL;
    }

    if (state >= NSSCKFWCryptoOperationState_Max) {
        return (NSSCKFWCryptoOperation *)NULL;
    }

    if (!fwSession->mdSession) {
        return (NSSCKFWCryptoOperation *)NULL;
    }
#endif /* NSSDEBUG */
    return fwSession->fwOperationArray[state];
}

/*
 * nssCKFWSession_Final
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_Final(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationType type,
    NSSCKFWCryptoOperationState state,
    CK_BYTE_PTR outBuf,
    CK_ULONG_PTR outBufLen)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSItem outputBuffer;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession, state);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (type != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* handle buffer issues, note for Verify, the type is an input buffer. */
    if (NSSCKFWCryptoOperationType_Verify == type) {
        if ((CK_BYTE_PTR)NULL == outBuf) {
            error = CKR_ARGUMENTS_BAD;
            goto done;
        }
    } else {
        CK_ULONG len = nssCKFWCryptoOperation_GetFinalLength(fwOperation, &error);
        CK_ULONG maxBufLen = *outBufLen;

        if (CKR_OK != error) {
            goto done;
        }
        *outBufLen = len;
        if ((CK_BYTE_PTR)NULL == outBuf) {
            return CKR_OK;
        }

        if (len > maxBufLen) {
            return CKR_BUFFER_TOO_SMALL;
        }
    }
    outputBuffer.data = outBuf;
    outputBuffer.size = *outBufLen;

    error = nssCKFWCryptoOperation_Final(fwOperation, &outputBuffer);
done:
    if (CKR_BUFFER_TOO_SMALL == error) {
        return error;
    }
    /* clean up our state */
    nssCKFWCryptoOperation_Destroy(fwOperation);
    nssCKFWSession_SetCurrentCryptoOperation(fwSession, NULL, state);
    return error;
}

/*
 * nssCKFWSession_Update
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_Update(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationType type,
    NSSCKFWCryptoOperationState state,
    CK_BYTE_PTR inBuf,
    CK_ULONG inBufLen,
    CK_BYTE_PTR outBuf,
    CK_ULONG_PTR outBufLen)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSItem inputBuffer;
    NSSItem outputBuffer;
    CK_ULONG len;
    CK_ULONG maxBufLen;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession, state);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (type != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    inputBuffer.data = inBuf;
    inputBuffer.size = inBufLen;

    /* handle buffer issues, note for Verify, the type is an input buffer. */
    len = nssCKFWCryptoOperation_GetOperationLength(fwOperation, &inputBuffer,
                                                    &error);
    if (CKR_OK != error) {
        return error;
    }
    maxBufLen = *outBufLen;

    *outBufLen = len;
    if ((CK_BYTE_PTR)NULL == outBuf) {
        return CKR_OK;
    }

    if (len > maxBufLen) {
        return CKR_BUFFER_TOO_SMALL;
    }
    outputBuffer.data = outBuf;
    outputBuffer.size = *outBufLen;

    return nssCKFWCryptoOperation_Update(fwOperation,
                                         &inputBuffer, &outputBuffer);
}

/*
 * nssCKFWSession_DigestUpdate
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_DigestUpdate(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationType type,
    NSSCKFWCryptoOperationState state,
    CK_BYTE_PTR inBuf,
    CK_ULONG inBufLen)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSItem inputBuffer;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession, state);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (type != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    inputBuffer.data = inBuf;
    inputBuffer.size = inBufLen;

    error = nssCKFWCryptoOperation_DigestUpdate(fwOperation, &inputBuffer);
    return error;
}

/*
 * nssCKFWSession_DigestUpdate
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_DigestKey(
    NSSCKFWSession *fwSession,
    NSSCKFWObject *fwKey)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSItem *inputBuffer;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession,
                                                           NSSCKFWCryptoOperationState_Digest);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (NSSCKFWCryptoOperationType_Digest !=
        nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    error = nssCKFWCryptoOperation_DigestKey(fwOperation, fwKey);
    if (CKR_FUNCTION_FAILED != error) {
        return error;
    }

    /* no machine depended way for this to happen, do it by hand */
    inputBuffer = nssCKFWObject_GetAttribute(fwKey, CKA_VALUE, NULL, NULL, &error);
    if (!inputBuffer) {
        /* couldn't get the value, just fail then */
        return error;
    }
    error = nssCKFWCryptoOperation_DigestUpdate(fwOperation, inputBuffer);
    nssItem_Destroy(inputBuffer);
    return error;
}

/*
 * nssCKFWSession_UpdateFinal
 */
NSS_IMPLEMENT CK_RV
nssCKFWSession_UpdateFinal(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationType type,
    NSSCKFWCryptoOperationState state,
    CK_BYTE_PTR inBuf,
    CK_ULONG inBufLen,
    CK_BYTE_PTR outBuf,
    CK_ULONG_PTR outBufLen)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSItem inputBuffer;
    NSSItem outputBuffer;
    PRBool isEncryptDecrypt;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession, state);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (type != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    inputBuffer.data = inBuf;
    inputBuffer.size = inBufLen;
    isEncryptDecrypt = (PRBool)((NSSCKFWCryptoOperationType_Encrypt == type) ||
                                (NSSCKFWCryptoOperationType_Decrypt == type));

    /* handle buffer issues, note for Verify, the type is an input buffer. */
    if (NSSCKFWCryptoOperationType_Verify == type) {
        if ((CK_BYTE_PTR)NULL == outBuf) {
            error = CKR_ARGUMENTS_BAD;
            goto done;
        }
    } else {
        CK_ULONG maxBufLen = *outBufLen;
        CK_ULONG len;

        len = (isEncryptDecrypt) ? nssCKFWCryptoOperation_GetOperationLength(fwOperation,
                                                                             &inputBuffer, &error)
                                 : nssCKFWCryptoOperation_GetFinalLength(fwOperation, &error);

        if (CKR_OK != error) {
            goto done;
        }

        *outBufLen = len;
        if ((CK_BYTE_PTR)NULL == outBuf) {
            return CKR_OK;
        }

        if (len > maxBufLen) {
            return CKR_BUFFER_TOO_SMALL;
        }
    }
    outputBuffer.data = outBuf;
    outputBuffer.size = *outBufLen;

    error = nssCKFWCryptoOperation_UpdateFinal(fwOperation,
                                               &inputBuffer, &outputBuffer);

    /* UpdateFinal isn't support, manually use Update and Final */
    if (CKR_FUNCTION_FAILED == error) {
        error = isEncryptDecrypt ? nssCKFWCryptoOperation_Update(fwOperation, &inputBuffer, &outputBuffer)
                                 : nssCKFWCryptoOperation_DigestUpdate(fwOperation, &inputBuffer);

        if (CKR_OK == error) {
            error = nssCKFWCryptoOperation_Final(fwOperation, &outputBuffer);
        }
    }

done:
    if (CKR_BUFFER_TOO_SMALL == error) {
        /* if we return CKR_BUFFER_TOO_SMALL, we the caller is not expecting.
         * the crypto state to be freed */
        return error;
    }

    /* clean up our state */
    nssCKFWCryptoOperation_Destroy(fwOperation);
    nssCKFWSession_SetCurrentCryptoOperation(fwSession, NULL, state);
    return error;
}

NSS_IMPLEMENT CK_RV
nssCKFWSession_UpdateCombo(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationType encryptType,
    NSSCKFWCryptoOperationType digestType,
    NSSCKFWCryptoOperationState digestState,
    CK_BYTE_PTR inBuf,
    CK_ULONG inBufLen,
    CK_BYTE_PTR outBuf,
    CK_ULONG_PTR outBufLen)
{
    NSSCKFWCryptoOperation *fwOperation;
    NSSCKFWCryptoOperation *fwPeerOperation;
    NSSItem inputBuffer;
    NSSItem outputBuffer;
    CK_ULONG maxBufLen = *outBufLen;
    CK_ULONG len;
    CK_RV error = CKR_OK;

#ifdef NSSDEBUG
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }

    if (!fwSession->mdSession) {
        return CKR_GENERAL_ERROR;
    }
#endif /* NSSDEBUG */

    /* make sure we have a valid operation initialized */
    fwOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession,
                                                           NSSCKFWCryptoOperationState_EncryptDecrypt);
    if (!fwOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (encryptType != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }
    /* make sure we have a valid operation initialized */
    fwPeerOperation = nssCKFWSession_GetCurrentCryptoOperation(fwSession,
                                                               digestState);
    if (!fwPeerOperation) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    /* make sure it's the correct type */
    if (digestType != nssCKFWCryptoOperation_GetType(fwOperation)) {
        return CKR_OPERATION_NOT_INITIALIZED;
    }

    inputBuffer.data = inBuf;
    inputBuffer.size = inBufLen;
    len = nssCKFWCryptoOperation_GetOperationLength(fwOperation,
                                                    &inputBuffer, &error);
    if (CKR_OK != error) {
        return error;
    }

    *outBufLen = len;
    if ((CK_BYTE_PTR)NULL == outBuf) {
        return CKR_OK;
    }

    if (len > maxBufLen) {
        return CKR_BUFFER_TOO_SMALL;
    }

    outputBuffer.data = outBuf;
    outputBuffer.size = *outBufLen;

    error = nssCKFWCryptoOperation_UpdateCombo(fwOperation, fwPeerOperation,
                                               &inputBuffer, &outputBuffer);
    if (CKR_FUNCTION_FAILED == error) {
        PRBool isEncrypt =
            (PRBool)(NSSCKFWCryptoOperationType_Encrypt == encryptType);

        if (isEncrypt) {
            error = nssCKFWCryptoOperation_DigestUpdate(fwPeerOperation,
                                                        &inputBuffer);
            if (CKR_OK != error) {
                return error;
            }
        }
        error = nssCKFWCryptoOperation_Update(fwOperation,
                                              &inputBuffer, &outputBuffer);
        if (CKR_OK != error) {
            return error;
        }
        if (!isEncrypt) {
            error = nssCKFWCryptoOperation_DigestUpdate(fwPeerOperation,
                                                        &outputBuffer);
        }
    }
    return error;
}

/*
 * NSSCKFWSession_GetMDSession
 *
 */

NSS_IMPLEMENT NSSCKMDSession *
NSSCKFWSession_GetMDSession(
    NSSCKFWSession *fwSession)
{
#ifdef DEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return (NSSCKMDSession *)NULL;
    }
#endif /* DEBUG */

    return nssCKFWSession_GetMDSession(fwSession);
}

/*
 * NSSCKFWSession_GetArena
 *
 */

NSS_IMPLEMENT NSSArena *
NSSCKFWSession_GetArena(
    NSSCKFWSession *fwSession,
    CK_RV *pError)
{
#ifdef DEBUG
    if (!pError) {
        return (NSSArena *)NULL;
    }

    *pError = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != *pError) {
        return (NSSArena *)NULL;
    }
#endif /* DEBUG */

    return nssCKFWSession_GetArena(fwSession, pError);
}

/*
 * NSSCKFWSession_CallNotification
 *
 */

NSS_IMPLEMENT CK_RV
NSSCKFWSession_CallNotification(
    NSSCKFWSession *fwSession,
    CK_NOTIFICATION event)
{
#ifdef DEBUG
    CK_RV error = CKR_OK;

    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return error;
    }
#endif /* DEBUG */

    return nssCKFWSession_CallNotification(fwSession, event);
}

/*
 * NSSCKFWSession_IsRWSession
 *
 */

NSS_IMPLEMENT CK_BBOOL
NSSCKFWSession_IsRWSession(
    NSSCKFWSession *fwSession)
{
#ifdef DEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CK_FALSE;
    }
#endif /* DEBUG */

    return nssCKFWSession_IsRWSession(fwSession);
}

/*
 * NSSCKFWSession_IsSO
 *
 */

NSS_IMPLEMENT CK_BBOOL
NSSCKFWSession_IsSO(
    NSSCKFWSession *fwSession)
{
#ifdef DEBUG
    if (CKR_OK != nssCKFWSession_verifyPointer(fwSession)) {
        return CK_FALSE;
    }
#endif /* DEBUG */

    return nssCKFWSession_IsSO(fwSession);
}

NSS_IMPLEMENT NSSCKFWCryptoOperation *
NSSCKFWSession_GetCurrentCryptoOperation(
    NSSCKFWSession *fwSession,
    NSSCKFWCryptoOperationState state)
{
#ifdef DEBUG
    CK_RV error = CKR_OK;
    error = nssCKFWSession_verifyPointer(fwSession);
    if (CKR_OK != error) {
        return (NSSCKFWCryptoOperation *)NULL;
    }

    if (state >= NSSCKFWCryptoOperationState_Max) {
        return (NSSCKFWCryptoOperation *)NULL;
    }
#endif /* DEBUG */
    return nssCKFWSession_GetCurrentCryptoOperation(fwSession, state);
}

/*
 * NSSCKFWSession_GetFWSlot
 *
 */

NSS_IMPLEMENT NSSCKFWSlot *
NSSCKFWSession_GetFWSlot(
    NSSCKFWSession *fwSession)
{
    return nssCKFWSession_GetFWSlot(fwSession);
}
