/*
* Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
* (c) 1999 Symbian Ltd
*
*/


/**
 @file
*/

#include <e32base.h>
#include <e32std.h>
#include <e32test.h>
#include <random.h>
#include <f32file.h>

#include <randsvr.h>

RTest test(_L("Random Number Generator Tests"));

TInt gNumberOfRandomNumbers=10000;

/** Wraps a console and logs output to a file. */
class CTestConsole:public CConsoleBase
	{
	public:
		static CTestConsole* NewL(CConsoleBase* aCon, const TDesC& aFilename);
		TInt Create(const TDesC16& aTitle,TSize aSize) {return iCon->Create(aTitle,aSize);};
		void Read(TRequestStatus& aStatus) {iCon->Read(aStatus);};
		void ReadCancel(void) {iCon->ReadCancel();};
		void Write(const TDesC16& aString);
		TPoint CursorPos(void) const {return iCon->CursorPos();};
		void SetCursorPosAbs(const TPoint& aPos) {iCon->SetCursorPosAbs(aPos);};
		void SetCursorPosRel(const TPoint& aPos) {iCon->SetCursorPosRel(aPos);};
		void SetCursorHeight(TInt aHeight) {iCon->SetCursorHeight(aHeight);};
		void SetTitle(const TDesC16& aTitle) {iCon->SetTitle(aTitle);};
		void ClearScreen(void) {iCon->ClearScreen();};
		void ClearToEndOfLine(void) {iCon->ClearToEndOfLine();};
		TSize ScreenSize(void) const {return iCon->ScreenSize();};
		TKeyCode KeyCode(void) const {return iCon->KeyCode();};
		TUint KeyModifiers(void) const {return iCon->KeyModifiers();};
		~CTestConsole(void);
	private:
		CTestConsole(CConsoleBase* aCon);
	    void ConstructL(const TDesC& aFilename);
	    CConsoleBase* iCon;  ///< Pointer to wrapped console, we don't own this
	    RFs iFs;
	    RFile iFile;		 ///< Log file
	};

CTestConsole* CTestConsole::NewL(CConsoleBase* aCon, const TDesC& aFilename)

	{
	CTestConsole* self;
	self=new (ELeave) CTestConsole(aCon);
	CleanupStack::PushL(self);
	self->ConstructL(aFilename);
	CleanupStack::Pop(self);
	return self;
	}

CTestConsole::CTestConsole(CConsoleBase* aCon) :
	CConsoleBase(), iCon(aCon)

	{
	}

void CTestConsole::ConstructL(const TDesC& aFilename)
	
	{
	User::LeaveIfError(iFs.Connect());
	User::LeaveIfError(iFile.Replace(iFs,aFilename,EFileShareAny|EFileWrite));
	}

CTestConsole::~CTestConsole(void)

	{
	iFile.Close();
	iFs.Close();
	}

void CTestConsole::Write(const TDesC16& aString)

	{
	iCon->Write(aString);
	TUint8 space[200];
	TPtr8 ptr(space,200);
	ptr.Copy(aString);
	iFile.Write(ptr);
	}

