/* 
 * 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 Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: token.c,v $ $Revision: 1.8 $ $Date: 2002/10/10 22:39:23 $ $Name: FIREFOX_0_10_RELEASE $";
#endif /* DEBUG */

/*
 * token.c
 *
 * This file implements the NSSCKFWToken type and methods.
 */

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

/*
 * NSSCKFWToken
 *
 *  -- create/destroy --
 *  nssCKFWToken_Create
 *  nssCKFWToken_Destroy
 *
 *  -- public accessors --
 *  NSSCKFWToken_GetMDToken
 *  NSSCKFWToken_GetFWSlot
 *  NSSCKFWToken_GetMDSlot
 *  NSSCKFWToken_GetSessionState
 *
 *  -- implement public accessors --
 *  nssCKFWToken_GetMDToken
 *  nssCKFWToken_GetFWSlot
 *  nssCKFWToken_GetMDSlot
 *  nssCKFWToken_GetSessionState
 *  nssCKFWToken_SetSessionState
 *
 *  -- private accessors --
 *  nssCKFWToken_SetSessionState
 *  nssCKFWToken_RemoveSession
 *  nssCKFWToken_CloseAllSessions
 *  nssCKFWToken_GetSessionCount
 *  nssCKFWToken_GetRwSessionCount
 *  nssCKFWToken_GetRoSessionCount
 *  nssCKFWToken_GetSessionObjectHash
 *  nssCKFWToken_GetMDObjectHash
 *  nssCKFWToken_GetObjectHandleHash
 *
 *  -- module fronts --
 *  nssCKFWToken_InitToken
 *  nssCKFWToken_GetLabel
 *  nssCKFWToken_GetManufacturerID
 *  nssCKFWToken_GetModel
 *  nssCKFWToken_GetSerialNumber
 *  nssCKFWToken_GetHasRNG
 *  nssCKFWToken_GetIsWriteProtected
 *  nssCKFWToken_GetLoginRequired
 *  nssCKFWToken_GetUserPinInitialized
 *  nssCKFWToken_GetRestoreKeyNotNeeded
 *  nssCKFWToken_GetHasClockOnToken
 *  nssCKFWToken_GetHasProtectedAuthenticationPath
 *  nssCKFWToken_GetSupportsDualCryptoOperations
 *  nssCKFWToken_GetMaxSessionCount
 *  nssCKFWToken_GetMaxRwSessionCount
 *  nssCKFWToken_GetMaxPinLen
 *  nssCKFWToken_GetMinPinLen
 *  nssCKFWToken_GetTotalPublicMemory
 *  nssCKFWToken_GetFreePublicMemory
 *  nssCKFWToken_GetTotalPrivateMemory
 *  nssCKFWToken_GetFreePrivateMemory
 *  nssCKFWToken_GetHardwareVersion
 *  nssCKFWToken_GetFirmwareVersion
 *  nssCKFWToken_GetUTCTime
 *  nssCKFWToken_OpenSession
 *  nssCKFWToken_GetMechanismCount
 *  nssCKFWToken_GetMechanismTypes
 *  nssCKFWToken_GetMechanism
 */

struct NSSCKFWTokenStr {
  NSSCKFWMutex *mutex;
  NSSArena *arena;
  NSSCKMDToken *mdToken;
  NSSCKFWSlot *fwSlot;
  NSSCKMDSlot *mdSlot;
  NSSCKFWInstance *fwInstance;
  NSSCKMDInstance *mdInstance;

  /*
   * 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 counts and hashes are consistant.
   *
   * 3) The object hashes are consistant.
   *
   * Note that the calls accessing the cached descriptions will call
   * the NSSCKMDToken methods with the mutex locked.  Those methods
   * may then call the public NSSCKFWToken routines.  Those public
   * routines only access the constant data above and the atomic
   * CK_STATE session state variable below, 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.
   */

  NSSUTF8 *label;
  NSSUTF8 *manufacturerID;
  NSSUTF8 *model;
  NSSUTF8 *serialNumber;
  CK_VERSION hardwareVersion;
  CK_VERSION firmwareVersion;

  CK_ULONG sessionCount;
  CK_ULONG rwSessionCount;
  nssCKFWHash *sessions;
  nssCKFWHash *sessionObjectHash;
  nssCKFWHash *mdObjectHash;

  CK_STATE state;
};

