// distribution boxbackup-0.10 (svn version: 494)
//  
// Copyright (c) 2003 - 2006
//      Ben Summers and contributors.  All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. All use of this software and associated advertising materials must 
//    display the following acknowledgment:
//        This product includes software developed by Ben Summers.
// 4. The names of the Authors may not be used to endorse or promote
//    products derived from this software without specific prior written
//    permission.
// 
// [Where legally impermissible the Authors do not disclaim liability for 
// direct physical injury or death caused solely by defects in the software 
// unless it is modified by a third party.]
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//  
//  
//  
// --------------------------------------------------------------------------
//
// File
//		Name:    testbasicserver.cpp
//		Purpose: Test basic server classes
//		Created: 2003/07/29
//
// --------------------------------------------------------------------------


#include "Box.h"

#include <stdio.h>
#include <unistd.h>
#include <time.h>

#include <typeinfo>

#include "Test.h"
#include "Daemon.h"
#include "Configuration.h"
#include "ServerStream.h"
#include "SocketStream.h"
#include "IOStreamGetLine.h"
#include "ServerTLS.h"
#include "CollectInBufferStream.h"

#include "TestContext.h"
#include "autogen_TestProtocolClient.h"
#include "autogen_TestProtocolServer.h"

#include "MemLeakFindOn.h"


#define SERVER_LISTEN_PORT	2003

// in ms
#define COMMS_READ_TIMEOUT					4
#define COMMS_SERVER_WAIT_BEFORE_REPLYING	40

class basicdaemon : public Daemon
{
public:
basicdaemon() {};
~basicdaemon() {}
virtual void Run();
};

void basicdaemon::Run()
{
	// Write a file to check it's done...
	const Configuration &c(GetConfiguration());
	
	FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w");
	fclose(f);

	while(!StopRun())
	{
		::sleep(10);
	}
}

void testservers_pause_before_reply()
{
	 struct timespec t;
	 t.tv_sec = 0;
	 t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000;	// convert to ns
	 ::nanosleep(&t, NULL);
}

#define LARGE_DATA_BLOCK_SIZE 19870
#define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000)

void testservers_connection(SocketStream &rStream)
{
	IOStreamGetLine getline(rStream);

	if(typeid(rStream) == typeid(SocketStreamTLS))
	{
		// need to wait for some data before sending stuff, otherwise timeout test doesn't work
		std::string line;
		while(!getline.GetLine(line))
			;
		SocketStreamTLS &rtls = (SocketStreamTLS&)rStream;
		std::string line1("CONNECTED:");
		line1 += rtls.GetPeerCommonName();
		line1 += '\n';
		testservers_pause_before_reply();
		rStream.Write(line1.c_str(), line1.size());
	}

	while(!getline.IsEOF())
	{
		std::string line;
		while(!getline.GetLine(line))
			;
		if(line == "QUIT")
		{
			break;
		}
		if(line == "LARGEDATA")
		{
			{
				// Send lots of data
				char data[LARGE_DATA_BLOCK_SIZE];
				for(unsigned int y = 0; y < sizeof(data); y++)
				{
					data[y] = y & 0xff;
				}
				for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
				{
					rStream.Write(data, sizeof(data));
				}
			}
			{
				// Receive lots of data
				char buf[1024];
				int total = 0;
				int r = 0;
				while(total < LARGE_DATA_SIZE && (r = rStream.Read(buf, sizeof(buf))) != 0)
				{
					total += r;
				}
				TEST_THAT(total == LARGE_DATA_SIZE);
			}
			
			// next!
			continue;
		}
		std::string backwards;
		for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i)
		{
			backwards += (*i);
		}
		backwards += '\n';
		testservers_pause_before_reply();
		rStream.Write(backwards.c_str(), backwards.size());
	}
	rStream.Shutdown();
	rStream.Close();
}



class testserver : public ServerStream<SocketStream, SERVER_LISTEN_PORT>
{
public:
	testserver() {}
	~testserver() {}
	
	void Connection(SocketStream &rStream);
	
	virtual const char *DaemonName() const
	{
		return "test-srv2";
	}
	const ConfigurationVerify *GetConfigVerify() const;

};

