/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: instance.c,v $ $Revision: 1.13 $ $Date: 2012/01/13 17:04:50 $";
#endif /* DEBUG */

/*
 * instance.c
 *
 * This file implements the NSSCKFWInstance type and methods.
 */

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

/*
 * NSSCKFWInstance
 *
 *  -- create/destroy --
 *  nssCKFWInstance_Create
 *  nssCKFWInstance_Destroy
 *
 *  -- public accessors --
 *  NSSCKFWInstance_GetMDInstance
 *  NSSCKFWInstance_GetArena
 *  NSSCKFWInstance_MayCreatePthreads
 *  NSSCKFWInstance_CreateMutex
 *  NSSCKFWInstance_GetConfigurationData
 *  NSSCKFWInstance_GetInitArgs
 *
 *  -- implement public accessors --
 *  nssCKFWInstance_GetMDInstance
 *  nssCKFWInstance_GetArena
 *  nssCKFWInstance_MayCreatePthreads
 *  nssCKFWInstance_CreateMutex
 *  nssCKFWInstance_GetConfigurationData
 *  nssCKFWInstance_GetInitArgs 
 *
 *  -- private accessors --
 *  nssCKFWInstance_CreateSessionHandle
 *  nssCKFWInstance_ResolveSessionHandle
 *  nssCKFWInstance_DestroySessionHandle
 *  nssCKFWInstance_FindSessionHandle
 *  nssCKFWInstance_CreateObjectHandle
 *  nssCKFWInstance_ResolveObjectHandle
 *  nssCKFWInstance_DestroyObjectHandle
 *
 *  -- module fronts --
 *  nssCKFWInstance_GetNSlots
 *  nssCKFWInstance_GetCryptokiVersion
 *  nssCKFWInstance_GetManufacturerID
 *  nssCKFWInstance_GetFlags
 *  nssCKFWInstance_GetLibraryDescription
 *  nssCKFWInstance_GetLibraryVersion
 *  nssCKFWInstance_GetModuleHandlesSessionObjects
 *  nssCKFWInstance_GetSlots
 *  nssCKFWInstance_WaitForSlotEvent
 *
 *  -- debugging versions only --
 *  nssCKFWInstance_verifyPointer
 */

struct NSSCKFWInstanceStr {
  NSSCKFWMutex *mutex;
  NSSArena *arena;
  NSSCKMDInstance *mdInstance;
  CK_C_INITIALIZE_ARGS_PTR pInitArgs;
  CK_C_INITIALIZE_ARGS initArgs;
  CryptokiLockingState LockingState;
  CK_BBOOL mayCreatePthreads;
  NSSUTF8 *configurationData;
  CK_ULONG nSlots;
  NSSCKFWSlot **fwSlotList;
  NSSCKMDSlot **mdSlotList;
  CK_BBOOL moduleHandlesSessionObjects;

  /*
   * Everything above is set at creation time, and then not modified.
   * The invariants the mutex protects are:
   *
   *  1) Each of the cached descriptions (versions, etc.) are in an
   *     internally consistant state.
   *
   *  2) The session handle hashes and count are consistant
   *
   *  3) The object handle hashes and count are consistant.
   *
   * I could use multiple locks, but let's wait to see if that's 
   * really necessary.
   *
   * Note that the calls accessing the cached descriptions will 
   * call the NSSCKMDInstance methods with the mutex locked.  Those
   * methods may then call the public NSSCKFWInstance routines.
   * Those public routines only access the constant data above, so
   * there's no problem.  But be careful if you add to this object;
   * mutexes are in general not reentrant, so don't create deadlock
   * situations.
   */

  CK_VERSION cryptokiVersion;
  NSSUTF8 *manufacturerID;
  NSSUTF8 *libraryDescription;
  CK_VERSION libraryVersion;

  CK_ULONG lastSessionHandle;
  nssCKFWHash *sessionHandleHash;

  CK_ULONG lastObjectHandle;
  nssCKFWHash *objectHandleHash;
};