#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
token_add_pointer
(
  const NSSCKFWToken *fwToken
)
{
  return CKR_OK;
}

static CK_RV
token_remove_pointer
(
  const NSSCKFWToken *fwToken
)
{
  return CKR_OK;
}

NSS_IMPLEMENT CK_RV
nssCKFWToken_verifyPointer
(
  const NSSCKFWToken *fwToken
)
{
  return CKR_OK;
}

#endif /* DEBUG */

/*
 * nssCKFWToken_Create
 *
 */
NSS_IMPLEMENT NSSCKFWToken *
nssCKFWToken_Create
(
  NSSCKFWSlot *fwSlot,
  NSSCKMDToken *mdToken,
  CK_RV *pError
)
{
  NSSArena *arena = (NSSArena *)NULL;
  NSSCKFWToken *fwToken = (NSSCKFWToken *)NULL;
  CK_BBOOL called_setup = CK_FALSE;

  /*
   * We have already verified the arguments in nssCKFWSlot_GetToken.
   */

  arena = NSSArena_Create();
  if( (NSSArena *)NULL == arena ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  fwToken = nss_ZNEW(arena, NSSCKFWToken);
  if( (NSSCKFWToken *)NULL == fwToken ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }    

  fwToken->arena = arena;
  fwToken->mdToken = mdToken;
  fwToken->fwSlot = fwSlot;
  fwToken->fwInstance = nssCKFWSlot_GetFWInstance(fwSlot);
  fwToken->mdInstance = nssCKFWSlot_GetMDInstance(fwSlot);
  fwToken->state = CKS_RO_PUBLIC_SESSION; /* some default */
  fwToken->sessionCount = 0;
  fwToken->rwSessionCount = 0;

  fwToken->mutex = nssCKFWInstance_CreateMutex(fwToken->fwInstance, arena, pError);
  if( (NSSCKFWMutex *)NULL == fwToken->mutex ) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto loser;
  }

  fwToken->sessions = nssCKFWHash_Create(fwToken->fwInstance, arena, pError);
  if( (nssCKFWHash *)NULL == fwToken->sessions ) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto loser;
  }

  if( CK_TRUE != nssCKFWInstance_GetModuleHandlesSessionObjects(
                   fwToken->fwInstance) ) {
    fwToken->sessionObjectHash = nssCKFWHash_Create(fwToken->fwInstance, 
                                   arena, pError);
    if( (nssCKFWHash *)NULL == fwToken->sessionObjectHash ) {
      if( CKR_OK == *pError ) {
        *pError = CKR_GENERAL_ERROR;
      }
      goto loser;
    }
  }

  fwToken->mdObjectHash = nssCKFWHash_Create(fwToken->fwInstance, 
                            arena, pError);
  if( (nssCKFWHash *)NULL == fwToken->mdObjectHash ) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto loser;
  }

  /* More here */

  if( (void *)NULL != (void *)mdToken->Setup ) {
    *pError = mdToken->Setup(mdToken, fwToken, fwToken->mdInstance, fwToken->fwInstance);
    if( CKR_OK != *pError ) {
      goto loser;
    }
  }

  called_setup = CK_TRUE;

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

  *pError = CKR_OK;
  return fwToken;

 loser:

  if( CK_TRUE == called_setup ) {
    if( (void *)NULL != (void *)mdToken->Invalidate ) {
      mdToken->Invalidate(mdToken, fwToken, fwToken->mdInstance, fwToken->fwInstance);
    }
  }

  if( (NSSArena *)NULL != arena ) {
    (void)NSSArena_Destroy(arena);
  }

  return (NSSCKFWToken *)NULL;
}

static void
nss_ckfwtoken_session_iterator
(
  const void *key,
  void *value,
  void *closure
)
{
  /*
   * Remember that the fwToken->mutex is locked
   */
  NSSCKFWSession *fwSession = (NSSCKFWSession *)value;
  (void)nssCKFWSession_Destroy(fwSession, CK_FALSE);
  return;
}