const ConfigurationVerify *testserver::GetConfigVerify() const
{
	static ConfigurationVerifyKey verifyserverkeys[] = 
	{
		SERVERSTREAM_VERIFY_SERVER_KEYS(0)	// no default addresses
	};

	static ConfigurationVerify verifyserver[] = 
	{
		{
			"Server",
			0,
			verifyserverkeys,
			ConfigTest_Exists | ConfigTest_LastEntry,
			0
		}
	};

	static ConfigurationVerify verify =
	{
		"root",
		verifyserver,
		0,
		ConfigTest_Exists | ConfigTest_LastEntry,
		0
	};

	return &verify;
}

void testserver::Connection(SocketStream &rStream)
{
	testservers_connection(rStream);
}

class testProtocolServer : public testserver
{
public:
	testProtocolServer() {}
	~testProtocolServer() {}

	void Connection(SocketStream &rStream);
	
	virtual const char *DaemonName() const
	{
		return "test-srv4";
	}
};

void testProtocolServer::Connection(SocketStream &rStream)
{
	TestProtocolServer server(rStream);
	TestContext context;
	server.DoServer(context);
}


class testTLSserver : public ServerTLS<SERVER_LISTEN_PORT>
{
public:
	testTLSserver() {}
	~testTLSserver() {}
	
	void Connection(SocketStreamTLS &rStream);
	
	virtual const char *DaemonName() const
	{
		return "test-srv3";
	}
	const ConfigurationVerify *GetConfigVerify() const;

};

const ConfigurationVerify *testTLSserver::GetConfigVerify() const
{
	static ConfigurationVerifyKey verifyserverkeys[] = 
	{
		SERVERTLS_VERIFY_SERVER_KEYS(0)	// no default listen addresses
	};

	static ConfigurationVerify verifyserver[] = 
	{
		{
			"Server",
			0,
			verifyserverkeys,
			ConfigTest_Exists | ConfigTest_LastEntry,
			0
		}
	};

	static ConfigurationVerify verify =
	{
		"root",
		verifyserver,
		0,
		ConfigTest_Exists | ConfigTest_LastEntry,
		0
	};

	return &verify;
}

void testTLSserver::Connection(SocketStreamTLS &rStream)
{
	testservers_connection(rStream);
}


void Srv2TestConversations(const std::vector<IOStream *> &conns)
{
	const static char *tosend[] = {
		"test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0
	};
	const static char *recieve[] = {
		"1 tset", "storrac", "selppaenip", "!ooob", 0
	};
	
	IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()];
	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		getline[c] = new IOStreamGetLine(*conns[c]);
		
		bool hadTimeout = false;
		if(typeid(*conns[c]) == typeid(SocketStreamTLS))
		{
			SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c];
			printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str());
		
			// Send some data, any data, to get the first response.
			conns[c]->Write("Hello\n", 6);
		
			std::string line1;
			while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT))
				hadTimeout = true;
			TEST_THAT(line1 == "CONNECTED:CLIENT");
			TEST_THAT(hadTimeout)
		}
	}
	
	for(int q = 0; tosend[q] != 0; ++q)
	{
		for(unsigned int c = 0; c < conns.size(); ++c)
		{
			//printf("%d: %s", c, tosend[q]);
			conns[c]->Write(tosend[q], strlen(tosend[q]));
			std::string rep;
			bool hadTimeout = false;
			while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT))
				hadTimeout = true;
			TEST_THAT(rep == recieve[q]);
			TEST_THAT(hadTimeout)
		}
	}
	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		conns[c]->Write("LARGEDATA\n", 10);
	}
	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		// Receive lots of data
		char buf[1024];
		int total = 0;
		int r = 0;
		while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0)
		{
			total += r;
		}
		TEST_THAT(total == LARGE_DATA_SIZE);
	}
	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		// Send lots of data
		char data[LARGE_DATA_BLOCK_SIZE];
		for(unsigned int y = 0; y < sizeof(data); y++)
		{
			data[y] = y & 0xff;
		}
		for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
		{
			conns[c]->Write(data, sizeof(data));
		}
	}

	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		conns[c]->Write("QUIT\n", 5);
	}
	
	for(unsigned int c = 0; c < conns.size(); ++c)
	{
		if ( getline[c] ) delete getline[c];
		getline[c] = 0;
	}
	if ( getline ) delete [] getline;
	getline = 0;
}

