/* Yarrow Random Number Generator (True Randomness Achieved in Software) *
 * Copyright (c) 1998-2000 by Yarrow Charnot, Identikey <mailto://ycharnot@identikey.com> 
 * All Lefts, Rights, Ups, Downs, Forwards, Backwards, Pasts and Futures Reserved *
 */

/* Made into a BeOS /dev/random and /dev/urandom by Daniel Berlin */
/* Adapted for OpenBeOS by David Reid, Axel Doerfler */


#include <OS.h>
#include <Drivers.h>
#include <Errors.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>


#define TRACE_DRIVER 0
#if TRACE_DRIVER
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif

int32 api_version = B_CUR_DRIVER_API_VERSION;

#define	DRIVER_NAME "random"

static const char *sRandomNames[] = {
	"random",
	"urandom",
	NULL
};

static status_t random_open(const char *name, uint32 flags, void **cookie);
static ssize_t random_read(void *cookie, off_t position, void *_buffer, size_t *_numBytes);
static ssize_t random_write(void *cookie, off_t position, const void *buffer, size_t *_numBytes);
static status_t random_control(void *cookie, uint32 op, void *arg, size_t length);
static status_t random_close(void *cookie);
static status_t random_free(void *cookie);

static device_hooks sRandomHooks = {
	random_open,
	random_close,
	random_free,
	random_control,
	random_read,
	random_write,
	NULL,
	NULL,
	NULL,
	NULL
};


//	The number generator itself

#define rotr32(x, n) ((((uint32)(x)) >> ((int) ((n) & 31))) | (((uint32)(x)) << ((int) ((32 - ((n) & 31))))))
#define rotl32(x, n) ((((uint32)(x)) << ((int) ((n) & 31))) | (((uint32)(x)) >> ((int) ((32 - ((n) & 31))))))

#define bswap32(x) \
	((rotl32((uint32)(x), 8) & 0x00ff00ff) | (rotr32((uint32)(x), 8) & 0xff00ff00))

typedef union _OCTET {
	uint64	Q[1];
	uint32	D[2];
	uint16	W[4];
	uint8	B[8];
} OCTET;

#define NK	257			/* internal state size */
#define NI	120			/* seed in increment */
#define NA	70			/* rand out increment A */
#define NB	139			/* rand out increment B */

typedef struct _ch_randgen {
	OCTET	ira[NK];	/* numbers live here */
	OCTET	*seedptr;	/* next seed pointer */
	OCTET	*rndptrA;	/* randomizing pointer #1 */
	OCTET	*rndptrB;	/* randomizing pointer #2 */
	OCTET	*rndptrX;	/* main randout pointer */
	OCTET	rndLeft;	/* left rand accumulator */
	OCTET	rndRite;	/* rite rand accumulator */
} ch_randgen;


static ch_randgen *sRandomEnv;
static uint32 sRandomCount = 0;
static sem_id sRandomSem;


extern void hash_block(const unsigned char *block, const unsigned int block_byte_size, unsigned char *md);


#define HASH_BITS			160		/* I use Tiger. Modify it to match your HASH */
#define HASH_BLOCK_BITS		512		/* I use Tiger. Modify it to match your HASH */
#define HASH_BYTES			(HASH_BITS / 8)
#define HASH_BLOCK_BYTES	(HASH_BLOCK_BITS / 8)
#define HASH_OCTETS			(HASH_BITS / 64)
#define HASH_BLOCK_OCTETS	(HASH_BLOCK_BITS / 64)


/* attach by Yarrow Charnot. attaches x to y. can be seen as about 2-3 rounds of RC6 encryption
 */

static inline void
attach(OCTET *y, const OCTET *x, const uint32 anyA, const uint32 anyB,
	const uint32 oddC, const uint32 oddD)
{
	register OCTET _x;
	register OCTET _y;

	_x.D[0] = x->D[0];
	_x.D[1] = x->D[1];
	_y.D[0] = y->D[0];
	_y.D[1] = y->D[1];
	_x.D[0] = rotl32(((bswap32(_x.D[0]) | 1) * x->D[1]), 5);
	_x.D[1] = rotl32((bswap32(_x.D[1]) | 1) * x->D[0], 5);
	_y.D[0] = (bswap32(rotl32(_y.D[0] ^ _x.D[0], _x.D[1])) + anyA) * oddC;
	_y.D[1] = (bswap32(rotl32(_y.D[1] ^ _x.D[1], _x.D[0])) + anyB) * oddD;
	y->D[1] = _y.D[0];
	y->D[0] = _y.D[1];
}


/** detach by Yarrow Charnot. detaches x from y. can be seen as about
 *	2-3 rounds of RC6 decryption.
 */

