/*
 * The contents of this file are subject to the AOLserver 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://aolserver.com/.
 *
 * 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 AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of 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
 * License, 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 License or the GPL.
 */


/* 
 * random.c --
 *
 *	This file implements the "ns_rand" command.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/random.c,v 1.14 2003/01/18 19:24:20 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * Local functions defined in this file
 */

static Ns_ThreadProc CounterThread;
static unsigned long TrueRand(void);
static unsigned long Roulette(void);

/*
 * Static variables used by Ns_GenSeeds to generate array of random
 * by utilizing the random nature of the thread scheduler.
 */

static volatile unsigned long counter;  /* Counter in counting thread */
static volatile char fRun; /* Flag for counting thread outer loop. */
static volatile char fCount; /* Flag for counting thread inner loop. */
static Ns_Sema sema;	/* Semaphore that controls counting threads. */

/*
 * Critical section around initial and subsequent seed generation.
 */

static Ns_Cs lock;
static volatile int initialized;


/*
 *----------------------------------------------------------------------
 *
 * NsTclRandObjCmd --
 *
 *	This procedure implements the AOLserver Tcl 
 *
 *	    ns_rand ?maximum?
 *
 *	command.  
 *
 * Results:
 *	The Tcl result string contains a random number, either a
 *	double >= 0.0 and < 1.0 or a integer >= 0 and < max.
 *
 * Side effects:
 *	None external.
 *
 * Note:
 *	Interpreters share the static variables which randomizes the
 *	the random numbers even more.
 *
 *----------------------------------------------------------------------
 */

int
NsTclRandObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
    double d;
    int max;
    Tcl_Obj *result;

    if (objc > 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "?maximum?");
	return TCL_ERROR;
    }
    if (objc == 2) {
    	if (Tcl_GetIntFromObj(interp, objv[1], &max) != TCL_OK) {
	    return TCL_ERROR;
	} else if (max <= 0) {
	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "invalid max \"", 
		    Tcl_GetString(objv[1]), "\": must be > 0", NULL);
	    return TCL_ERROR;
	}
    }
    result = Tcl_GetObjResult(interp);
    d = Ns_DRand();
    if (objc == 1) {
	Tcl_SetDoubleObj(result, d);
    } else {
	Tcl_SetIntObj(result, (int) (d * max));
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_DRand --
 * 
 *	Return a random double value between 0 and 1.0.
 * 	
 * Results:
 *  	Random double.
 *
 * Side effects:
 * 	Will generate random seed on first call.
 *
 *----------------------------------------------------------------------
 */

double
Ns_DRand(void)
{
    if (!initialized) {
	Ns_CsEnter(&lock);
	if (!initialized) {
	    unsigned long seed;
	    Ns_GenSeeds(&seed, 1);
#ifdef HAVE_DRAND48
    	    srand48((long) seed);
#elif defined(HAVE_RANDOM)
    	    srandom((unsigned int) seed);
#else
    	    srand((unsigned int) seed);
#endif
	    initialized = 1;
	}
	Ns_CsLeave(&lock);
    }
#if HAVE_DRAND48
    return drand48();
#elif HAVE_RANDOM
    return ((double) random() / (LONG_MAX + 1.0));
#else
    return ((double) rand() / (RAND_MAX + 1.0));
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_GenSeeds --
 * 
 *	Calculate an array of random seeds used by both Ns_DRand() and
 *  	the old SSL module.
 * 	
 * Results:
 *  	None.
 *
 * Side effects:
 * 	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_GenSeeds(unsigned long *seedsPtr, int nseeds)
{
    Ns_Thread thr;
    
    Ns_Log(Notice, "random: generating %d seed%s", nseeds,
	nseeds == 1 ? "" : "s");
    Ns_CsEnter(&lock);
    Ns_SemaInit(&sema, 0);
    fRun = 1;
    Ns_ThreadCreate(CounterThread, NULL, 0, &thr);
    while (nseeds-- > 0) {
    	*seedsPtr++ = TrueRand();
    }
    fRun = 0;
    Ns_SemaPost(&sema, 1);
    Ns_ThreadJoin(&thr, NULL);
    Ns_SemaDestroy(&sema);
    Ns_CsLeave(&lock);
}

/*
 *----------------------------------------------------------------------
 *
 * CounterThread --
 *
 *	Generate a random seed.  This routine runs as a separate thread 
 *	where it imcrements a counter some indeterminate number of times. 
 *	The assumption is that this thread runs for a sufficiently long time
 * 	to be preempted an arbitrary number of times by the kernel threads 
 *	scheduler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
CounterThread(void *ignored)
{
    while (fRun) {
        Ns_SemaWait(&sema);
        if (fRun) {
	    while (fCount) {
		counter++;
            }
        }
    }
}

/*
 *==========================================================================
 * AT&T Seed Generation Code
 *==========================================================================
 *
 * The authors of this software are Don Mitchell and Matt Blaze.
 *              Copyright (c) 1995 by AT&T.
 * Permission to use, copy, and modify this software without fee
 * is hereby granted, provided that this entire notice is included in
 * all copies of any software which is or includes a copy or
 * modification of this software and in all copies of the supporting
 * documentation for such software.
 *
 * This software may be subject to United States export controls.
 *
 * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
 * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
 * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
 */

#define MSEC_TO_COUNT 31  /* Duration of thread counting in milliseconds. */
#define ROULETTE_PRE_ITERS 10

static unsigned long
TrueRand(void)
{
    int i;

    for (i = 0; i < ROULETTE_PRE_ITERS; i++) {
	Roulette();
    }
    return Roulette();
}

static unsigned long
Roulette(void)
{
    static unsigned long ocount, randbuf;
    struct timeval tv;

    counter = 0;
    fCount = 1;
    Ns_SemaPost(&sema, 1);
    tv.tv_sec = (time_t)0;
    tv.tv_usec = MSEC_TO_COUNT * 1000;
    select(0, NULL, NULL, NULL, &tv);
    fCount = 0;
    counter ^= (counter >> 3) ^ (counter >> 6) ^ (ocount);
    counter &= 0x7;
    ocount = counter;
    randbuf = (randbuf<<3) ^ counter;
    return randbuf;
}