#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
instance_add_pointer
(
  const NSSCKFWInstance *fwInstance
)
{
  return CKR_OK;
}

static CK_RV
instance_remove_pointer
(
  const NSSCKFWInstance *fwInstance
)
{
  return CKR_OK;
}

NSS_IMPLEMENT CK_RV
nssCKFWInstance_verifyPointer
(
  const NSSCKFWInstance *fwInstance
)
{
  return CKR_OK;
}

#endif /* DEBUG */

/*
 * nssCKFWInstance_Create
 *
 */
NSS_IMPLEMENT NSSCKFWInstance *
nssCKFWInstance_Create
(
  CK_C_INITIALIZE_ARGS_PTR pInitArgs,
  CryptokiLockingState LockingState,
  NSSCKMDInstance *mdInstance,
  CK_RV *pError
)
{
  NSSCKFWInstance *fwInstance;
  NSSArena *arena = (NSSArena *)NULL;
  CK_ULONG i;
  CK_BBOOL called_Initialize = CK_FALSE;

#ifdef NSSDEBUG
  if( (CK_RV)NULL == pError ) {
    return (NSSCKFWInstance *)NULL;
  }

  if (!mdInstance) {
    *pError = CKR_ARGUMENTS_BAD;
    return (NSSCKFWInstance *)NULL;
  }
#endif /* NSSDEBUG */

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

  fwInstance = nss_ZNEW(arena, NSSCKFWInstance);
  if (!fwInstance) {
    goto nomem;
  }

  fwInstance->arena = arena;
  fwInstance->mdInstance = mdInstance;

  fwInstance->LockingState = LockingState;
  if( (CK_C_INITIALIZE_ARGS_PTR)NULL != pInitArgs ) {
    fwInstance->initArgs = *pInitArgs;
    fwInstance->pInitArgs = &fwInstance->initArgs;
    if( pInitArgs->flags & CKF_LIBRARY_CANT_CREATE_OS_THREADS ) {
      fwInstance->mayCreatePthreads = CK_FALSE;
    } else {
      fwInstance->mayCreatePthreads = CK_TRUE;
    }
    fwInstance->configurationData = (NSSUTF8 *)(pInitArgs->pReserved);
  } else {
    fwInstance->mayCreatePthreads = CK_TRUE;
  }

  fwInstance->mutex = nssCKFWMutex_Create(pInitArgs, LockingState, arena,
                                          pError);
  if (!fwInstance->mutex) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto loser;
  }

  if (mdInstance->Initialize) {
    *pError = mdInstance->Initialize(mdInstance, fwInstance, fwInstance->configurationData);
    if( CKR_OK != *pError ) {
      goto loser;
    }

    called_Initialize = CK_TRUE;
  }

  if (mdInstance->ModuleHandlesSessionObjects) {
    fwInstance->moduleHandlesSessionObjects = 
      mdInstance->ModuleHandlesSessionObjects(mdInstance, fwInstance);
  } else {
    fwInstance->moduleHandlesSessionObjects = CK_FALSE;
  }

  if (!mdInstance->GetNSlots) {
    /* That routine is required */
    *pError = CKR_GENERAL_ERROR;
    goto loser;
  }

  fwInstance->nSlots = mdInstance->GetNSlots(mdInstance, fwInstance, pError);
  if( (CK_ULONG)0 == fwInstance->nSlots ) {
    if( CKR_OK == *pError ) {
      /* Zero is not a legitimate answer */
      *pError = CKR_GENERAL_ERROR;
    }
    goto loser;
  }

  fwInstance->fwSlotList = nss_ZNEWARRAY(arena, NSSCKFWSlot *, fwInstance->nSlots);
  if( (NSSCKFWSlot **)NULL == fwInstance->fwSlotList ) {
    goto nomem;
  }

  fwInstance->mdSlotList = nss_ZNEWARRAY(arena, NSSCKMDSlot *, fwInstance->nSlots);
  if( (NSSCKMDSlot **)NULL == fwInstance->mdSlotList ) {
    goto nomem;
  }

  fwInstance->sessionHandleHash = nssCKFWHash_Create(fwInstance, 
    fwInstance->arena, pError);
  if (!fwInstance->sessionHandleHash) {
    goto loser;
  }

  fwInstance->objectHandleHash = nssCKFWHash_Create(fwInstance,
    fwInstance->arena, pError);
  if (!fwInstance->objectHandleHash) {
    goto loser;
  }

  if (!mdInstance->GetSlots) {
    /* That routine is required */
    *pError = CKR_GENERAL_ERROR;
    goto loser;
  }

  *pError = mdInstance->GetSlots(mdInstance, fwInstance, fwInstance->mdSlotList);
  if( CKR_OK != *pError ) {
    goto loser;
  }

  for( i = 0; i < fwInstance->nSlots; i++ ) {
    NSSCKMDSlot *mdSlot = fwInstance->mdSlotList[i];

    if (!mdSlot) {
      *pError = CKR_GENERAL_ERROR;
      goto loser;
    }

    fwInstance->fwSlotList[i] = nssCKFWSlot_Create(fwInstance, mdSlot, i, pError);
    if( CKR_OK != *pError ) {
      CK_ULONG j;

      for( j = 0; j < i; j++ ) {
        (void)nssCKFWSlot_Destroy(fwInstance->fwSlotList[j]);
      }

      for( j = i; j < fwInstance->nSlots; j++ ) {
        NSSCKMDSlot *mds = fwInstance->mdSlotList[j];
        if (mds->Destroy) {
          mds->Destroy(mds, (NSSCKFWSlot *)NULL, mdInstance, fwInstance);
        }
      }

      goto loser;
    }
  }