static inline void
detach(OCTET *y, const OCTET *x, const uint32 sameA, const uint32 sameB,
	const uint32 invoddC, const uint32 invoddD)
{
	register OCTET _x;
	register OCTET _y;

	_x.D[0] = x->D[0];
	_x.D[1] = x->D[1];
	_y.D[0] = y->D[1];
	_y.D[1] = y->D[0];
	_x.D[0] = rotl32((bswap32(_x.D[0]) | 1) * x->D[1], 5);
	_x.D[1] = rotl32((bswap32(_x.D[1]) | 1) * x->D[0], 5);
	_y.D[0] = rotr32(bswap32(_y.D[0] * invoddC - sameA), _x.D[1]) ^ _x.D[0];
	_y.D[1] = rotr32(bswap32(_y.D[1] * invoddD - sameB), _x.D[0]) ^ _x.D[1];
	y->D[0] = _y.D[0];
	y->D[1] = _y.D[1];
}


/**	QUICKLY seeds in a 64 bit number, modified so that a subsequent call really
 *	"stirs" in another seed value (no bullshit XOR here!)
 */

static inline void
chseed(ch_randgen *prandgen, const uint64 seed)
{
	prandgen->seedptr += NI;
	if (prandgen->seedptr >= (prandgen->ira + NK))
		prandgen->seedptr -= NK;

	attach(prandgen->seedptr, (OCTET *) &seed, 0x213D42F6U, 0x6552DAF9U,
		0x2E496B7BU, 0x1749A255U);
}


/**	The heart of Yarrow 2000 Chuma Random Number Generator: fast and reliable
 *	randomness collection.
 *	Thread yielding function is the most OPTIMAL source of randomness combined
 *	with a clock counter (editor's note: thread_yield() is not properly done in BeOS).
 *	It doesn't have to switch to another thread, the call itself is random enough.
 *	Test it yourself.
 *	This FASTEST way to collect minimal randomness on each step couldn't use the
 *	processor any LESS. Even functions based on just creation of threads and their
 *	destruction can not compare by speed.
 *	Temporary file creation is just a little extra thwart to bewilder the processor
 *	cache and pipes.
 *	If you make clock_counter() (system_time()) return all 0's, still produces a
 *	stream indistinguishable from random.
 */ 

static void
reseed(ch_randgen *prandgen, const uint32 initTimes)
{
	volatile uint32 i, j;
	OCTET x, y;

	for (j = initTimes; j; j--) {
		for (i = NK * initTimes; i; i--) {
			// Note: there is no yield() in BeOS - we use snooze as an approximation,
			//	although it might not work as good as this one (especially, it's not
			//	as cheap as).
			//thread_yield();
			snooze(5);

			y.Q[0] += system_time();
			attach(&x, &y, 0x52437EFFU, 0x026A4CEBU, 0xD9E66AC9U, 0x56E5A975U);
			attach(&y, &x, 0xC70B8B41U, 0x9126B036U, 0x36CC6FDBU, 0x31D477F7U);
			chseed(prandgen, y.Q[0]);
		}
	}
}


/* returns a 64 bit of Yarrow 2000 Chuma RNG random number */ 

static inline uint64
chrand(ch_randgen *prandgen)
{
	prandgen->rndptrX++;
	prandgen->rndptrA += NA;
	prandgen->rndptrB += NB;
	if (prandgen->rndptrX >= (prandgen->ira + NK)) {
		prandgen->rndptrX -= NK;
		reseed (prandgen, 1);
	}

	if (prandgen->rndptrA >= (prandgen->ira + NK))
		prandgen->rndptrA -= NK;
	if (prandgen->rndptrB >= (prandgen->ira + NK))
		prandgen->rndptrB -= NK;

	attach(&prandgen->rndLeft, prandgen->rndptrX, prandgen->rndptrA->D[0],
		prandgen->rndptrA->D[1], 0x49A3BC71UL, 0x60E285FDUL);
	attach(&prandgen->rndRite, &prandgen->rndLeft, prandgen->rndptrB->D[0],
		prandgen->rndptrB->D[1], 0xC366A5FDUL, 0x20C763EFUL);

	chseed(prandgen, prandgen->rndRite.Q[0]);

	return prandgen->rndRite.Q[0] ^ prandgen->rndLeft.Q[0];
}


/** returns a 32 bit random number */

static inline uint32
chrand32(ch_randgen *prandgen)
{
	OCTET r = {{chrand(prandgen)}};
	return r.D[0] ^ r.D[1];
}


/** returns an 8 bit random number */

static inline uint8
chrand8(ch_randgen *prandgen)
{
	OCTET r = {{chrand(prandgen)}};
	return r.B[0] ^ r.B[1] ^ r.B[2] ^ r.B[3] ^ r.B[4] ^ r.B[5] ^ r.B[6] ^ r.B[7];
}

/* generates a cryptographically secure random big number 0 <= x < 32^n */ 
/* automatically reseeds if necessary or if requested 1/16 of the internal state or more */ 
/* 
   __inline void bigrand (ch_randgen *prandgen, unsigned __int32 *x, unsigned __int32 n) 
   { 
   unsigned int i; 
   OCTET block[HASH_BLOCK_OCTETS]; 
   OCTET hash[HASH_OCTETS]; 
   OCTET *j; 
   if (n >= NK/8) reseed (prandgen, 1); 
   for (*x++ = n; (signed) n > 0; ) 
   { 
   for (i = 0; i < HASH_BLOCK_OCTETS; i++) block->Q[i] += chrand (prandgen) + hash
   ->Q[i % HASH_OCTETS]; 
   hash_block (block->B, HASH_BLOCK_BYTES, hash->B); 
   for (i = HASH_OCTETS, j = hash; i && ((signed) n > 0); i--, j++, x += 2, n -= 2) 
   { 
   attach ((OCTET *) &x, j, 0x0AEF7ED2U, 0x3F85C5C1U, 0xD3EFB373U, 
   0x13ECF0B9U); 
   } 
   } 
   } 
 */