void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream)
{
	std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QueryGetStream(value, uncertainstream));
	TEST_THAT(reply->GetStartingValue() == value);
	
	// Get a stream
	std::auto_ptr<IOStream> stream(protocol.ReceiveStream());
	
	// check uncertainty
	TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown));
	
	printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size");
	
	// Then check the contents
	int values[998];
	int v = value;
	int count = 0;
	int bytesleft = 0;
	int bytessofar = 0;
	while(stream->StreamDataLeft())
	{
		// Read some data
		int bytes = stream->Read(((char*)values) + bytesleft, sizeof(values) - bytesleft);
		bytessofar += bytes;
		bytes += bytesleft;
		int n = bytes / 4;
		//printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar);
		for(int t = 0; t < n; ++t)
		{
			if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v);
			TEST_THAT(values[t] == v++);
		}
		count += n;
		bytesleft = bytes - (n*4);
		if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft);
	}
	
	TEST_THAT(bytesleft == 0);
	TEST_THAT(count == (24273*3));	// over 64 k of data, definately
}


int test(int argc, const char *argv[])
{
	// Server launching stuff
	if(argc >= 2)
	{
		if(strcmp(argv[1], "srv1") == 0)
		{
			// Run very basic daemon
			basicdaemon daemon;
			return daemon.Main("doesnotexist", argc - 1, argv + 1);
		}
		else if(strcmp(argv[1], "srv2") == 0)
		{
			// Run daemon which accepts connections
			testserver daemon;
			return daemon.Main("doesnotexist", argc - 1, argv + 1);
		}		
		else if(strcmp(argv[1], "srv3") == 0)
		{
			testTLSserver daemon;
			return daemon.Main("doesnotexist", argc - 1, argv + 1);
		}
		else if(strcmp(argv[1], "srv4") == 0)
		{
			testProtocolServer daemon;
			return daemon.Main("doesnotexist", argc - 1, argv + 1);
		}
	}

//printf("SKIPPING TESTS------------------------\n");
//goto protocolserver;

	// Launch a basic server
	{
		int pid = LaunchServer("./test srv1 testfiles/srv1.conf", "testfiles/srv1.pid");
		TEST_THAT(pid != -1 && pid != 0);
		if(pid > 0)
		{
			// Check that it's written the expected file
			TEST_THAT(TestFileExists("testfiles/srv1.test1"));
			TEST_THAT(ServerIsAlive(pid));
			// Move the config file over
			TEST_THAT(::rename("testfiles/srv1b.conf", "testfiles/srv1.conf") != -1);
			// Get it to reread the config file
			TEST_THAT(HUPServer(pid));
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));
			// Check that new file exists
			TEST_THAT(TestFileExists("testfiles/srv1.test2"));
			// Kill it off
			TEST_THAT(KillServer(pid));
			TestRemoteProcessMemLeaks("generic-daemon.memleaks");
		}
	}
	
	// Launch a test forking server
	{
		int pid = LaunchServer("./test srv2 testfiles/srv2.conf", "testfiles/srv2.pid");
		TEST_THAT(pid != -1 && pid != 0);
		if(pid > 0)
		{
			// Will it restart?
			TEST_THAT(ServerIsAlive(pid));
			TEST_THAT(HUPServer(pid));
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));
			// Make some connections
			{
				SocketStream conn1;
				conn1.Open(Socket::TypeINET, "localhost", 2003);
				SocketStream conn2;
				conn2.Open(Socket::TypeUNIX, "testfiles/srv2.sock");
				SocketStream conn3;
				conn3.Open(Socket::TypeINET, "localhost", 2003);
				// Quick check that reconnections fail
				TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX, "testfiles/srv2.sock");, ServerException, SocketAlreadyOpen);
				// Stuff some data around
				std::vector<IOStream *> conns;
				conns.push_back(&conn1);
				conns.push_back(&conn2);
				conns.push_back(&conn3);
				Srv2TestConversations(conns);
				// Implicit close
			}
			// HUP again
			TEST_THAT(HUPServer(pid));
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));
			// Kill it
			TEST_THAT(KillServer(pid));
			::sleep(1);
			TEST_THAT(!ServerIsAlive(pid));
			TestRemoteProcessMemLeaks("test-srv2.memleaks");
		}
	}

	// Launch a test SSL server
	{
		int pid = LaunchServer("./test srv3 testfiles/srv3.conf", "testfiles/srv3.pid");
		TEST_THAT(pid != -1 && pid != 0);
		if(pid > 0)
		{
			// Will it restart?
			TEST_THAT(ServerIsAlive(pid));
			TEST_THAT(HUPServer(pid));
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));
			// Make some connections
			{
				// SSL library
				SSLLib::Initialise();
			
				// Context first
				TLSContext context;
				context.Initialise(false /* client */,
						"testfiles/clientCerts.pem",
						"testfiles/clientPrivKey.pem",
						"testfiles/clientTrustedCAs.pem");

				SocketStreamTLS conn1;
				conn1.Open(context, Socket::TypeINET, "localhost", 2003);
				SocketStreamTLS conn2;
				conn2.Open(context, Socket::TypeUNIX, "testfiles/srv3.sock");
				SocketStreamTLS conn3;
				conn3.Open(context, Socket::TypeINET, "localhost", 2003);
				// Quick check that reconnections fail
				TEST_CHECK_THROWS(conn1.Open(context, Socket::TypeUNIX, "testfiles/srv3.sock");, ServerException, SocketAlreadyOpen);
				// Stuff some data around
				std::vector<IOStream *> conns;
				conns.push_back(&conn1);
				conns.push_back(&conn2);
				conns.push_back(&conn3);
				Srv2TestConversations(conns);
				// Implicit close
			}
			// HUP again
			TEST_THAT(HUPServer(pid));
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));
			// Kill it
			TEST_THAT(KillServer(pid));
			::sleep(1);
			TEST_THAT(!ServerIsAlive(pid));
			TestRemoteProcessMemLeaks("test-srv3.memleaks");
		}
	}
	