/*
 * nssCKFWToken_Destroy
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_Destroy
(
  NSSCKFWToken *fwToken
)
{
  CK_RV error = CKR_OK;

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

  (void)nssCKFWMutex_Destroy(fwToken->mutex);
  
  if( (void *)NULL != (void *)fwToken->mdToken->Invalidate ) {
    fwToken->mdToken->Invalidate(fwToken->mdToken, fwToken,
      fwToken->mdInstance, fwToken->fwInstance);
  }
  /* we can destroy the list without locking now because no one else is 
   * referencing us (or _Destroy was invalidly called!)
   */
  nssCKFWHash_Iterate(fwToken->sessions, nss_ckfwtoken_session_iterator, 
								(void *)NULL);
  nssCKFWHash_Destroy(fwToken->sessions);

  if (fwToken->sessionObjectHash) {
    nssCKFWHash_Destroy(fwToken->sessionObjectHash);
  }
  if (fwToken->mdObjectHash) {
    nssCKFWHash_Destroy(fwToken->mdObjectHash);
  }

  nssCKFWSlot_ClearToken(fwToken->fwSlot);
  
#ifdef DEBUG
  error = token_remove_pointer(fwToken);
#endif /* DEBUG */

  (void)NSSArena_Destroy(fwToken->arena);
  return error;
}

/*
 * nssCKFWToken_GetMDToken
 *
 */
NSS_IMPLEMENT NSSCKMDToken *
nssCKFWToken_GetMDToken
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKMDToken *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->mdToken;
}

/*
 * nssCKFWToken_GetArena
 *
 */
NSS_IMPLEMENT NSSArena *
nssCKFWToken_GetArena
(
  NSSCKFWToken *fwToken,
  CK_RV *pError
)
{
#ifdef NSSDEBUG
  if( (CK_RV *)NULL == pError ) {
    return (NSSArena *)NULL;
  }

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

  return fwToken->arena;
}

/*
 * nssCKFWToken_GetFWSlot
 *
 */
NSS_IMPLEMENT NSSCKFWSlot *
nssCKFWToken_GetFWSlot
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKFWSlot *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->fwSlot;
}

/*
 * nssCKFWToken_GetMDSlot
 *
 */
NSS_IMPLEMENT NSSCKMDSlot *
nssCKFWToken_GetMDSlot
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKMDSlot *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->mdSlot;
}

/*
 * nssCKFWToken_GetSessionState
 *
 */
NSS_IMPLEMENT CK_STATE
nssCKFWToken_GetSessionState
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CKS_RO_PUBLIC_SESSION; /* whatever */
  }
#endif /* NSSDEBUG */

  /*
   * BTW, do not lock the token in this method.
   */

  /*
   * Theoretically, there is no state if there aren't any
   * sessions open.  But then we'd need to worry about
   * reporting an error, etc.  What the heck-- let's just
   * revert to CKR_RO_PUBLIC_SESSION as the "default."
   */

  return fwToken->state;
}

/*
 * nssCKFWToken_InitToken
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_InitToken
(
  NSSCKFWToken *fwToken,
  NSSItem *pin,
  NSSUTF8 *label
)
{
  CK_RV error;

#ifdef NSSDEBUG
  error = nssCKFWToken_verifyPointer(fwToken);
  if( CKR_OK != error ) {
    return CKR_ARGUMENTS_BAD;
  }
#endif /* NSSDEBUG */

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

  if( fwToken->sessionCount > 0 ) {
    error = CKR_SESSION_EXISTS;
    goto done;
  }

  if( (void *)NULL == (void *)fwToken->mdToken->InitToken ) {
    error = CKR_DEVICE_ERROR;
    goto done;
  }

  if( (NSSItem *)NULL == pin ) {
    if( nssCKFWToken_GetHasProtectedAuthenticationPath(fwToken) ) {
      ; /* okay */
    } else {
      error = CKR_PIN_INCORRECT;
      goto done;
    }
  }

  if( (NSSUTF8 *)NULL == label ) {
    label = (NSSUTF8 *) "";
  }

  error = fwToken->mdToken->InitToken(fwToken->mdToken, fwToken,
            fwToken->mdInstance, fwToken->fwInstance, pin, label);

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