#ifdef DEBUG
  *pError = instance_add_pointer(fwInstance);
  if( CKR_OK != *pError ) {
    for( i = 0; i < fwInstance->nSlots; i++ ) {
      (void)nssCKFWSlot_Destroy(fwInstance->fwSlotList[i]);
    }
    
    goto loser;
  }
#endif /* DEBUG */

  *pError = CKR_OK;
  return fwInstance;

 nomem:
  *pError = CKR_HOST_MEMORY;
  /*FALLTHROUGH*/
 loser:

  if( CK_TRUE == called_Initialize ) {
    if (mdInstance->Finalize) {
      mdInstance->Finalize(mdInstance, fwInstance);
    }
  }

  if (fwInstance && fwInstance->mutex) {
    nssCKFWMutex_Destroy(fwInstance->mutex);
  }

  if (arena) {
    (void)NSSArena_Destroy(arena);
  }
  return (NSSCKFWInstance *)NULL;
}

/*
 * nssCKFWInstance_Destroy
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWInstance_Destroy
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  CK_RV error = CKR_OK;
#endif /* NSSDEBUG */
  CK_ULONG i;

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

  nssCKFWMutex_Destroy(fwInstance->mutex);

  for( i = 0; i < fwInstance->nSlots; i++ ) {
    (void)nssCKFWSlot_Destroy(fwInstance->fwSlotList[i]);
  }

  if (fwInstance->mdInstance->Finalize) {
    fwInstance->mdInstance->Finalize(fwInstance->mdInstance, fwInstance);
  }

  if (fwInstance->sessionHandleHash) {
     nssCKFWHash_Destroy(fwInstance->sessionHandleHash);
  }

  if (fwInstance->objectHandleHash) {
     nssCKFWHash_Destroy(fwInstance->objectHandleHash);
  }

#ifdef DEBUG
  (void)instance_remove_pointer(fwInstance);
#endif /* DEBUG */

  (void)NSSArena_Destroy(fwInstance->arena);
  return CKR_OK;
}

/*
 * nssCKFWInstance_GetMDInstance
 *
 */
NSS_IMPLEMENT NSSCKMDInstance *
nssCKFWInstance_GetMDInstance
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSCKMDInstance *)NULL;
  }
#endif /* NSSDEBUG */

  return fwInstance->mdInstance;
}

/*
 * nssCKFWInstance_GetArena
 *
 */
