/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * mutex.c
 *
 * This file implements a mutual-exclusion locking facility for Modules
 * using the NSS Cryptoki Framework.
 */

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

/*
 * NSSCKFWMutex
 *
 *  NSSCKFWMutex_Destroy
 *  NSSCKFWMutex_Lock
 *  NSSCKFWMutex_Unlock
 *
 *  nssCKFWMutex_Create
 *  nssCKFWMutex_Destroy
 *  nssCKFWMutex_Lock
 *  nssCKFWMutex_Unlock
 *
 *  -- debugging versions only --
 *  nssCKFWMutex_verifyPointer
 *
 */

struct NSSCKFWMutexStr {
    PRLock *lock;
};

#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
mutex_add_pointer(
    const NSSCKFWMutex *fwMutex)
{
    return CKR_OK;
}

static CK_RV
mutex_remove_pointer(
    const NSSCKFWMutex *fwMutex)
{
    return CKR_OK;
}

NSS_IMPLEMENT CK_RV
nssCKFWMutex_verifyPointer(
    const NSSCKFWMutex *fwMutex)
{
    return CKR_OK;
}

#endif /* DEBUG */

/*
 * nssCKFWMutex_Create
 *
 */
NSS_EXTERN NSSCKFWMutex *
nssCKFWMutex_Create(
    CK_C_INITIALIZE_ARGS_PTR pInitArgs,
    CryptokiLockingState LockingState,
    NSSArena *arena,
    CK_RV *pError)
{
    NSSCKFWMutex *mutex;

    mutex = nss_ZNEW(arena, NSSCKFWMutex);
    if (!mutex) {
        *pError = CKR_HOST_MEMORY;
        return (NSSCKFWMutex *)NULL;
    }
    *pError = CKR_OK;
    mutex->lock = NULL;
    if (LockingState == MultiThreaded) {
        mutex->lock = PR_NewLock();
        if (!mutex->lock) {
            *pError = CKR_HOST_MEMORY; /* we couldn't get the resource */
        }
    }

    if (CKR_OK != *pError) {
        (void)nss_ZFreeIf(mutex);
        return (NSSCKFWMutex *)NULL;
    }

#ifdef DEBUG
    *pError = mutex_add_pointer(mutex);
    if (CKR_OK != *pError) {
        if (mutex->lock) {
            PR_DestroyLock(mutex->lock);
        }
        (void)nss_ZFreeIf(mutex);
        return (NSSCKFWMutex *)NULL;
    }
#endif /* DEBUG */

    return mutex;
}

/*
 * nssCKFWMutex_Destroy
 *
 */
NSS_EXTERN CK_RV
nssCKFWMutex_Destroy(
    NSSCKFWMutex *mutex)
{
    CK_RV rv = CKR_OK;

#ifdef NSSDEBUG
    rv = nssCKFWMutex_verifyPointer(mutex);
    if (CKR_OK != rv) {
        return rv;
    }
#endif /* NSSDEBUG */

    if (mutex->lock) {
        PR_DestroyLock(mutex->lock);
    }

#ifdef DEBUG
    (void)mutex_remove_pointer(mutex);
#endif /* DEBUG */

    (void)nss_ZFreeIf(mutex);
    return rv;
}

/*
 * nssCKFWMutex_Lock
 *
 */
NSS_EXTERN CK_RV
nssCKFWMutex_Lock(
    NSSCKFWMutex *mutex)
{
#ifdef NSSDEBUG
    CK_RV rv = nssCKFWMutex_verifyPointer(mutex);
    if (CKR_OK != rv) {
        return rv;
    }
#endif /* NSSDEBUG */
    if (mutex->lock) {
        PR_Lock(mutex->lock);
    }

    return CKR_OK;
}

/*
 * nssCKFWMutex_Unlock
 *
 */
NSS_EXTERN CK_RV
nssCKFWMutex_Unlock(
    NSSCKFWMutex *mutex)
{
    PRStatus nrv;
#ifdef NSSDEBUG
    CK_RV rv = nssCKFWMutex_verifyPointer(mutex);

    if (CKR_OK != rv) {
        return rv;
    }
#endif /* NSSDEBUG */

    if (!mutex->lock)
        return CKR_OK;

    nrv = PR_Unlock(mutex->lock);

    /* if unlock fails, either we have a programming error, or we have
     * some sort of hardware failure... in either case return CKR_DEVICE_ERROR.
     */
    return nrv == PR_SUCCESS ? CKR_OK : CKR_DEVICE_ERROR;
}

/*
 * NSSCKFWMutex_Destroy
 *
 */
NSS_EXTERN CK_RV
NSSCKFWMutex_Destroy(
    NSSCKFWMutex *mutex)
{
#ifdef DEBUG
    CK_RV rv = nssCKFWMutex_verifyPointer(mutex);
    if (CKR_OK != rv) {
        return rv;
    }
#endif /* DEBUG */

    return nssCKFWMutex_Destroy(mutex);
}

/*
 * NSSCKFWMutex_Lock
 *
 */
NSS_EXTERN CK_RV
NSSCKFWMutex_Lock(
    NSSCKFWMutex *mutex)
{
#ifdef DEBUG
    CK_RV rv = nssCKFWMutex_verifyPointer(mutex);
    if (CKR_OK != rv) {
        return rv;
    }
#endif /* DEBUG */

    return nssCKFWMutex_Lock(mutex);
}

/*
 * NSSCKFWMutex_Unlock
 *
 */
NSS_EXTERN CK_RV
NSSCKFWMutex_Unlock(
    NSSCKFWMutex *mutex)
{
#ifdef DEBUG
    CK_RV rv = nssCKFWMutex_verifyPointer(mutex);
    if (CKR_OK != rv) {
        return rv;
    }
#endif /* DEBUG */

    return nssCKFWMutex_Unlock(mutex);
}