/*
 * nssCKFWToken_GetLabel
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetLabel
(
  NSSCKFWToken *fwToken,
  CK_CHAR label[32]
)
{
  CK_RV error = CKR_OK;

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

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

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

  if( (NSSUTF8 *)NULL == fwToken->label ) {
    if( (void *)NULL != (void *)fwToken->mdToken->GetLabel ) {
      fwToken->label = fwToken->mdToken->GetLabel(fwToken->mdToken, fwToken,
        fwToken->mdInstance, fwToken->fwInstance, &error);
      if( ((NSSUTF8 *)NULL == fwToken->label) && (CKR_OK != error) ) {
        goto done;
      }
    } else {
      fwToken->label = (NSSUTF8 *) "";
    }
  }

  (void)nssUTF8_CopyIntoFixedBuffer(fwToken->label, (char *)label, 32, ' ');
  error = CKR_OK;

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

/*
 * nssCKFWToken_GetManufacturerID
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetManufacturerID
(
  NSSCKFWToken *fwToken,
  CK_CHAR manufacturerID[32]
)
{
  CK_RV error = CKR_OK;

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

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

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

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

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

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

/*
 * nssCKFWToken_GetModel
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetModel
(
  NSSCKFWToken *fwToken,
  CK_CHAR model[16]
)
{
  CK_RV error = CKR_OK;

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

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

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

  if( (NSSUTF8 *)NULL == fwToken->model ) {
    if( (void *)NULL != (void *)fwToken->mdToken->GetModel ) {
      fwToken->model = fwToken->mdToken->GetModel(fwToken->mdToken, fwToken,
        fwToken->mdInstance, fwToken->fwInstance, &error);
      if( ((NSSUTF8 *)NULL == fwToken->model) && (CKR_OK != error) ) {
        goto done;
      }
    } else {
      fwToken->model = (NSSUTF8 *)"";
    }
  }

  (void)nssUTF8_CopyIntoFixedBuffer(fwToken->model, (char *)model, 16, ' ');
  error = CKR_OK;

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

/*
 * nssCKFWToken_GetSerialNumber
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetSerialNumber
(
  NSSCKFWToken *fwToken,
  CK_CHAR serialNumber[16]
)
{
  CK_RV error = CKR_OK;

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

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

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

  if( (NSSUTF8 *)NULL == fwToken->serialNumber ) {
    if( (void *)NULL != (void *)fwToken->mdToken->GetSerialNumber ) {
      fwToken->serialNumber = fwToken->mdToken->GetSerialNumber(fwToken->mdToken, 
        fwToken, fwToken->mdInstance, fwToken->fwInstance, &error);
      if( ((NSSUTF8 *)NULL == fwToken->serialNumber) && (CKR_OK != error) ) {
        goto done;
      }
    } else {
      fwToken->serialNumber = (NSSUTF8 *)"";
    }
  }

  (void)nssUTF8_CopyIntoFixedBuffer(fwToken->serialNumber, (char *)serialNumber, 16, ' ');
  error = CKR_OK;

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


/*
 * nssCKFWToken_GetHasRNG
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetHasRNG
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetHasRNG ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetHasRNG(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetIsWriteProtected
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetIsWriteProtected
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetIsWriteProtected ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetIsWriteProtected(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetLoginRequired
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetLoginRequired
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetLoginRequired ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetLoginRequired(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetUserPinInitialized
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetUserPinInitialized
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetUserPinInitialized ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetUserPinInitialized(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetRestoreKeyNotNeeded
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetRestoreKeyNotNeeded
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetRestoreKeyNotNeeded ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetRestoreKeyNotNeeded(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetHasClockOnToken
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetHasClockOnToken
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetHasClockOnToken ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetHasClockOnToken(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetHasProtectedAuthenticationPath
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetHasProtectedAuthenticationPath
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetHasProtectedAuthenticationPath ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetHasProtectedAuthenticationPath(fwToken->mdToken, 
    fwToken, fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetSupportsDualCryptoOperations
 *
 */