NSS_IMPLEMENT NSSArena *
nssCKFWInstance_GetArena
(
  NSSCKFWInstance *fwInstance,
  CK_RV *pError
)
{
#ifdef NSSDEBUG
  if (!pError) {
    return (NSSArena *)NULL;
  }

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

  *pError = CKR_OK;
  return fwInstance->arena;
}

/*
 * nssCKFWInstance_MayCreatePthreads
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWInstance_MayCreatePthreads
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  return fwInstance->mayCreatePthreads;
}

/*
 * nssCKFWInstance_CreateMutex
 *
 */
NSS_IMPLEMENT NSSCKFWMutex *
nssCKFWInstance_CreateMutex
(
  NSSCKFWInstance *fwInstance,
  NSSArena *arena,
  CK_RV *pError
)
{
  NSSCKFWMutex *mutex;

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

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (NSSCKFWMutex *)NULL;
  }
#endif /* NSSDEBUG */

  mutex = nssCKFWMutex_Create(fwInstance->pInitArgs, fwInstance->LockingState,
                              arena, pError);
  if (!mutex) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }

    return (NSSCKFWMutex *)NULL;
  }

  return mutex;
}

/*
 * nssCKFWInstance_GetConfigurationData
 *
 */
NSS_IMPLEMENT NSSUTF8 *
nssCKFWInstance_GetConfigurationData
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSUTF8 *)NULL;
  }
#endif /* NSSDEBUG */

  return fwInstance->configurationData;
}

/*
 * nssCKFWInstance_GetInitArgs
 *
 */
CK_C_INITIALIZE_ARGS_PTR
nssCKFWInstance_GetInitArgs
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (CK_C_INITIALIZE_ARGS_PTR)NULL;
  }
#endif /* NSSDEBUG */

    return fwInstance->pInitArgs;
}

/*
 * nssCKFWInstance_CreateSessionHandle
 *
 */
NSS_IMPLEMENT CK_SESSION_HANDLE
nssCKFWInstance_CreateSessionHandle
(
  NSSCKFWInstance *fwInstance,
  NSSCKFWSession *fwSession,
  CK_RV *pError
)
{
  CK_SESSION_HANDLE hSession;

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

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (CK_SESSION_HANDLE)0;
  }
#endif /* NSSDEBUG */

  *pError = nssCKFWMutex_Lock(fwInstance->mutex);
  if( CKR_OK != *pError ) {
    return (CK_SESSION_HANDLE)0;
  }

  hSession = ++(fwInstance->lastSessionHandle);

  /* Alan would say I should unlock for this call. */
  
  *pError = nssCKFWSession_SetHandle(fwSession, hSession);
  if( CKR_OK != *pError ) {
    goto done;
  }

  *pError = nssCKFWHash_Add(fwInstance->sessionHandleHash, 
              (const void *)hSession, (const void *)fwSession);
  if( CKR_OK != *pError ) {
    hSession = (CK_SESSION_HANDLE)0;
    goto done;
  }

 done:
  nssCKFWMutex_Unlock(fwInstance->mutex);
  return hSession;
}

/*
 * nssCKFWInstance_ResolveSessionHandle
 *
 */
NSS_IMPLEMENT NSSCKFWSession *
nssCKFWInstance_ResolveSessionHandle
(
  NSSCKFWInstance *fwInstance,
  CK_SESSION_HANDLE hSession
)
{
  NSSCKFWSession *fwSession;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSCKFWSession *)NULL;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwInstance->mutex) ) {
    return (NSSCKFWSession *)NULL;
  }

  fwSession = (NSSCKFWSession *)nssCKFWHash_Lookup(
                fwInstance->sessionHandleHash, (const void *)hSession);

  /* Assert(hSession == nssCKFWSession_GetHandle(fwSession)) */

  (void)nssCKFWMutex_Unlock(fwInstance->mutex);

  return fwSession;
}

/*
 * nssCKFWInstance_DestroySessionHandle
 *
 */