void Monobit(const TUint8* aData)

	{
	const TInt bitcount[256]=
		{ 0,1,1,2,1,2,2,3,		// 00-07
		  1,2,2,3,2,3,3,4,		// 08-0f
		  1,2,2,3,2,3,3,4,		// 10-17
		  2,3,3,4,3,4,4,5,		// 18-1f
		  1,2,2,3,2,3,3,4,		// 20-27
		  2,3,3,4,3,4,4,5,		// 28-2f
		  2,3,3,4,3,4,4,5,		// 30-37
		  3,4,4,5,4,5,5,6,		// 38-3f

		  1,2,2,3,2,3,3,4,		// 40-47
		  2,3,3,4,3,4,4,5,		// 48-4f
		  2,3,3,4,3,4,4,5,		// 50-57
		  3,4,4,5,4,5,5,6,		// 58-5f
		  2,3,3,4,3,4,4,5,		// 60-67
		  3,4,4,5,4,5,5,6,		// 68-6f
		  3,4,4,5,4,5,5,6,		// 70-77
		  4,5,5,6,5,6,6,7,		// 78-7f

		  1,2,2,3,2,3,3,4,		// 80-87
		  2,3,3,4,3,4,4,5,		// 88-8f
		  2,3,3,4,3,4,4,5,		// 90-97
		  3,4,4,5,4,5,5,6,		// 98-9f
		  2,3,3,4,3,4,4,5,		// a0-a7
		  3,4,4,5,4,5,5,6,		// a8-af
		  3,4,4,5,4,5,5,6,		// b0-b7
		  4,5,5,6,5,6,6,7,		// b8-bf

		  2,3,3,4,3,4,4,5,		// c0-c7
		  3,4,4,5,4,5,5,6,		// c8-cf
		  3,4,4,5,4,5,5,6,		// d0-d7
		  4,5,5,6,5,6,6,7,		// d8-df
		  3,4,4,5,4,5,5,6,		// e0-e7
		  4,5,5,6,5,6,6,7,		// e8-ef
		  4,5,5,6,5,6,6,7,		// f0-f7
		  5,6,6,7,6,7,7,8		// f8-ff
		};
	TInt total=0;
	TInt i;
	for (i=0;i<2500;i++)
		{
		total+=bitcount[aData[i]];
		}
	test.Printf(_L("    Total bitcount %d\r\n"),total);
	if ((total>9654)&&(total<10346))
		{
		test.Printf(_L("    Passed Monobit\r\n"));
		}
	else
		{
		test.Printf(_L("    ***FAILED!\r\n"));
		User::Panic(_L("t_random.exe"), KErrGeneral);
		}
	}

void Poker(const TUint8* aData)
	
	{
	TInt f[16]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	TInt i;
	for (i=0;i<2500;i++)
		{
		f[(aData[i]&0x0f)]++;
		f[((aData[i]&0xf0)>>4)]++;
		}
	TReal x=0;
	for (i=0;i<16;i++)
		{
		x+=f[i]*f[i];
		}
	x*=16;
	x/=5000;
	x-=5000;
	if ((x>1.03)&&(x<57.4))
		{
		test.Printf(_L("    Passed poker test\r\n"));
		}
	else
		{
		test.Printf(_L("    ***FAILED poker test\r\n"));
		User::Panic(_L("t_random.exe"), KErrGeneral);
		}
	}

void Runs(const TUint8* aData)

	{
	TInt i;
	TInt lastbit=0;
	TInt count[7][2]={
		{ 0,0 },
		{ 0,0 },
		{ 0,0 },
		{ 0,0 },
		{ 0,0 },
		{ 0,0 },
		{ 0,0 }
		};
	TInt thisrun=0;
	TInt longrun=0;
	for (i=0;i<2500;i++)
		{
		TInt bit;
		for (bit=0;bit<8;bit++)
			{
			if (((aData[i]>>bit)&1)==lastbit)
				{
				thisrun++;
				}
			else
				{
				if (thisrun<5)
					{
					count[thisrun][lastbit]++;
					}
				else
					{
					count[5][lastbit]++;
					}
				lastbit^=1;
				if (thisrun>longrun)
					{
					longrun=thisrun;
					}
				thisrun=0;
				}
			}
		}
	TInt bound[6][2]=
		{
			{2267,2733},
			{1079,1421},
			{ 502, 748},
			{ 223, 402},
			{  90, 223},
			{  90, 223}
		};
	TBool failed=EFalse;
	for (i=0;i<6;i++)
		{
		if (!((count[i][0]>bound[i][0])&&(count[i][0]<bound[i][1])))
			{
			test.Printf(_L("    ***FAILED runs test\r\n"));
			failed=ETrue;
			}
		if (!((count[i][1]>bound[i][0])&&(count[i][1]<bound[i][1])))
			{
			test.Printf(_L("    ***FAILED runs test\r\n"));
			failed=ETrue;
			}
		}
	if (!failed)
		{
		test.Printf(_L("    Passed runs test\r\n"));
		}
	if ( (longrun>34) || (failed) )
		{
		test.Printf(_L("    ***FAILED longrun test\r\n"));
		User::Panic(_L("t_random.exe"), KErrGeneral);
		}
	}

