/*==============================================================================================*\
| Builds/Runs on SOLARIS only!
|
| Compile and link as:
|
| 	CC -I$OPENSSLDIR/include -L$OPENSSLDIR/lib -mt -norunpath -I- -DDEBUG -g -D_REENTRANT -DNO_RSA -DNO_RC4 -DNO_RC5 -DNO_BF -DNO_IDEA -I. -lcrypto -lssl -c tst.cpp
| 	CC -o tst tst.o -L$OPENSSLDIR/lib -lssl -lcrypto -lsocket -mt 
|
| Run 'tst' to casue threads to crash.
|-----------------------------------------------------------------------------------------------
|
| !!!! NOTE !!!!
|
|	Use Editor set with HARD TABS at 4 spaces apart or this is unreadable!! 
|
\*==============================================================================================*/
#include <pthread.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <unistd.h>

#include "openssl/crypto.h"
#include "openssl/ssl.h"
#include "openssl/err.h"

/*----------------------------------------------------------------------------------------------*\
| __cCritSec class [mutexes]
\*----------------------------------------------------------------------------------------------*/
class __cCritSec
{
	public:
		__cCritSec () {pthread_mutex_init (&_mutex, NULL);};
		void Enter () {pthread_mutex_lock (&_mutex);};
		void Leave () {pthread_mutex_unlock (&_mutex);};
		pthread_mutex_t 	_mutex;
};

/*----------------------------------------------------------------------------------------------*\
| Some static data for statistics,e tc.
\*----------------------------------------------------------------------------------------------*/
static __cCritSec 	_cCritSec;
static int 			_iThreadCnt;
static int 			_iTotThreads;

/*----------------------------------------------------------------------------------------------*\
| Sleep_us - high-granularity thread-sleep function (accuracy & precision are platform dependent)
\*----------------------------------------------------------------------------------------------*/
Sleep_us (int iMicroseconds)
{
   	struct timeval  tval;
   	tval.tv_sec = iMicroseconds / 1000000;
   	tval.tv_usec = iMicroseconds % 1000000;
   	select(0, NULL, NULL, NULL, &tval);
   	return (0);
}

/*----------------------------------------------------------------------------------------------*\
| Locking callback routines as prescribed by OpenSSL Docs/examples
\*----------------------------------------------------------------------------------------------*/
static __cCritSec ** pcCSCrypto;

extern "C" void crypto_locking_callback(int mode, int type, const char *file, int line)
{
	if (mode & CRYPTO_LOCK)
	{
		pcCSCrypto[type]->Enter ();
	}
	else
	{
		pcCSCrypto[type]->Leave ();
	}
}

void thread_setup (void)
{
	pcCSCrypto = (__cCritSec **)malloc (CRYPTO_num_locks() * sizeof(__cCritSec *));
	for (int i = 0; i < CRYPTO_num_locks (); i++)
	{
		pcCSCrypto[i] = new __cCritSec ();
	}
	CRYPTO_set_locking_callback (crypto_locking_callback);
}

/*----------------------------------------------------------------------------------------------*\
| ThreadRootStartingPoint
| 
| This is the bootstrap for the __cThread class;  it's called by the C library's "create a thread"  
\*----------------------------------------------------------------------------------------------*/
extern "C" void *
ThreadMain	(		void 					* pNothing)
{
	Sleep_us (1000);

	char buf[256];

	int iErr = ERR_get_error ();
	ERR_error_string (iErr, buf);
	ERR_reason_error_string (iErr);
	ERR_remove_state (0);

	_cCritSec.Enter ();
		--_iThreadCnt;
	_cCritSec.Leave ();

	return (0);
}

/*----------------------------------------------------------------------------------------------*\
| main
\*----------------------------------------------------------------------------------------------*/
main ()
{	
	int iTargetNum = 80;
	pthread_attr_t		_threadAttr;

	SSL_library_init ();
	SSL_load_error_strings();
	// This is apparently yet another string set to load.  Not sure.  Taken from examples.
	ERR_load_ERR_strings();

	// Set up the locking callbacks
	thread_setup ();

	pthread_attr_init (&_threadAttr);
	pthread_attr_setdetachstate (&_threadAttr, PTHREAD_CREATE_DETACHED);

	//-------------------------------------------------------------------------------------------
	// just keep starting new threads as old ones die off, attempting to keep 'iTargetNum' of 
	// them going at once. 
 	int iCnt;
	while (1)
	{
		_cCritSec.Enter (); 
		iCnt = _iThreadCnt;
		_cCritSec.Leave ();
		if (iCnt < iTargetNum)
		{
			_cCritSec.Enter (); 
				++_iThreadCnt; 
				++_iTotThreads;
			_cCritSec.Leave ();

			if (pthread_create (NULL, &_threadAttr, ThreadMain, NULL))
			{
				printf ("\nERROR CREATING THREAD!\n");
			}
			// Don't CritSec these accesses - we don't really care if they're exactly correct.
			printf ("#%d/%d ", _iThreadCnt, _iTotThreads);
		}
		else
		{
			// Just Poll the thread counter by waiting a bit for some threads to die...
			printf (".");
  			Sleep_us (10);
  		}
	}	
}