NSS_IMPLEMENT void
nssCKFWInstance_DestroySessionHandle
(
  NSSCKFWInstance *fwInstance,
  CK_SESSION_HANDLE hSession
)
{
  NSSCKFWSession *fwSession;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwInstance->mutex) ) {
    return;
  }

  fwSession = (NSSCKFWSession *)nssCKFWHash_Lookup(
                fwInstance->sessionHandleHash, (const void *)hSession);
  if (fwSession) {
    nssCKFWHash_Remove(fwInstance->sessionHandleHash, (const void *)hSession);
    nssCKFWSession_SetHandle(fwSession, (CK_SESSION_HANDLE)0);
  }

  (void)nssCKFWMutex_Unlock(fwInstance->mutex);

  return;
}

/*
 * nssCKFWInstance_FindSessionHandle
 *
 */
NSS_IMPLEMENT CK_SESSION_HANDLE
nssCKFWInstance_FindSessionHandle
(
  NSSCKFWInstance *fwInstance,
  NSSCKFWSession *fwSession
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (CK_SESSION_HANDLE)0;
  }

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

  return nssCKFWSession_GetHandle(fwSession);
  /* look it up and assert? */
}

/*
 * nssCKFWInstance_CreateObjectHandle
 *
 */
NSS_IMPLEMENT CK_OBJECT_HANDLE
nssCKFWInstance_CreateObjectHandle
(
  NSSCKFWInstance *fwInstance,
  NSSCKFWObject *fwObject,
  CK_RV *pError
)
{
  CK_OBJECT_HANDLE hObject;

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

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (CK_OBJECT_HANDLE)0;
  }
#endif /* NSSDEBUG */

  *pError = nssCKFWMutex_Lock(fwInstance->mutex);
  if( CKR_OK != *pError ) {
    return (CK_OBJECT_HANDLE)0;
  }

  hObject = ++(fwInstance->lastObjectHandle);

  *pError = nssCKFWObject_SetHandle(fwObject, hObject);
  if( CKR_OK != *pError ) {
    hObject = (CK_OBJECT_HANDLE)0;
    goto done;
  }

  *pError = nssCKFWHash_Add(fwInstance->objectHandleHash, 
              (const void *)hObject, (const void *)fwObject);
  if( CKR_OK != *pError ) {
    hObject = (CK_OBJECT_HANDLE)0;
    goto done;
  }

 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return hObject;
}

/*
 * nssCKFWInstance_ResolveObjectHandle
 *
 */
NSS_IMPLEMENT NSSCKFWObject *
nssCKFWInstance_ResolveObjectHandle
(
  NSSCKFWInstance *fwInstance,
  CK_OBJECT_HANDLE hObject
)
{
  NSSCKFWObject *fwObject;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSCKFWObject *)NULL;
  }
#endif /* NSSDEBUG */

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

  fwObject = (NSSCKFWObject *)nssCKFWHash_Lookup(
                fwInstance->objectHandleHash, (const void *)hObject);

  /* Assert(hObject == nssCKFWObject_GetHandle(fwObject)) */

  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return fwObject;
}

/*
 * nssCKFWInstance_ReassignObjectHandle
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWInstance_ReassignObjectHandle
(
  NSSCKFWInstance *fwInstance,
  CK_OBJECT_HANDLE hObject,
  NSSCKFWObject *fwObject
)
{
  CK_RV error = CKR_OK;
  NSSCKFWObject *oldObject;

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

  error = nssCKFWMutex_Lock(fwInstance->mutex);
  if( CKR_OK != error ) {
    return error;
  }

  oldObject = (NSSCKFWObject *)nssCKFWHash_Lookup(
                 fwInstance->objectHandleHash, (const void *)hObject);
  if(oldObject) {
    /* Assert(hObject == nssCKFWObject_GetHandle(oldObject) */
    (void)nssCKFWObject_SetHandle(oldObject, (CK_SESSION_HANDLE)0);
    nssCKFWHash_Remove(fwInstance->objectHandleHash, (const void *)hObject);
  }

  error = nssCKFWObject_SetHandle(fwObject, hObject);
  if( CKR_OK != error ) {
    goto done;
  }
  error = nssCKFWHash_Add(fwInstance->objectHandleHash, 
            (const void *)hObject, (const void *)fwObject);

 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return error;
}

