/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#define _RTL_RANDOM_C_ "$Revision: 1.6 $"

#include <sal/types.h>
#include <osl/thread.h>
#include <osl/time.h>
#include <rtl/alloc.h>
#include <rtl/digest.h>
#include <rtl/random.h>
#include <osl/time.h>

/*========================================================================
 *
 * rtlRandom internals.
 *
 *======================================================================*/
#define RTL_RANDOM_RNG_1(a) ((a) * 16807L)
#define RTL_RANDOM_RNG_2(a) ((a) * 65539L)

#define RTL_RANDOM_RNG(x, y, z) \
{ \
	(x) = 170 * ((x) % 178) - 63 * ((x) / 178); \
	if ((x) < 0) (x) += 30328L; \
	\
	(y) = 171 * ((y) % 177) -  2 * ((y) / 177); \
	if ((y) < 0) (y) += 30269L; \
	\
	(z) = 172 * ((z) % 176) - 35 * ((z) / 176); \
	if ((z) < 0) (z) += 30307L; \
}

/** RandomData_Impl.
 */
typedef struct random_data_impl_st
{
	sal_Int16 m_nX;
	sal_Int16 m_nY;
	sal_Int16 m_nZ;
} RandomData_Impl;

/** __rtl_random_data.
 */
static double __rtl_random_data (RandomData_Impl *pImpl);

/** RandomPool_Impl.
 */
#define RTL_RANDOM_DIGEST      rtl_Digest_AlgorithmMD5
#define RTL_RANDOM_SIZE_DIGEST RTL_DIGEST_LENGTH_MD5
#define RTL_RANDOM_SIZE_POOL   1023

typedef struct random_pool_impl_st
{
	rtlDigest  m_hDigest;
	sal_uInt8  m_pDigest[RTL_RANDOM_SIZE_DIGEST];
	sal_uInt8  m_pData[RTL_RANDOM_SIZE_POOL + 1];
	sal_uInt32 m_nData;
	sal_uInt32 m_nIndex;
	sal_uInt32 m_nCount;
} RandomPool_Impl;

/** __rtl_random_initPool.
 */
static sal_Bool __rtl_random_initPool (
	RandomPool_Impl *pImpl);

/** __rtl_random_seedPool.
 */
static void __rtl_random_seedPool (
	RandomPool_Impl *pImpl, const sal_uInt8 *pBuffer, sal_Size nBufLen);

/** __rtl_random_readPool.
 */
static void __rtl_random_readPool (
	RandomPool_Impl *pImpl, sal_uInt8 *pBuffer, sal_Size nBufLen);

/*
 * __rtl_random_data.
 */
static double __rtl_random_data (RandomData_Impl *pImpl)
{
	register double random;

	RTL_RANDOM_RNG (pImpl->m_nX, pImpl->m_nY, pImpl->m_nZ);
	random = (((double)(pImpl->m_nX) / 30328.0) +
			  ((double)(pImpl->m_nY) / 30269.0) +
			  ((double)(pImpl->m_nZ) / 30307.0)   );

	random -= ((double)((sal_uInt32)(random)));
	return (random);
}

/*
 * __rtl_random_initPool.
 */
static sal_Bool __rtl_random_initPool (RandomPool_Impl *pImpl)
{
	pImpl->m_hDigest = rtl_digest_create (RTL_RANDOM_DIGEST);
	if (pImpl->m_hDigest)
	{
		oslThreadIdentifier id;
		TimeValue           tv;
		RandomData_Impl     rd;
		double              seed;

        /* The use of uninitialized stack variables as a way to
         * enhance the entropy of the random pool triggers 
         * memory checkers like purify and valgrind.
         */

        /*
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&id, sizeof(id));
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&tv, sizeof(tv));
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&rd, sizeof(rd));
        */

		id = osl_getThreadIdentifier (NULL);
		id = RTL_RANDOM_RNG_2(RTL_RANDOM_RNG_1(id));
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&id, sizeof(id));

		osl_getSystemTime (&tv);
		tv.Seconds = RTL_RANDOM_RNG_2(tv.Seconds);
		tv.Nanosec = RTL_RANDOM_RNG_2(tv.Nanosec);
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&tv, sizeof(tv));

		rd.m_nX = (sal_Int16)(((id         >> 1) << 1) + 1);
		rd.m_nY = (sal_Int16)(((tv.Seconds >> 1) << 1) + 1);
		rd.m_nZ = (sal_Int16)(((tv.Nanosec >> 1) << 1) + 1);
		__rtl_random_seedPool (pImpl, (sal_uInt8*)&rd, sizeof(rd));

		while (pImpl->m_nData < RTL_RANDOM_SIZE_POOL)
		{
			seed = __rtl_random_data (&rd);
			__rtl_random_seedPool (pImpl, (sal_uInt8*)&seed, sizeof(seed));
		}
		return sal_True;
	}
	return sal_False;
}

/*
 * __rtl_random_seedPool.
 */