//protocolserver:
	// Launch a test protocol handling server
	{
		int pid = LaunchServer("./test srv4 testfiles/srv4.conf", "testfiles/srv4.pid");
		TEST_THAT(pid != -1 && pid != 0);
		if(pid > 0)
		{
			::sleep(1);
			TEST_THAT(ServerIsAlive(pid));

			// Open a connection to it		
			SocketStream conn;
			conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock");
			
			// Create a protocol
			TestProtocolClient protocol(conn);

			// Simple query
			{
				std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(41));
				TEST_THAT(reply->GetValuePlusOne() == 42);
			}
			{
				std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(809));
				TEST_THAT(reply->GetValuePlusOne() == 810);
			}
			
			// Streams, twice, both uncertain and certain sizes
			TestStreamReceive(protocol, 374, false);
			TestStreamReceive(protocol, 23983, true);
			TestStreamReceive(protocol, 12098, false);
			TestStreamReceive(protocol, 4342, true);
			
			// Try to send a stream
			{
				CollectInBufferStream s;
				char buf[1663];
				s.Write(buf, sizeof(buf));
				s.SetForReading();
				std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QuerySendStream(0x73654353298ffLL, s));
				TEST_THAT(reply->GetStartingValue() == sizeof(buf));
			}

			// Lots of simple queries
			for(int q = 0; q < 514; q++)
			{
				std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(q));
				TEST_THAT(reply->GetValuePlusOne() == (q+1));
			}
			// Send a list of strings to it
			{
				std::vector<std::string> strings;
				strings.push_back(std::string("test1"));
				strings.push_back(std::string("test2"));
				strings.push_back(std::string("test3"));
				std::auto_ptr<TestProtocolClientListsReply> reply(protocol.QueryLists(strings));
				TEST_THAT(reply->GetNumberOfStrings() == 3);
			}
			
			// And another
			{
				std::auto_ptr<TestProtocolClientHello> reply(protocol.QueryHello(41,87,11,std::string("pingu")));
				TEST_THAT(reply->GetNumber32() == 12);
				TEST_THAT(reply->GetNumber16() == 89);
				TEST_THAT(reply->GetNumber8() == 22);
				TEST_THAT(reply->GetText() == "Hello world!");
			}
		
			// Quit query to finish
			protocol.QueryQuit();
		
			// Kill it
			TEST_THAT(KillServer(pid));
			::sleep(1);
			TEST_THAT(!ServerIsAlive(pid));
			TestRemoteProcessMemLeaks("test-srv4.memleaks");
		}
	}

	return 0;
}