/*
 * nssCKFWInstance_DestroyObjectHandle
 *
 */
NSS_IMPLEMENT void
nssCKFWInstance_DestroyObjectHandle
(
  NSSCKFWInstance *fwInstance,
  CK_OBJECT_HANDLE hObject
)
{
  NSSCKFWObject *fwObject;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwInstance->mutex) ) {
    return;
  }

  fwObject = (NSSCKFWObject *)nssCKFWHash_Lookup(
                fwInstance->objectHandleHash, (const void *)hObject);
  if (fwObject) {
    /* Assert(hObject = nssCKFWObject_GetHandle(fwObject)) */
    nssCKFWHash_Remove(fwInstance->objectHandleHash, (const void *)hObject);
    (void)nssCKFWObject_SetHandle(fwObject, (CK_SESSION_HANDLE)0);
  }

  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return;
}

/*
 * nssCKFWInstance_FindObjectHandle
 *
 */
NSS_IMPLEMENT CK_OBJECT_HANDLE
nssCKFWInstance_FindObjectHandle
(
  NSSCKFWInstance *fwInstance,
  NSSCKFWObject *fwObject
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (CK_OBJECT_HANDLE)0;
  }

  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
    return (CK_OBJECT_HANDLE)0;
  }
#endif /* NSSDEBUG */
  
  return nssCKFWObject_GetHandle(fwObject);
}

/*
 * nssCKFWInstance_GetNSlots
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWInstance_GetNSlots
(
  NSSCKFWInstance *fwInstance,
  CK_RV *pError
)
{
#ifdef NSSDEBUG
  if (!pError) {
    return (CK_ULONG)0;
  }

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (CK_ULONG)0;
  }
#endif /* NSSDEBUG */

  *pError = CKR_OK;
  return fwInstance->nSlots;
}  

/*
 * nssCKFWInstance_GetCryptokiVersion
 *
 */
NSS_IMPLEMENT CK_VERSION
nssCKFWInstance_GetCryptokiVersion
(
  NSSCKFWInstance *fwInstance
)
{
  CK_VERSION rv;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    rv.major = rv.minor = 0;
    return rv;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwInstance->mutex) ) {
    rv.major = rv.minor = 0;
    return rv;
  }

  if( (0 != fwInstance->cryptokiVersion.major) ||
      (0 != fwInstance->cryptokiVersion.minor) ) {
    rv = fwInstance->cryptokiVersion;
    goto done;
  }

  if (fwInstance->mdInstance->GetCryptokiVersion) {
    fwInstance->cryptokiVersion = fwInstance->mdInstance->GetCryptokiVersion(
      fwInstance->mdInstance, fwInstance);
  } else {
    fwInstance->cryptokiVersion.major = 2;
    fwInstance->cryptokiVersion.minor = 1;
  }

  rv = fwInstance->cryptokiVersion;

 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return rv;
}

/*
 * nssCKFWInstance_GetManufacturerID
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWInstance_GetManufacturerID
(
  NSSCKFWInstance *fwInstance,
  CK_CHAR manufacturerID[32]
)
{
  CK_RV error = CKR_OK;

#ifdef NSSDEBUG
  if( (CK_CHAR_PTR)NULL == manufacturerID ) {
    return CKR_ARGUMENTS_BAD;
  }

  error = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != error ) {
    return error;
  }
#endif /* NSSDEBUG */

  error = nssCKFWMutex_Lock(fwInstance->mutex);
  if( CKR_OK != error ) {
    return error;
  }

  if (!fwInstance->manufacturerID) {
    if (fwInstance->mdInstance->GetManufacturerID) {
      fwInstance->manufacturerID = fwInstance->mdInstance->GetManufacturerID(
        fwInstance->mdInstance, fwInstance, &error);
      if ((!fwInstance->manufacturerID) && (CKR_OK != error)) {
        goto done;
      }
    } else {
      fwInstance->manufacturerID = (NSSUTF8 *) "";
    }
  }

  (void)nssUTF8_CopyIntoFixedBuffer(fwInstance->manufacturerID, (char *)manufacturerID, 32, ' ');
  error = CKR_OK;

 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return error;
}