void FIPSTest(const TUint8* aData)
//	Run some basic tests to check it's returned some numbers
//	These will panic if a failure is detected
	{
	Monobit(aData);
	Poker(aData);
	Runs(aData);
	}

void WriteFile(const TUint8* aData,const TDesC& aFileName)

	{
	RFs fs;
	fs.Connect();
	RFile file;
	TInt err;
	err=file.Open(fs,aFileName,EFileShareAny|EFileWrite);
	if (err)
		{
		if (file.Create(fs,aFileName,EFileShareAny|EFileWrite))
			{
			return;
			}
		}
	TPtrC8 ptr(aData,gNumberOfRandomNumbers);
	TInt size;
	file.Size(size);
	file.Write(size,ptr);
	file.Close();
	fs.Close();
	FIPSTest(aData);
	}

class RTestRandomSession : public RRandomSession
	{
	public:
	void SendMalformedInputL()
		{		
		test.Printf(_L("Test malformed input with negative buffer size\r\n"));		
		TBuf8<1024> buffer;
		TInt err = SendReceive(CRandomSession::KRandomRequest, TIpcArgs(&buffer, -1));
		if (err != KErrArgument)
			{
			test.Printf(_L("%d should have been returned on negative buffer size test, but %d was returned!\r\n"), KErrArgument, err);
			User::Leave(KErrGeneral);
			}
		}
	};

// Checks that RandomServer handles malformed length correctly - see INC113902
void TestMalformedInputL()
	{
	RTestRandomSession rs;
	TRAPD(err, rs.ConnectL());
	User::LeaveIfError(err); // The connect method leaves with zero even if it succeeds, so we have to trap the error
	CleanupClosePushL(rs);		
	rs.SendMalformedInputL();
	CleanupStack::PopAndDestroy(&rs);
	}

void DoTestsL()

	{
	TestMalformedInputL();
	
	test.Printf(_L("  Run random tests with normal salting\r\n"));
    
	TInt i;
	TBuf8<16> buf2;
	for (i=0;i<16;i++)
		{
		buf2.SetLength(i);
		TRandom::RandomL(buf2);
		}
	HBufC8* buf=HBufC8::NewMaxL(gNumberOfRandomNumbers);
	TPtr8 buffer=buf->Des();
	for (i=0;i<11;i++)
		{
		User::After(10000000);
		TPtr8 thePtr(buf->Des());
		thePtr.FillZ();
	//	Generate the random data	
		TRandom::RandomL(buffer);
		if (buf->Length()!=gNumberOfRandomNumbers)
			User::Leave(KErrGeneral);


		WriteFile(buffer.Ptr(),_L("User.rnd"));
		test.Printf(_L("."));
		}
	delete buf;
	}

void TestsL(void)
	{
	TDriveUnit sysDrive (RFs::GetSystemDrive());
	TDriveName driveName(sysDrive.Name());
	TBuf<64> logFile (driveName);
	logFile.Append(_L("\\t_random.log"));
	CTestConsole* con = CTestConsole::NewL(test.Console(), logFile);
	test.SetConsole(con);
	
	DoTestsL();
	
	//	If test reached here, no tests failed, otherwise it would have panicked
	//	and terminated prematurely. Print this out for tester's reference.	
	test.Printf(_L("\n0 tests failed out of 12\r\n"));
	}

GLDEF_C TInt E32Main(void)

	{
	CTrapCleanup* cleanup;
	cleanup=CTrapCleanup::New();
	
	__UHEAP_MARK;
	
	test.Start(_L(" SYMTestCaseID:SEC-CRYPTO-RANDOM-0001 Starting random number generator tests\r\n"));
	CConsoleBase* originalConsole = test.Console();

	TRAPD(ret,TestsL());
	if (ret)
		{
		test.Printf(_L("Unexpected leave\r\n"));
		// Print something to let the build system know we failed
		test.Printf(_L("\n1 tests failed out of 11\r\n"));
		}	
	test.End();

	if (test.Console() != originalConsole)
		{
		delete test.Console();
		test.SetConsole(originalConsole);
		}	
	test.Close();
	
	__UHEAP_MARKEND;
	
	delete cleanup;
	return(KErrNone);
	}
