/* ***** 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: session.c,v $ $Revision: 1.12.46.1 $ $Date: 2009/07/31 02:16:06 $";
#endif /* DEBUG */

/*
 * 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
 *
 *  -- implement public accessors --
 *  nssCKFWSession_GetMDSession
 *  nssCKFWSession_GetArena
 *  nssCKFWSession_CallNotification
 *  nssCKFWSession_IsRWSession
 *  nssCKFWSession_IsSO
 *
 *  -- private accessors --
 *  nssCKFWSession_GetSlot
 *  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,
  NSSItem *newPin,
  NSSItem *oldPin
)
{
  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, newPin, oldPin);

  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(arena, 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(arena, 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 i, j, n, newLength, k;
    CK_ATTRIBUTE_TYPE_PTR oldTypes;
    NSSCKFWObject *rv;
    
    tmpArena = NSSArena_Create();
    if (!tmpArena) {
      *pError = CKR_HOST_MEMORY;
      return (NSSCKFWObject *)NULL;
    }

    n = nssCKFWObject_GetAttributeCount(fwObject, pError);
    if( (0 == n) && (CKR_OK != *pError) ) {
      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);
}