/** Initializes Yarrow 2000 Chuma Random Number Generator.
 *	Reseeding about 8 times prior to the first use is recommended.
 *	More than 16 will probably be a bit too much as time increases
 *	by n^2.
 */

static ch_randgen *
new_chrand(const unsigned int inittimes)
{
	ch_randgen *prandgen;

	prandgen = (ch_randgen *)malloc(sizeof(ch_randgen));
	if (prandgen == NULL)
		return NULL;

	prandgen->seedptr = prandgen->ira;
	prandgen->rndptrX = prandgen->ira;
	prandgen->rndptrA = prandgen->ira;
	prandgen->rndptrB = prandgen->ira;
	prandgen->rndLeft.Q[0] = 0x1A4B385C72D69E0FUL;
	prandgen->rndRite.Q[0] = 0x9C805FE7361A42DBUL;
	reseed (prandgen, inittimes);

	prandgen->seedptr = prandgen->ira + chrand (prandgen) % NK;
	prandgen->rndptrX = prandgen->ira + chrand (prandgen) % NK;
	prandgen->rndptrA = prandgen->ira + chrand (prandgen) % NK;
	prandgen->rndptrB = prandgen->ira + chrand (prandgen) % NK;
	return prandgen;
}


/** Clean up after chuma */

static void
kill_chrand(ch_randgen *randgen)
{
	free(randgen);
}


//	#pragma mark -
//	device driver


status_t
init_hardware(void)
{
	TRACE((DRIVER_NAME ": init_hardware()\n"));
	return B_OK;
}


status_t
init_driver(void)
{
	TRACE((DRIVER_NAME ": init_driver()\n"));

	sRandomSem = create_sem(1, "RNG semaphore");
	set_sem_owner(sRandomSem, B_SYSTEM_TEAM);
	return B_OK;
}


void
uninit_driver(void)
{
	TRACE((DRIVER_NAME ": uninit_driver()\n"));
	kill_chrand(sRandomEnv);
	delete_sem(sRandomSem);
}


const char **
publish_devices(void)
{
	TRACE((DRIVER_NAME ": publish_devices()\n"));
	return sRandomNames;
}

device_hooks *
find_device(const char* name)
{
	int	i;

	TRACE((DRIVER_NAME ": find_device(\"%s\")\n", name));

	for (i = 0; sRandomNames[i] != NULL; i++)
		if (strcmp(name, sRandomNames[i]) == 0)
			return &sRandomHooks;

	return NULL;
}


//	#pragma mark -
//	device functions


static status_t
random_open(const char *name, uint32 flags, void **cookie)
{
	TRACE((DRIVER_NAME ": open(\"%s\")\n", name));
	acquire_sem(sRandomSem);

	sRandomEnv = new_chrand(8);
		// the random generator is only initialized on demand, but
		// remains valid as long as the driver is loaded

	release_sem(sRandomSem);

	return sRandomEnv != NULL ? B_OK : B_NO_MEMORY;
}


static ssize_t
random_read(void *cookie, off_t position, void *_buffer, size_t *_numBytes)
{
	int32 *buffer = (int32 *)_buffer;
	uint8 *buffer8 = (uint8 *)_buffer;
	uint32 i, j;

	acquire_sem(sRandomSem);
	sRandomCount += *_numBytes;

	/* Reseed if we have or are gonna use up > 1/16th the entropy around */
	if (sRandomCount >= NK/8) {
		sRandomCount = 0;
		reseed(sRandomEnv, 1);
	}

	/* ToDo: Yes, i know this is not the way we should do it. What we really should do is
	 * take the md5 or sha1 hash of the state of the pool, and return that. Someday.
	 */
	for (i = 0; i < (*_numBytes) / 4; i++)
		buffer[i] = chrand32(sRandomEnv);
	for (j = 0; j < (*_numBytes) % 4; j++)
		buffer8[(i*4) + j] = chrand8(sRandomEnv);

	release_sem(sRandomSem);
	return B_OK;
}


static ssize_t 
random_write(void *cookie, off_t position, const void *buffer, size_t *_numBytes)
{
	return B_OK;
}


static status_t
random_control(void *cookie, uint32 op, void *arg, size_t length)
{
	TRACE((DRIVER_NAME ": ioctl(%d)\n", op));
	return B_ERROR;
}


static status_t
random_close(void *cookie)
{
	TRACE((DRIVER_NAME ": close()\n"));
	return B_OK;
}


static status_t
random_free(void *cookie)
{
	TRACE((DRIVER_NAME ": free()\n"));
	return B_OK;
}
