/**
 * @file rand.c  Random generator
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <stdlib.h>
#ifdef USE_OPENSSL
#include <openssl/rand.h>
#include <openssl/err.h>
#endif
#include <re_types.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_tmr.h>
#include <re_sys.h>


#define DEBUG_MODULE "rand"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


#ifndef RELEASE
#define RAND_DEBUG 1  /**< Enable random debugging */
#endif

static const char alphanum[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"abcdefghijklmnopqrstuvwxyz"
	"0123456789";

#if RAND_DEBUG
static bool inited = false;
/** Check random state */
#define RAND_CHECK							\
	if (!inited) {							\
		DEBUG_WARNING("%s: random not inited\n", __REFUNC__);	\
	}
#else
#define RAND_CHECK if (0) {}
#endif


/**
 * Initialise random number generator
 */
void rand_init(void)
{
#ifndef USE_OPENSSL
	srand((uint32_t) tmr_jiffies());
#endif

#if RAND_DEBUG
	inited = true;
#endif
}


/**
 * Generate an unsigned 16-bit random value
 *
 * @return 16-bit random value
 */
uint16_t rand_u16(void)
{
	RAND_CHECK;

	/* Use higher-order bits (see man 3 rand) */
	return rand_u32() >> 16;
}


/**
 * Generate an unsigned 32-bit random value
 *
 * @return 32-bit random value
 */
uint32_t rand_u32(void)
{
	uint32_t v;

	RAND_CHECK;

#ifdef USE_OPENSSL
	v = 0;
	if (RAND_bytes((unsigned char *)&v, sizeof(v)) <= 0) {
		DEBUG_WARNING("RAND_bytes() error: %i\n",
			      ERR_GET_REASON(ERR_get_error()));
		ERR_clear_error();
	}
#elif defined(HAVE_ARC4RANDOM)
	v = arc4random();
#elif defined(WIN32)
	v = (rand() << 16) + rand(); /* note: 16-bit rand */
#else
	v = rand();
#endif

	return v;
}


/**
 * Generate an unsigned 64-bit random value
 *
 * @return 64-bit random value
 */
uint64_t rand_u64(void)
{
	RAND_CHECK;

	return (uint64_t)rand_u32()<<32 | rand_u32();
}


/**
 * Generate a random printable character
 *
 * @return Random printable character
 */
char rand_char(void)
{
	char s[2];

	RAND_CHECK;

	rand_str(s, sizeof(s));

	return s[0];
}


/**
 * Generate a string of random characters
 *
 * @param str  Pointer to string
 * @param size Size of string
 */
void rand_str(char *str, size_t size)
{
	size_t i;

	if (!str || !size)
		return;

	RAND_CHECK;

	--size;

	rand_bytes((uint8_t *)str, size);

	for (i=0; i<size; i++)
		str[i] = alphanum[((uint8_t)str[i]) % (sizeof(alphanum)-1)];

	str[size] = '\0';
}


/**
 * Generate a set of random bytes
 *
 * @param p    Pointer to buffer
 * @param size Size of buffer
 */
void rand_bytes(uint8_t *p, size_t size)
{
#ifdef USE_OPENSSL
	if (RAND_bytes(p, (int)size) <= 0) {
		DEBUG_WARNING("RAND_bytes() error: %i\n",
			      ERR_GET_REASON(ERR_get_error()));
		ERR_clear_error();
	}
#elif defined (HAVE_ARC4RANDOM)
	arc4random_buf(p, size);
#else
	while (size--) {
		p[size] = rand_u32();
	}
#endif
}
