#include "xmlrpc_config.h"
#include <assert.h>
#include <pthread.h>
#include <openssl/crypto.h>

#include "mallocvar.h"

#include "xmlrpc-c/string_int.h"

#include "xmlrpc-c/openssl_thread.h"


/* These ugly global variables are necessary and appropriate because our
   purpose is to manipulate OpenSSL's ugly global state.
*/

static pthread_mutex_t * opensslMutex;
    /* Dynamically allocated array with an entry for each lock OpenSSL
       has created.  opensslMutex[N] is the mutex for the lock known by
       OpenSSL with ID N.
    */

static unsigned int maxLockCt;



#pragma GCC diagnostic push
/* Openssl library may define CRYPTO_set_locking_callback to expand to nothing,
   in which case the following will be unreferenced.
*/
#pragma GCC diagnostic ignored "-Wunused-function"

static void
lock(int          const mode,
     int          const lockId,
     const char * const fileName ATTR_UNUSED,
     int          const lineNum ATTR_UNUSED) {
/*----------------------------------------------------------------------------
   This is a locking function for OpenSSL to call when it wants to lock or
   unlock a lock.
-----------------------------------------------------------------------------*/
    assert(lockId >= 0);
    assert((unsigned int)lockId < maxLockCt);

    pthread_mutex_t * const mutexP = &opensslMutex[lockId];

    if (mode & CRYPTO_LOCK)
        pthread_mutex_lock(mutexP);
    else
        pthread_mutex_unlock(mutexP);
}

#pragma GCC diagnostic pop



#pragma GCC diagnostic push
/* Openssl library may define CRYPTO_set_id_callback to expand to nothing,
   in which case the following will be unreferenced.
*/
#pragma GCC diagnostic ignored "-Wunused-function"

static unsigned long
id(void) {
/*----------------------------------------------------------------------------
   This is an ID function for OpenSSL to call when it wants a unique
   identifier of the executing thread.
-----------------------------------------------------------------------------*/
    return ((unsigned long)pthread_self());
}

#pragma GCC diagnostic pop



void
xmlrpc_openssl_thread_setup(const char ** const errorP) {
/*----------------------------------------------------------------------------
   Set up the OpenSSL library to handle being called by multiple threads
   concurrently.

   This setup is process-global, so this must be called exactly once in the
   program (ergo a subroutine library cannot normally call this under the
   covers; if a subroutine library want to use OpenSSL in a thread-safe
   manner, it must depend upon its user to call this).

   This needs to be called as the program starts up, when it is only one
   thread.

   (But this need not be called at all in a program that will never call
   OpenSSL functions from multiple threads).
-----------------------------------------------------------------------------*/
    maxLockCt = CRYPTO_num_locks();

    MALLOCARRAY(opensslMutex, maxLockCt);

    if (!opensslMutex)
        xmlrpc_asprintf(errorP, "Failed to allocate an array for %u "
                        "potential OpenSSL locks", maxLockCt);
    else {
        *errorP = NULL;

        unsigned int i;

        for (i = 0;  i < maxLockCt;  ++i)
            pthread_mutex_init(&opensslMutex[i], NULL);

        CRYPTO_set_id_callback(id);

        CRYPTO_set_locking_callback(lock);
    }
}



void
xmlrpc_openssl_thread_cleanup() {
/*----------------------------------------------------------------------------
   Undo 'xmlrpc_openssl_thread_setup'.

   This should be called at the end of a program, when there is only one
   thread left.

   (As a practical matter, there's really no need to call this at all; if the
   program is just going to exit afterward, any cleanup we do is moot).
-----------------------------------------------------------------------------*/
    CRYPTO_set_locking_callback(NULL);

    CRYPTO_set_id_callback(NULL);

    {
        unsigned int i;

        for (i = 0;  i < maxLockCt; ++i)
            pthread_mutex_destroy(&opensslMutex[i]);
    }
    free(opensslMutex);
}