NSS_IMPLEMENT CK_BBOOL
nssCKFWToken_GetSupportsDualCryptoOperations
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_FALSE;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetSupportsDualCryptoOperations ) {
    return CK_FALSE;
  }

  return fwToken->mdToken->GetSupportsDualCryptoOperations(fwToken->mdToken, 
    fwToken, fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetMaxSessionCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetMaxSessionCount
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMaxSessionCount ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetMaxSessionCount(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetMaxRwSessionCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetMaxRwSessionCount
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMaxRwSessionCount ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetMaxRwSessionCount(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetMaxPinLen
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetMaxPinLen
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMaxPinLen ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetMaxPinLen(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetMinPinLen
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetMinPinLen
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMinPinLen ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetMinPinLen(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetTotalPublicMemory
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetTotalPublicMemory
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetTotalPublicMemory ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetTotalPublicMemory(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetFreePublicMemory
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetFreePublicMemory
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetFreePublicMemory ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetFreePublicMemory(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetTotalPrivateMemory
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetTotalPrivateMemory
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetTotalPrivateMemory ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetTotalPrivateMemory(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetFreePrivateMemory
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetFreePrivateMemory
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CK_UNAVAILABLE_INFORMATION;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetFreePrivateMemory ) {
    return CK_UNAVAILABLE_INFORMATION;
  }

  return fwToken->mdToken->GetFreePrivateMemory(fwToken->mdToken, fwToken, 
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetHardwareVersion
 *
 */
NSS_IMPLEMENT CK_VERSION
nssCKFWToken_GetHardwareVersion
(
  NSSCKFWToken *fwToken
)
{
  CK_VERSION rv;

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

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

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

  if( (void *)NULL != (void *)fwToken->mdToken->GetHardwareVersion ) {
    fwToken->hardwareVersion = fwToken->mdToken->GetHardwareVersion(
      fwToken->mdToken, fwToken, fwToken->mdInstance, fwToken->fwInstance);
  } else {
    fwToken->hardwareVersion.major = 0;
    fwToken->hardwareVersion.minor = 1;
  }

  rv = fwToken->hardwareVersion;

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

/*
 * nssCKFWToken_GetFirmwareVersion
 *
 */
NSS_IMPLEMENT CK_VERSION
nssCKFWToken_GetFirmwareVersion
(
  NSSCKFWToken *fwToken
)
{
  CK_VERSION rv;

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

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

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

  if( (void *)NULL != (void *)fwToken->mdToken->GetFirmwareVersion ) {
    fwToken->firmwareVersion = fwToken->mdToken->GetFirmwareVersion(
      fwToken->mdToken, fwToken, fwToken->mdInstance, fwToken->fwInstance);
  } else {
    fwToken->firmwareVersion.major = 0;
    fwToken->firmwareVersion.minor = 1;
  }

  rv = fwToken->firmwareVersion;

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

/*
 * nssCKFWToken_GetUTCTime
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetUTCTime
(
  NSSCKFWToken *fwToken,
  CK_CHAR utcTime[16]
)
{
  CK_RV error = CKR_OK;

#ifdef NSSDEBUG
  error = nssCKFWToken_verifyPointer(fwToken);
  if( CKR_OK != error ) {
    return error;
  }

  if( (CK_CHAR_PTR)NULL == utcTime ) {
    return CKR_ARGUMENTS_BAD;
  }
#endif /* DEBUG */

  if( CK_TRUE != nssCKFWToken_GetHasClockOnToken(fwToken) ) {
    /* return CKR_DEVICE_ERROR; */
    (void)nssUTF8_CopyIntoFixedBuffer((NSSUTF8 *)NULL, (char *)utcTime, 16, ' ');
    return CKR_OK;
  }

  if( (void *)NULL == (void *)fwToken->mdToken->GetUTCTime ) {
    /* It said it had one! */
    return CKR_GENERAL_ERROR;
  }

  error = fwToken->mdToken->GetUTCTime(fwToken->mdToken, fwToken, 
            fwToken->mdInstance, fwToken->fwInstance, utcTime);
  if( CKR_OK != error ) {
    return error;
  }

  /* Sanity-check the data */
  {
    /* Format is YYYYMMDDhhmmss00 */
    int i;
    int Y, M, D, h, m, s, z;
    static int dims[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    for( i = 0; i < 16; i++ ) {
      if( (utcTime[i] < '0') || (utcTime[i] > '9') ) {
        goto badtime;
      }
    }

    Y = ((utcTime[ 0] - '0') * 1000) + ((utcTime[1] - '0') * 100) +
        ((utcTime[ 2] - '0') * 10) + (utcTime[ 3] - '0');
    M = ((utcTime[ 4] - '0') * 10) + (utcTime[ 5] - '0');
    D = ((utcTime[ 6] - '0') * 10) + (utcTime[ 7] - '0');
    h = ((utcTime[ 8] - '0') * 10) + (utcTime[ 9] - '0');
    m = ((utcTime[10] - '0') * 10) + (utcTime[11] - '0');
    s = ((utcTime[12] - '0') * 10) + (utcTime[13] - '0');
    z = ((utcTime[14] - '0') * 10) + (utcTime[15] - '0');

    if( (Y < 1990) || (Y > 3000) ) goto badtime; /* Y3K problem.  heh heh heh */
    if( (M < 1) || (M > 12) ) goto badtime;
    if( (D < 1) || (D > 31) ) goto badtime;

    if( D > dims[M-1] ) goto badtime; /* per-month check */
    if( (2 == M) && (((Y%4)||!(Y%100))&&(Y%400)) && (D > 28) ) goto badtime; /* leap years */

    if( (h < 0) || (h > 23) ) goto badtime;
    if( (m < 0) || (m > 60) ) goto badtime;
    if( (s < 0) || (s > 61) ) goto badtime;

    /* 60m and 60 or 61s is only allowed for leap seconds. */
    if( (60 == m) || (s >= 60) ) {
      if( (23 != h) || (60 != m) || (s < 60) ) goto badtime;
      /* leap seconds can only happen on June 30 or Dec 31.. I think */
      /* if( ((6 != M) || (30 != D)) && ((12 != M) || (31 != D)) ) goto badtime; */
    }
  }

  return CKR_OK;

 badtime:
  return CKR_GENERAL_ERROR;
}

/*
 * nssCKFWToken_OpenSession
 *
 */
NSS_IMPLEMENT NSSCKFWSession *
nssCKFWToken_OpenSession
(
  NSSCKFWToken *fwToken,
  CK_BBOOL rw,
  CK_VOID_PTR pApplication,
  CK_NOTIFY Notify,
  CK_RV *pError
)
{
  NSSCKFWSession *fwSession = (NSSCKFWSession *)NULL;
  NSSCKMDSession *mdSession;

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

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

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

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

  if( CK_TRUE == rw ) {
    /* Read-write session desired */
    if( CK_TRUE == nssCKFWToken_GetIsWriteProtected(fwToken) ) {
      *pError = CKR_TOKEN_WRITE_PROTECTED;
      goto done;
    }
  } else {
    /* Read-only session desired */
    if( CKS_RW_SO_FUNCTIONS == nssCKFWToken_GetSessionState(fwToken) ) {
      *pError = CKR_SESSION_READ_WRITE_SO_EXISTS;
      goto done;
    }
  }

  /* We could compare sesion counts to any limits we know of, I guess.. */

  if( (void *)NULL == (void *)fwToken->mdToken->OpenSession ) {
    /*
     * I'm not sure that the Module actually needs to implement
     * mdSessions -- the Framework can keep track of everything 
     * needed, really.  But I'll sort out that detail later..
     */
    *pError = CKR_GENERAL_ERROR;
    goto done;
  }

  fwSession = nssCKFWSession_Create(fwToken, rw, pApplication, Notify, pError);
  if( (NSSCKFWSession *)NULL == fwSession ) {
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto done;
  }

  mdSession = fwToken->mdToken->OpenSession(fwToken->mdToken, fwToken,
                fwToken->mdInstance, fwToken->fwInstance, fwSession,
                rw, pError);
  if( (NSSCKMDSession *)NULL == mdSession ) {
    (void)nssCKFWSession_Destroy(fwSession, CK_FALSE);
    if( CKR_OK == *pError ) {
      *pError = CKR_GENERAL_ERROR;
    }
    goto done;
  }

  *pError = nssCKFWSession_SetMDSession(fwSession, mdSession);
  if( CKR_OK != *pError ) {
    if( (void *)NULL != (void *)mdSession->Close ) {
      mdSession->Close(mdSession, fwSession, fwToken->mdToken, fwToken,
      fwToken->mdInstance, fwToken->fwInstance);
    }
    (void)nssCKFWSession_Destroy(fwSession, CK_FALSE);
    goto done;
  }

  *pError = nssCKFWHash_Add(fwToken->sessions, fwSession, fwSession);
  if( CKR_OK != *pError ) {
    (void)nssCKFWSession_Destroy(fwSession, CK_FALSE);
    fwSession = (NSSCKFWSession *)NULL;
    goto done;
  }

 done:
  (void)nssCKFWMutex_Unlock(fwToken->mutex);
  return fwSession;
}

/*
 * nssCKFWToken_GetMechanismCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetMechanismCount
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return 0;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMechanismCount ) {
    return 0;
  }

  return fwToken->mdToken->GetMechanismCount(fwToken->mdToken, fwToken,
    fwToken->mdInstance, fwToken->fwInstance);
}

/*
 * nssCKFWToken_GetMechanismTypes
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_GetMechanismTypes
(
  NSSCKFWToken *fwToken,
  CK_MECHANISM_TYPE types[]
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CKR_ARGUMENTS_BAD;
  }

  if( (CK_MECHANISM_TYPE *)NULL == types ) {
    return CKR_ARGUMENTS_BAD;
  }
#endif /* NSSDEBUG */

  if( (void *)NULL == (void *)fwToken->mdToken->GetMechanismTypes ) {
    /*
     * This should only be called with a sufficiently-large
     * "types" array, which can only be done if GetMechanismCount
     * is implemented.  If that's implemented (and returns nonzero),
     * then this should be too.  So return an error.
     */
    return CKR_GENERAL_ERROR;
  }

  return fwToken->mdToken->GetMechanismTypes(fwToken->mdToken, fwToken,
    fwToken->mdInstance, fwToken->fwInstance, types);
}


/*
 * nssCKFWToken_GetMechanism
 *
 */
NSS_IMPLEMENT NSSCKFWMechanism *
nssCKFWToken_GetMechanism
(
  NSSCKFWToken *fwToken,
  CK_MECHANISM_TYPE which,
  CK_RV *pError
)
{
  /* XXX fgmr */
  return (NSSCKFWMechanism *)NULL;
}

NSS_IMPLEMENT CK_RV
nssCKFWToken_SetSessionState
(
  NSSCKFWToken *fwToken,
  CK_STATE newState
)
{
  CK_RV error = CKR_OK;

#ifdef NSSDEBUG
  error = nssCKFWToken_verifyPointer(fwToken);
  if( CKR_OK != error ) {
    return error;
  }

  switch( newState ) {
  case CKS_RO_PUBLIC_SESSION:
  case CKS_RO_USER_FUNCTIONS:
  case CKS_RW_PUBLIC_SESSION:
  case CKS_RW_USER_FUNCTIONS:
  case CKS_RW_SO_FUNCTIONS:
    break;
  default:
    return CKR_ARGUMENTS_BAD;
  }
#endif /* NSSDEBUG */

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

  fwToken->state = newState;
  (void)nssCKFWMutex_Unlock(fwToken->mutex);
  return CKR_OK;
}

/*
 * nssCKFWToken_RemoveSession
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_RemoveSession
(
  NSSCKFWToken *fwToken,
  NSSCKFWSession *fwSession
)
{
  CK_RV error = CKR_OK;

#ifdef NSSDEBUG
  error = nssCKFWToken_verifyPointer(fwToken);
  if( CKR_OK != error ) {
    return error;
  }

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

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

  if( CK_TRUE != nssCKFWHash_Exists(fwToken->sessions, fwSession) ) {
    error = CKR_SESSION_HANDLE_INVALID;
    goto done;
  }

  nssCKFWHash_Remove(fwToken->sessions, fwSession);
  fwToken->sessionCount--;

  if( nssCKFWSession_IsRWSession(fwSession) ) {
    fwToken->rwSessionCount--;
  }

  if( 0 == fwToken->sessionCount ) {
    fwToken->rwSessionCount = 0; /* sanity */
    fwToken->state = CKS_RO_PUBLIC_SESSION; /* some default */
  }

  error = CKR_OK;

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


/*
 * nssCKFWToken_CloseAllSessions
 *
 */
NSS_IMPLEMENT CK_RV
nssCKFWToken_CloseAllSessions
(
  NSSCKFWToken *fwToken
)
{
  CK_RV error = CKR_OK;

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

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

  nssCKFWHash_Iterate(fwToken->sessions, nss_ckfwtoken_session_iterator, (void *)NULL);

  nssCKFWHash_Destroy(fwToken->sessions);

  fwToken->sessions = nssCKFWHash_Create(fwToken->fwInstance, fwToken->arena, &error);
  if( (nssCKFWHash *)NULL == fwToken->sessions ) {
    if( CKR_OK == error ) {
      error = CKR_GENERAL_ERROR;
    }
    goto done;
  }

  fwToken->state = CKS_RO_PUBLIC_SESSION; /* some default */
  fwToken->sessionCount = 0;
  fwToken->rwSessionCount = 0;

  error = CKR_OK;

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

/*
 * nssCKFWToken_GetSessionCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetSessionCount
(
  NSSCKFWToken *fwToken
)
{
  CK_ULONG rv;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (CK_ULONG)0;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwToken->mutex) ) {
    return (CK_ULONG)0;
  }

  rv = fwToken->sessionCount;
  (void)nssCKFWMutex_Unlock(fwToken->mutex);
  return rv;
}

/*
 * nssCKFWToken_GetRwSessionCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetRwSessionCount
(
  NSSCKFWToken *fwToken
)
{
  CK_ULONG rv;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (CK_ULONG)0;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwToken->mutex) ) {
    return (CK_ULONG)0;
  }

  rv = fwToken->rwSessionCount;
  (void)nssCKFWMutex_Unlock(fwToken->mutex);
  return rv;
}

/*
 * nssCKFWToken_GetRoSessionCount
 *
 */
NSS_IMPLEMENT CK_ULONG
nssCKFWToken_GetRoSessionCount
(
  NSSCKFWToken *fwToken
)
{
  CK_ULONG rv;

#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (CK_ULONG)0;
  }
#endif /* NSSDEBUG */

  if( CKR_OK != nssCKFWMutex_Lock(fwToken->mutex) ) {
    return (CK_ULONG)0;
  }

  rv = fwToken->sessionCount - fwToken->rwSessionCount;
  (void)nssCKFWMutex_Unlock(fwToken->mutex);
  return rv;
}

/*
 * nssCKFWToken_GetSessionObjectHash
 *
 */
NSS_IMPLEMENT nssCKFWHash *
nssCKFWToken_GetSessionObjectHash
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (nssCKFWHash *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->sessionObjectHash;
}

/*
 * nssCKFWToken_GetMDObjectHash
 *
 */
NSS_IMPLEMENT nssCKFWHash *
nssCKFWToken_GetMDObjectHash
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (nssCKFWHash *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->mdObjectHash;
}

/*
 * nssCKFWToken_GetObjectHandleHash
 *
 */
NSS_IMPLEMENT nssCKFWHash *
nssCKFWToken_GetObjectHandleHash
(
  NSSCKFWToken *fwToken
)
{
#ifdef NSSDEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (nssCKFWHash *)NULL;
  }
#endif /* NSSDEBUG */

  return fwToken->mdObjectHash;
}

/*
 * NSSCKFWToken_GetMDToken
 *
 */

NSS_IMPLEMENT NSSCKMDToken *
NSSCKFWToken_GetMDToken
(
  NSSCKFWToken *fwToken
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKMDToken *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWToken_GetMDToken(fwToken);
}

/*
 * NSSCKFWToken_GetArena
 *
 */

NSS_IMPLEMENT NSSArena *
NSSCKFWToken_GetArena
(
  NSSCKFWToken *fwToken,
  CK_RV *pError
)
{
#ifdef DEBUG
  if( (CK_RV *)NULL == pError ) {
    return (NSSArena *)NULL;
  }

  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    *pError = CKR_ARGUMENTS_BAD;
    return (NSSArena *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWToken_GetArena(fwToken, pError);
}

/*
 * NSSCKFWToken_GetFWSlot
 *
 */

NSS_IMPLEMENT NSSCKFWSlot *
NSSCKFWToken_GetFWSlot
(
  NSSCKFWToken *fwToken
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKFWSlot *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWToken_GetFWSlot(fwToken);
}

/*
 * NSSCKFWToken_GetMDSlot
 *
 */

NSS_IMPLEMENT NSSCKMDSlot *
NSSCKFWToken_GetMDSlot
(
  NSSCKFWToken *fwToken
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return (NSSCKMDSlot *)NULL;
  }
#endif /* DEBUG */

  return nssCKFWToken_GetMDSlot(fwToken);
}

/*
 * NSSCKFWToken_GetSessionState
 *
 */

NSS_IMPLEMENT CK_STATE
NSSCKFWSession_GetSessionState
(
  NSSCKFWToken *fwToken
)
{
#ifdef DEBUG
  if( CKR_OK != nssCKFWToken_verifyPointer(fwToken) ) {
    return CKS_RO_PUBLIC_SESSION;
  }
#endif /* DEBUG */

  return nssCKFWToken_GetSessionState(fwToken);
}