/*
 * nssCKFWInstance_GetFlags
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWInstance_GetFlags
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (CK_ULONG)0;
  }
#endif /* NSSDEBUG */

  /* No "instance flags" are yet defined by Cryptoki. */
  return (CK_ULONG)0;
}

/*
 * nssCKFWInstance_GetLibraryDescription
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWInstance_GetLibraryDescription
(
  NSSCKFWInstance *fwInstance,
  CK_CHAR libraryDescription[32]
)
{
  CK_RV error = CKR_OK;

#ifdef NSSDEBUG
  if( (CK_CHAR_PTR)NULL == libraryDescription ) {
    return CKR_ARGUMENTS_BAD;
  }

  error = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != error ) {
    return error;
  }
#endif /* NSSDEBUG */

  error = nssCKFWMutex_Lock(fwInstance->mutex);
  if( CKR_OK != error ) {
    return error;
  }

  if (!fwInstance->libraryDescription) {
    if (fwInstance->mdInstance->GetLibraryDescription) {
      fwInstance->libraryDescription = fwInstance->mdInstance->GetLibraryDescription(
        fwInstance->mdInstance, fwInstance, &error);
      if ((!fwInstance->libraryDescription) && (CKR_OK != error)) {
        goto done;
      }
    } else {
      fwInstance->libraryDescription = (NSSUTF8 *) "";
    }
  }

  (void)nssUTF8_CopyIntoFixedBuffer(fwInstance->libraryDescription, (char *)libraryDescription, 32, ' ');
  error = CKR_OK;

 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return error;
}

/*
 * nssCKFWInstance_GetLibraryVersion
 *
 */
NSS_IMPLEMENT CK_VERSION
nssCKFWInstance_GetLibraryVersion
(
  NSSCKFWInstance *fwInstance
)
{
  CK_VERSION rv;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    rv.major = rv.minor = 0;
    return rv;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwInstance->mutex) ) {
    rv.major = rv.minor = 0;
    return rv;
  }

  if( (0 != fwInstance->libraryVersion.major) ||
      (0 != fwInstance->libraryVersion.minor) ) {
    rv = fwInstance->libraryVersion;
    goto done;
  }

  if (fwInstance->mdInstance->GetLibraryVersion) {
    fwInstance->libraryVersion = fwInstance->mdInstance->GetLibraryVersion(
      fwInstance->mdInstance, fwInstance);
  } else {
    fwInstance->libraryVersion.major = 0;
    fwInstance->libraryVersion.minor = 3;
  }

  rv = fwInstance->libraryVersion;
 done:
  (void)nssCKFWMutex_Unlock(fwInstance->mutex);
  return rv;
}

/*
 * nssCKFWInstance_GetModuleHandlesSessionObjects
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWInstance_GetModuleHandlesSessionObjects
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  return fwInstance->moduleHandlesSessionObjects;
}

/*
 * nssCKFWInstance_GetSlots
 *
 */
NSS_IMPLEMENT NSSCKFWSlot **
nssCKFWInstance_GetSlots
(
  NSSCKFWInstance *fwInstance,
  CK_RV *pError
)
{
#ifdef NSSDEBUG
  if (!pError) {
    return (NSSCKFWSlot **)NULL;
  }

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (NSSCKFWSlot **)NULL;
  }
#endif /* NSSDEBUG */

  return fwInstance->fwSlotList;
}

/*
 * nssCKFWInstance_WaitForSlotEvent
 *
 */