static void __rtl_random_seedPool (
	RandomPool_Impl *pImpl, const sal_uInt8 *pBuffer, sal_Size nBufLen)
{
	sal_Size i;
	sal_sSize  j, k;

	for (i = 0; i < nBufLen; i += RTL_RANDOM_SIZE_DIGEST)
	{
		j = nBufLen - i;
		if (j > RTL_RANDOM_SIZE_DIGEST)
			j = RTL_RANDOM_SIZE_DIGEST;

		rtl_digest_update (
			pImpl->m_hDigest, pImpl->m_pDigest, RTL_RANDOM_SIZE_DIGEST);

		k = (pImpl->m_nIndex + j) - RTL_RANDOM_SIZE_POOL;
		if (k > 0)
		{
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[pImpl->m_nIndex]), j - k);
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[0]), k);
		}
		else
		{
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[pImpl->m_nIndex]), j);
		}

		rtl_digest_update (pImpl->m_hDigest, pBuffer, j);
		pBuffer += j;

		rtl_digest_get (
			pImpl->m_hDigest, pImpl->m_pDigest, RTL_RANDOM_SIZE_DIGEST);
		for (k = 0; k < j; k++)
		{
			pImpl->m_pData[pImpl->m_nIndex++] ^= pImpl->m_pDigest[k];
			if (pImpl->m_nIndex >= RTL_RANDOM_SIZE_POOL)
			{
				pImpl->m_nData  = RTL_RANDOM_SIZE_POOL;
				pImpl->m_nIndex = 0;
			}
		}
	}

	if (pImpl->m_nIndex > pImpl->m_nData)
		pImpl->m_nData = pImpl->m_nIndex;
}

/*
 * __rtl_random_readPool.
 */
static void __rtl_random_readPool (
	RandomPool_Impl *pImpl, sal_uInt8 *pBuffer, sal_Size nBufLen)
{
	sal_Int32 j, k;

	while (nBufLen > 0)
	{
		j = nBufLen;
		if (j > RTL_RANDOM_SIZE_DIGEST/2)
			j = RTL_RANDOM_SIZE_DIGEST/2;
		nBufLen -= j;

		rtl_digest_update (
			pImpl->m_hDigest,
			&(pImpl->m_pDigest[RTL_RANDOM_SIZE_DIGEST/2]),
			RTL_RANDOM_SIZE_DIGEST/2);

		k = (pImpl->m_nIndex + j) - pImpl->m_nData;
		if (k > 0)
		{
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[pImpl->m_nIndex]), j - k);
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[0]), k);
		}
		else
		{
			rtl_digest_update (
				pImpl->m_hDigest, &(pImpl->m_pData[pImpl->m_nIndex]), j);
		}

		rtl_digest_get (
			pImpl->m_hDigest, pImpl->m_pDigest, RTL_RANDOM_SIZE_DIGEST);
		for (k = 0; k < j; k++)
		{
			if (pImpl->m_nIndex >= pImpl->m_nData) pImpl->m_nIndex = 0;
			pImpl->m_pData[pImpl->m_nIndex++] ^= pImpl->m_pDigest[k];
			*pBuffer++ = pImpl->m_pDigest[k + RTL_RANDOM_SIZE_DIGEST/2];
		}
	}

	pImpl->m_nCount++;
	rtl_digest_update (
		pImpl->m_hDigest, &(pImpl->m_nCount), sizeof(pImpl->m_nCount));
	rtl_digest_update (
		pImpl->m_hDigest, pImpl->m_pDigest, RTL_RANDOM_SIZE_DIGEST);
	rtl_digest_get (
		pImpl->m_hDigest, pImpl->m_pDigest, RTL_RANDOM_SIZE_DIGEST);
}

/*========================================================================
 *
 * rtlRandom implementation.
 *
 *======================================================================*/
/*
 * rtl_random_createPool.
 */
rtlRandomPool SAL_CALL rtl_random_createPool (void)
{
	RandomPool_Impl *pImpl = (RandomPool_Impl*)NULL;
	pImpl = (RandomPool_Impl*)rtl_allocateZeroMemory (sizeof(RandomPool_Impl));
	if (pImpl)
	{
		if (!__rtl_random_initPool (pImpl))
		{
			rtl_freeZeroMemory (pImpl, sizeof(RandomPool_Impl));
			pImpl = (RandomPool_Impl*)NULL;
		}
	}
	return ((rtlRandomPool)pImpl);
}

/*
 * rtl_random_destroyPool.
 */
void SAL_CALL rtl_random_destroyPool (rtlRandomPool Pool)
{
	RandomPool_Impl *pImpl = (RandomPool_Impl *)Pool;
	if (pImpl)
	{
		rtl_digest_destroy (pImpl->m_hDigest);
		rtl_freeZeroMemory (pImpl, sizeof (RandomPool_Impl));
	}
}

/*
 * rtl_random_addBytes.
 */
rtlRandomError SAL_CALL rtl_random_addBytes (
	rtlRandomPool Pool, const void *Buffer, sal_Size Bytes)
{
	RandomPool_Impl *pImpl   = (RandomPool_Impl *)Pool;
	const sal_uInt8 *pBuffer = (const sal_uInt8 *)Buffer;

	if ((pImpl == NULL) || (pBuffer == NULL))
		return rtl_Random_E_Argument;

	__rtl_random_seedPool (pImpl, pBuffer, Bytes);
	return rtl_Random_E_None;
}

/*
 * rtl_random_getBytes.
 */
rtlRandomError SAL_CALL rtl_random_getBytes (
	rtlRandomPool Pool, void *Buffer, sal_Size Bytes)
{
	RandomPool_Impl *pImpl   = (RandomPool_Impl *)Pool;
	sal_uInt8       *pBuffer = (sal_uInt8 *)Buffer;

	if ((pImpl == NULL) || (pBuffer == NULL))
		return rtl_Random_E_Argument;

	__rtl_random_readPool (pImpl, pBuffer, Bytes);
	return rtl_Random_E_None;
}

