#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "getrand.c,v 1.1.1.1 1995/06/16 17:42:41 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * Everything dealing with randomness
 *
 * The security of our random numbers are based upon (in order of importance):
 *
 * The cryptographical security and diffusion properties of our hash
 * function (currently MD5).
 *
 * The granularity of the system clock (*truerand* stuff)
 *
 * The prime modulus multiplicative congruential generator
 */

#include "sos.h"
#include "md5.h"


/* Constants for prime modulus multiplicative congruential generator */
#define RNG_M 2147483647L  /* m = 2^31 - 1 */
#define RNG_A 16807L
#define RNG_Q 127773L      /* m div a */
#define RNG_R 2836L        /* m mod a */

/* Num words before you must refresh random pool */
#define REFRESH_POOL_LIMIT	4096
#define ADDRANDS_PER_REFRESH	4


static sos_buffer privpool;
static char statpool[16];
static unsigned volatile int sos_truerand_interrupted=0;
static unsigned volatile int sos_truerand_count = 0;

static void sos_truerand_interrupt();
static void sos_truerand_addrand(sos_buffer *rand);
static void sos_truerand_refresh(sos_buffer *rand);



/*
 * Generate random numbers
 *
 * len must be multiple of 4 bytes
 */
int 
sos_get_rand(unsigned char *buf, int len)
{
  int c;
  int intlen = sizeof(int);
  char *origbuf = NULL;
  int origlen = 0;
  char minbuf[4];
  unsigned int *tp = (unsigned int *)buf;

  /* If buffer is smaller than 4 bytes, round up :-) */
  if (len < 4)
    {
      origlen = len;
      origbuf = buf;
      buf = minbuf;
      len = 4;
    }

  if (len % 4)
    return (-1);

  /* Throw data into buf a word at a time */
  for (c = 0; c < len; c += intlen)
    {
      *((unsigned int *)(buf + c)) = sos_get_intrand();
    }

  if (origlen)
    {
      memcpy(origbuf, buf, origlen);
    }

  return (len / intlen);
}



/*
 * Return a random integer based on pool
 * update pool to keep things flowing
 */
unsigned int
sos_get_intrand()
{
  static signed short poolexpiry = REFRESH_POOL_LIMIT;
  static unsigned char inited = 0;
  struct timeval beforetime, aftertime;
  unsigned int retval = 0;
  unsigned int new[4];
  int count;
  MD5_CTX context;

  gettimeofday(&beforetime, NULL);

  if (!inited)
    {
      inited = 1;
      
      privpool.str = statpool;
      privpool.len = sizeof(statpool);
    }

  /* Update random pool every once in a while */
  if (++poolexpiry > REFRESH_POOL_LIMIT)
    {
      poolexpiry = 0;
      sos_truerand_refresh(&privpool);
    }

  /* Our returned random number is first word in privpool */
  memcpy((char *)&retval,privpool.str,sizeof(retval));

  /*
   * Use prime modulus multiplicative congruential generator
   * to generate new pseudo-random data for MD5 to munge
   * into current pool
   */
  for(count=0;count<4;count++)
    {
      new[count] = sos_rand_pmmcg(*(((int *)privpool.str)+count));
    }

  gettimeofday(&aftertime, NULL);

  /* Stir the pot */
  MD5Init(&context);
  MD5Update(&context, privpool.str, privpool.len);
  MD5Update(&context, (char *)new, sizeof(new));
  MD5Update(&context, (char *)&beforetime, sizeof(beforetime));
  MD5Update(&context, (char *)&aftertime, sizeof(aftertime));
  MD5Final(&context);
  
  /* Update the pool */
  memcpy(privpool.str,&(context.digest[0]), 16);

  return(retval);
}



/*
 * prime modulus multiplicative congruential generator
 * with period 2^31 - 1 (not very important)
 * and range [1 - 2^31 - 1] (important)
 */
unsigned int
sos_rand_pmmcg(unsigned int seed)
{
  register long low, high, test;

  /* Normalize to between 1 and m-1 */
  seed = (seed % (RNG_M - 1)) + 1;

  high = (seed) / RNG_Q;
  low = (seed) % RNG_Q;
  test = RNG_A * low - RNG_R * high;
  if (test > 0)
    (seed) = test;
  else
    (seed) = test + RNG_M;

  return(seed);
}



/*
 * Schedule an alarm call in a very
 * short time (hopefully the
 * granularity of the system clock)
 */
static void
sos_truerand_set_alarm()
{
  struct itimerval next;
  unsigned int tps;

  if ((tps = sysconf(_SC_CLK_TCK)) < 1)
    {
      tps = 60;
    }

  tps = 1000000 / tps;		/* usec / (tick/sec) == usecs/tick */
  tps--;			/* Best value for randomness */

  timerclear(&next.it_interval);
  next.it_value.tv_sec = 0;
  next.it_value.tv_usec = tps;
  if (setitimer(ITIMER_REAL, &next, NULL) < 0)
    {
      /* Something drastic */
    }
}



/*
 * Interrupt handler for tick alarm
 */
static void
sos_truerand_interrupt()
{
  if (sos_truerand_count)
    {
      sos_truerand_interrupted = 1;
      return;
    }

  /* Got interrupted before we counted anything.  Hmm. */
  (void)signal(SIGALRM, sos_truerand_interrupt);
  sos_truerand_set_alarm();
}



/*
 * Add ``some'' (hopefully enough) bits of random data into the random
 * pool
 *
 * (Somewhere between 8 and 14 bits, from tests, from sos_truerand_count
 * so assume 8 bits to be conservative)
 */
static void
sos_truerand_addrand(sos_buffer *rand)
{
  MD5_CTX context;
  struct timeval beforetime, aftertime;

  sos_truerand_interrupted = 0;
  sos_truerand_count = 0;

  gettimeofday(&beforetime, NULL);

  (void)signal(SIGALRM, sos_truerand_interrupt);
  sos_truerand_set_alarm();

  /*
   * Get an int with ``some'' random data in it
   */
  while (!sos_truerand_interrupted)
    sos_truerand_count++;

  gettimeofday(&aftertime, NULL);


  /* Create the new pool based on the old one plus the new bits */
  MD5Init(&context);
  MD5Update(&context, rand->str, rand->len);
  MD5Update(&context, (char *)&sos_truerand_count, sizeof(sos_truerand_count));
  MD5Update(&context, (char *)&beforetime, sizeof(beforetime));
  MD5Update(&context, (char *)&aftertime, sizeof(aftertime));
  MD5Final(&context);

  /* Update the pool */
  memcpy(rand->str,&(context.digest[0]), 16);
}



/*
 * Refresh the pool of random data
 */
static void
sos_truerand_refresh(sos_buffer *rand)
{
  int x;

  for(x=0;x<ADDRANDS_PER_REFRESH;x++)
    sos_truerand_addrand(rand);
}