NSS_IMPLEMENT NSSCKFWSlot *
nssCKFWInstance_WaitForSlotEvent
(
  NSSCKFWInstance *fwInstance,
  CK_BBOOL block,
  CK_RV *pError
)
{
  NSSCKFWSlot *fwSlot = (NSSCKFWSlot *)NULL;
  NSSCKMDSlot *mdSlot;
  CK_ULONG i, n;

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

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (NSSCKFWSlot *)NULL;
  }

  switch( block ) {
  case CK_TRUE:
  case CK_FALSE:
    break;
  default:
    *pError = CKR_ARGUMENTS_BAD;
    return (NSSCKFWSlot *)NULL;
  }
#endif /* NSSDEBUG */

  if (!fwInstance->mdInstance->WaitForSlotEvent) {
    *pError = CKR_NO_EVENT;
    return (NSSCKFWSlot *)NULL;
  }

  mdSlot = fwInstance->mdInstance->WaitForSlotEvent(
    fwInstance->mdInstance,
    fwInstance,
    block,
    pError
  );

  if (!mdSlot) {
    return (NSSCKFWSlot *)NULL;
  }

  n = nssCKFWInstance_GetNSlots(fwInstance, pError);
  if( ((CK_ULONG)0 == n) && (CKR_OK != *pError) ) {
    return (NSSCKFWSlot *)NULL;
  }

  for( i = 0; i < n; i++ ) {
    if( fwInstance->mdSlotList[i] == mdSlot ) {
      fwSlot = fwInstance->fwSlotList[i];
      break;
    }
  }

  if (!fwSlot) {
    /* Internal error */
    *pError = CKR_GENERAL_ERROR;
    return (NSSCKFWSlot *)NULL;
  }

  return fwSlot;
}

/*
 * NSSCKFWInstance_GetMDInstance
 *
 */
NSS_IMPLEMENT NSSCKMDInstance *
NSSCKFWInstance_GetMDInstance
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSCKMDInstance *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWInstance_GetMDInstance(fwInstance);
}

/*
 * NSSCKFWInstance_GetArena
 *
 */
NSS_IMPLEMENT NSSArena *
NSSCKFWInstance_GetArena
(
  NSSCKFWInstance *fwInstance,
  CK_RV *pError
)
{
#ifdef DEBUG
  if (!pError) {
    return (NSSArena *)NULL;
  }

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

  return nssCKFWInstance_GetArena(fwInstance, pError);
}

/*
 * NSSCKFWInstance_MayCreatePthreads
 *
 */
NSS_IMPLEMENT CK_BBOOL
NSSCKFWInstance_MayCreatePthreads
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return CK_FALSE;
  }
#endif /* DEBUG */

  return nssCKFWInstance_MayCreatePthreads(fwInstance);
}

/*
 * NSSCKFWInstance_CreateMutex
 *
 */
NSS_IMPLEMENT NSSCKFWMutex *
NSSCKFWInstance_CreateMutex
(
  NSSCKFWInstance *fwInstance,
  NSSArena *arena,
  CK_RV *pError
)
{
#ifdef DEBUG
  if (!pError) {
    return (NSSCKFWMutex *)NULL;
  }

  *pError = nssCKFWInstance_verifyPointer(fwInstance);
  if( CKR_OK != *pError ) {
    return (NSSCKFWMutex *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWInstance_CreateMutex(fwInstance, arena, pError);
}

/*
 * NSSCKFWInstance_GetConfigurationData
 *
 */
NSS_IMPLEMENT NSSUTF8 *
NSSCKFWInstance_GetConfigurationData
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (NSSUTF8 *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWInstance_GetConfigurationData(fwInstance);
}

/*
 * NSSCKFWInstance_GetInitArgs
 *
 */
NSS_IMPLEMENT CK_C_INITIALIZE_ARGS_PTR
NSSCKFWInstance_GetInitArgs
(
  NSSCKFWInstance *fwInstance
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWInstance_verifyPointer(fwInstance) ) {
    return (CK_C_INITIALIZE_ARGS_PTR)NULL;
  }
#endif /* DEBUG */

  return nssCKFWInstance_GetInitArgs(fwInstance);
}

