#include "cdevServer.h"
#include "ClientAcceptor.h"
#include "ClientHandler.h"

sig_atomic_t            cdevServer::Finished = 0;
SignalManager           cdevServer::SigManager;
cdevGenericServerTagDef cdevServer::tags;

// *****************************************************************************
// * This function is an interrupt handler that will be executed whenever the
// * program receives a SIGINT. When called it will set the finished flag to 1
// * and trigger the termination of the program.
// *****************************************************************************
static void SIGINT_handler (int signo)
	{
	cdevServer::Finished = 1;
	signal(signo, SIGINT_handler);
	}


// *****************************************************************************
// * cdevServer::cdevServer :
// *	This is the constructor for the class.  It will post the listening
// *	socket, establish registration with the name server and record all
// *	other pertinent information.
// *****************************************************************************
cdevServer::cdevServer (char * DomainName, char * ServerName, unsigned short Port, double Rate)
	: cdevSessionManager(),
	  serverName (NULL),
	  acceptor   (NULL),
	  timer      (NULL),
	  status     (UNINITIALIZED),
	  serverInfo (NULL)
	{
	// *********************************************************************
	// * Register the import method for the cdevMessage class with the 
	// * cdevPacket class.
	// *********************************************************************
	cdevPacket::registerImportMethod(cdevMessage::CDEV_PACKET_VERSION, 
	                                 cdevMessage::import);

	startServer(DomainName, ServerName, Port, Rate, 0);
	}

// *****************************************************************************
// * cdevServer::cdevServer :
// *	This is the do nothing constructor for the class.
// *****************************************************************************
cdevServer::cdevServer ( void )
	: cdevSessionManager (),
	  serverName (NULL),
	  acceptor   (NULL),
	  timer      (NULL),
	  status     (UNINITIALIZED),
	  serverInfo (NULL)
	{
	// *********************************************************************
	// * Register the import method for the cdevMessage class with the 
	// * cdevPacket class.
	// *********************************************************************
	cdevPacket::registerImportMethod(cdevMessage::CDEV_PACKET_VERSION, 
	                                 cdevMessage::import);

	}

// *****************************************************************************
// * cdevServer::~cdevServer :
// *	This is the destructor for the object.  It will close all listening
// *	sockets and will delete all allocated items.
// *****************************************************************************
cdevServer::~cdevServer ( void )
	{
	outputError ( CDEV_SEVERITY_INFO, "CDEV Server",
		     "Server %s is Terminating...",
		      serverName);

	Reactor.extractHandler(this);

	if(timer!=NULL)      delete timer;
	if(serverName!=NULL) delete serverName;
	if(acceptor!=NULL)   delete acceptor;
	}


// *****************************************************************************
// * cdevServer::startServer :
// *	This method will initialize a cdevServer within a given name, will
// *	register it within the CDEV Name Server and will commence listening
// *	for connections on the specified port.
// *****************************************************************************
int cdevServer::startServer (char * DomainName, char * ServerName, unsigned short Port, double Rate, int searchForPort)
	{
	if(acceptor!=NULL) 
		{ 
		delete acceptor; 
		acceptor=NULL; 
		}	
	if(timer)
		{
		delete timer;
		timer=NULL;
		}
	if(serverName)
		{
		delete serverName;
		serverName = NULL;
		}
	if(serverInfo)
		{
		delete serverInfo;
		serverInfo = NULL;
		}
		
	Reactor.extractHandler(this);
	
	setTimeoutRate(Rate);
	serverName = strdup(ServerName);
	status     = UNINITIALIZED;
	acceptor   = new ClientAcceptor(*this);
	
	// *********************************************************************
	// * Attempt to open the acceptor to receive new connections.
	// *********************************************************************
	if(searchForPort) Port = 0;
	cdevInetAddr addr(Port);
	int initialized=!(acceptor->open(addr));

	// *********************************************************************
	// * Call getLocalAddress to retrieve the actual port number that was 
	// * opened. This value will differ from the specified port if...
	// * 	1) the user specified 0 for the port number -or-
	// *	2) the user set the searchForPort flag to non-zero.
	// *********************************************************************
	if(acceptor->getLocalAddress(addr)==0) Port = addr.getPortNum();
	else Port = (unsigned short)-1;
	
	// *********************************************************************
	// * ServerInfo must be created after the port has been searched for
	// * and found.  Otherwise, the server will report the original port
	// * number rather than the actual port number.
	// *********************************************************************
	serverInfo = new ServerInfo(DomainName, ServerName, Port);

	if(initialized)
		{
		// *************************************************************
		// * Register the acceptor with the Reactor.
		// *************************************************************
		if(Reactor.registerHandler(acceptor, READ_MASK)!=-1)
			{
			// *****************************************************
			// * Register this event handler with the Reactor.
			// *****************************************************
			if(Reactor.registerHandler(this, READ_MASK)!=-1)
				{
				// *********************************************
				// * Schedule the periodic timer if it is
				// * greater than 0.
				// *********************************************
				if(Rate <= 0.0 || 
				   Reactor.registerTimer(this)!=-1)
					{
					// *************************************
					// * Install a name server timer to
					// * continuously register this server
					// * with the Name Server.
					// *************************************
					timer = new cdevNameServerManager(Reactor, DomainName, ServerName, Port);
					outputError (CDEV_SEVERITY_INFO, "CDEV Server",
						     "Server %s is operational and servicing requests...",
						     serverName);
					}
				else
					{
					outputError ( CDEV_SEVERITY_SEVERE, "CDEV Server",
				                  "Server %s - Unable to register server timer with cdevReactor",
				 	           serverName);
					status = CANT_REGISTER_TIMER;
					}
				}
			else
				{
				outputError ( CDEV_SEVERITY_SEVERE, "CDEV Server",
		        	          "Server %s - Unable to register server handler with cdevReactor",
			 	           serverName);
				status = CANT_REGISTER_SERVER;
				}
			}
		else
			{
			outputError (CDEV_SEVERITY_SEVERE, "CDEV Server",
			          "Server %s - Unable to register listening service with cdevReactor",
			          serverName);
			status = CANT_REGISTER_LISTENER;
			}
		}
	else
		{
		outputError ( CDEV_SEVERITY_SEVERE, "CDEV Server", 
		          "Server %s - Unable to open listening socket",
		           serverName);
		status = CANT_OPEN_SOCKET;
		}
		
	switch (status)
		{
		case CANT_REGISTER_TIMER:
		Reactor.extractHandler(this);
		
		case CANT_REGISTER_SERVER:
		Reactor.extractHandler(acceptor);
		
		case CANT_REGISTER_LISTENER:		
		case CANT_OPEN_SOCKET:
		delete acceptor;
		acceptor = NULL;
		break;
			
		case SUCCESS:
		default:
		status = SUCCESS;
		break;
		}	

	return status;
	}


// ***************************************************************************
// * cdevServer::runServer :
// *	This method is called to simultaneously execute all servers that
// *	reside in the system.
// ***************************************************************************
void cdevServer::runServer ( void )
	{
	SigManager.installDefaults(); 
	SigManager.installHandler (SIGINT, SIGINT_handler);
	while (!Finished) Reactor.handleEvents ();
	}


// *****************************************************************************
// * cdevServer::registerClient :
// *	This is the cdevMessage specific method  that is used to send a 
// *	"register" message to the server.
// *****************************************************************************
void cdevServer::registerClient ( short localID )
	{
	cdevMessage        message(localID,0,0,0,0,0,0,0,NULL,"register");
	cdevPacketBinary * packet = new cdevPacketBinary;
	char             * binary;
	size_t             binaryLen;

	message.streamOut (&binary, &binaryLen);
	packet->attachData(binary, binaryLen);
	inbound.enqueue   (packet);
	}


// *****************************************************************************
// * cdevServer::unregisterClient :
// *	This is the cdevMessage specific method  that is used to send an 
// *	"unregister" message to the server.
// *****************************************************************************
void cdevServer::unregisterClient   ( short localID )
	{
	cdevMessage        message(localID,0,0,0,0,0,0,0,NULL,"unregister");
	cdevPacketBinary * packet = new cdevPacketBinary;
	char             * binary;
	size_t             binaryLen;

	message.streamOut (&binary, &binaryLen);
	packet->attachData(binary, binaryLen);
	inbound.enqueue   (packet);
	trigger.insertEvent();	
	}


// *****************************************************************************
// * cdevServer::newClientSession :
// *	This method allows the caller to create a new ClientSession object.  The
// *	ClientSession object functions primarily as a pointer to the queue that
// *	holds packets destined for a specific client, however, the developer
// *	can create a subclass of the ClientSession that may be used to associate
// *	additional, client specific information to the structure.
// * 
// *	The CLIPClientSession class allows the developer to associate a context
// *	with the clientID.
// *****************************************************************************
ClientSession * cdevServer::newClientSession ( int SocketID, int ClientID, int LocalID )
	{
	return new CLIPClientSession (SocketID, ClientID, LocalID);
	}
	

// *****************************************************************************
// * cdevServer::newSocketSession :
// *	This method allows the caller to create a new SocketSession object.  The
// *	SocketSession object functions primarily as a pointer to the queue that
// *	holds packets destined for a specific socket, however, the developer
// *	can create a subclass of the SocketSession that may be used to associate
// *	additional, socket specific information to the structure.
// * 
// *	The CLIPSocketSession class allows the developer to associate a 
// *	context map and a tag table with the socket number.
// *****************************************************************************
SocketSession * cdevServer::newSocketSession ( int SocketID )
	{
	if(serverInfo) serverInfo->clientCnt++;

	return new CLIPSocketSession (SocketID);
	}

// *****************************************************************************
// * cdevServer::deleteSocketSession :
// *	This method is called to delete an existing socket session.
// *****************************************************************************
void cdevServer::deleteSocketSession ( SocketSession *socket )
	{
	if(socket)
		{
		if(serverInfo)
			{
			if(serverInfo->clientCnt>0) serverInfo->clientCnt--;
			else                        serverInfo->clientCnt=0;
			}
			
		sockets.remove(socket->getSocketID());
		delete socket;
		}
	}

// *****************************************************************************
// * cdevServer::dequeue :
// *	This method provides a mechanism for the cdevServer object to only
// *	dequeue packets that are of the type cdevMessage.
// *****************************************************************************
int cdevServer::dequeue ( cdevMessage * &message )
	{
	cdevPacket * packet = NULL;
		
	while(packet==NULL && cdevSessionManager::dequeue(packet)==0)
		{
		if(packet->getVersion()!=cdevMessage::CDEV_PACKET_VERSION)
			{
			delete packet;
			packet = NULL;
			}
		else if(((cdevMessage *)packet)->getOperationCode()==CDEV_SERVER_OP)
			{
			processLocal((cdevMessage * &)packet);
			delete packet;
			packet = NULL;
			}
		}
	
	message = (cdevMessage *)packet;
	return message==NULL?-1:0;	      
	}
	
// *****************************************************************************
// * cdevServer::processLocal :
// *	This method is called to process requests that are being transmitted
// *	directly to the cdev Generic Server Engine to set or retrieve statistics
// *	about the operation of the server or its clients.
// *****************************************************************************
void cdevServer::processLocal ( cdevMessage * & message ) 
	{
	CLIPClientSession * client  = NULL;
	CLIPSocketSession * socket  = NULL;
	
	if((client = (CLIPClientSession *)findLocalClient(message->getClientID()))!=NULL)
		{
	   	socket = (CLIPSocketSession *)findSocket(client->getSocketID());
		}
	if(!strcmp(message->getMessage(), "set ClientInfo"))
		{
		cdevData * data = message->getData();
		if(data && socket) socket->updateClientInfo(*data);
		}
	else if(!strcmp(message->getMessage(), "get ServerInfo"))
		{
		if(serverInfo)
			{
			message->setOperationCode(CDEV_NORMAL_OP);
			message->setData(&serverInfo->getServerData(), 1);
			}
		else	{
			message->setOperationCode(CDEV_NORMAL_OP);
			message->setCompletionCode(-1);
			}
		enqueue(message);	
		}
	else if(!strcmp(message->getMessage(), "get ClientInfo"))
		{
		IntHashIterator iter(&sockets);
		ClientHandler * handler   = NULL;
		int             socketCnt = 0;
		int             index     = 0;
		cdevData        result;
		
		iter.first();
		while(iter.data()!=NULL) 
			{
			iter++;
			socketCnt++;
			}
		if(socketCnt)
			{
			char    ** username    = new char *[socketCnt];
			char    ** group       = new char *[socketCnt];
			unsigned * uid         = new unsigned [socketCnt];
			unsigned * gid         = new unsigned [socketCnt];
			unsigned * pid         = new unsigned [socketCnt];
			char    ** program     = new char *[socketCnt];
			char    ** commandline = new char *[socketCnt];
			unsigned * starttime   = new unsigned [socketCnt];
			unsigned * connecttime = new unsigned [socketCnt];
			char    ** host        = new char *[socketCnt];
			char    ** os          = new char *[socketCnt];
			char    ** osrelease   = new char *[socketCnt];
			char    ** osversion   = new char *[socketCnt];
			char    ** machine     = new char *[socketCnt];
			char    ** shell       = new char *[socketCnt];
			unsigned * socketNum   = new unsigned[socketCnt];
			unsigned * sendPktCnt  = new unsigned[socketCnt];
			unsigned * recvPktCnt  = new unsigned[socketCnt];
			
			iter.first();
			while(index<socketCnt && (socket = (CLIPSocketSession *)iter.data())!=NULL)
				{
				username[index]    = socket->getUsername();
				group[index]       = socket->getGroup();
				uid[index]         = socket->getUid();
				gid[index]         = socket->getGid();
				pid[index]         = socket->getPid();
				program[index]     = socket->getProgram();
				commandline[index] = socket->getCommandLine();
				starttime[index]   = (unsigned)socket->getStartTime();
				connecttime[index] = (unsigned)socket->getConnectTime();
				host[index]        = socket->getHost();
				os[index]          = socket->getOs();
				osrelease[index]   = socket->getOsRelease();
				osversion[index]   = socket->getOsVersion();
				machine[index]     = socket->getMachine();
				shell[index]       = socket->getShell();
				socketNum[index]   = socket->getSocketID();
				if(Reactor.getHandler(socketNum[index], (cdevEventHandler *&)handler)==0)
					{
					sendPktCnt[index] = handler->getPacketsSent();
					recvPktCnt[index] = handler->getPacketsRecv();
					}
				else	{
					sendPktCnt[index] = 0;
					recvPktCnt[index] = 0;
					}
				index++;
				iter++;
				}
			
			result.insert("username", username, socketCnt);       delete username;
			result.insert("group", group, socketCnt);             delete group;
			result.insert("uid", uid, socketCnt);                 delete uid;
			result.insert("gid", gid, socketCnt);                 delete gid;
			result.insert("pid", pid, socketCnt);                 delete pid;
			result.insert("program", program, socketCnt);         delete program;
			result.insert("commandline", commandline, socketCnt); delete commandline;
			result.insert("starttime", starttime, socketCnt);     delete starttime;
			result.insert("connecttime", connecttime, socketCnt); delete connecttime;
			result.insert("host", host, socketCnt);               delete host; 
			result.insert("os", os, socketCnt);                   delete os;
			result.insert("osrelease", osrelease, socketCnt);     delete osrelease;
			result.insert("osversion", osversion, socketCnt);     delete osversion;
			result.insert("machine", machine, socketCnt);         delete machine;
			result.insert("shell", shell, socketCnt);             delete shell;
			result.insert("socket", socketNum, socketCnt);        delete socketNum;
			result.insert("sendPktCnt", sendPktCnt, socketCnt);   delete sendPktCnt;
			result.insert("recvPktCnt", recvPktCnt, socketCnt);   delete recvPktCnt;
			}
		message->setOperationCode(CDEV_NORMAL_OP);
		message->setData(&result, 1);
		enqueue(message);	
		}
	}

// *****************************************************************************
// * cdevServer::decodePacket :
// *	This method is used to perform preprocessing on a newly dequeued binary
// *	packet before it is provided to the caller.  This method allows the 
// *	developer to perform special preparations on the packet before providing
// *	it to the caller.
// *****************************************************************************
cdevPacket * cdevServer::decodePacket( cdevPacketBinary * input )
	{
	cdevPacket    * packet = NULL;

	if(input!=NULL) 
	   	{
	  	short version;
	  	input->getVersion(version);
	   	switch (version)
			{
			// *****************************************************
			// * If it is a cdevMessage object, I want to call the
			// * cdevMessage specific decodePacket method.
			// *****************************************************
			case cdevMessage::CDEV_PACKET_VERSION:
			packet = decodePacket ((cdevMessage *)cdevPacket::import(*input));
			break;
			
			// *****************************************************
			// * Use the cdevSessionManager::decodePacket method
			// * to handle any other type of packet.
			// *****************************************************
			default:
			packet = cdevSessionManager::decodePacket(input);
			break;
			}
		if(serverInfo) serverInfo->recvPktCnt++;
		}
	return packet;
	} 

// *****************************************************************************
// * cdevServer::decodePacket :
// *	This decodePacket method is designed specifically to preprocess the 
// *	data associated with a cdevMessage object.
// *****************************************************************************	
cdevPacket * cdevServer::decodePacket (cdevMessage * message )
	{
	CLIPClientSession * client = NULL;
	CLIPSocketSession * socket = NULL;
	
	if(message!=NULL &&
	   (client = (CLIPClientSession *)findLocalClient(message->getClientID()))!=NULL && 
	   (socket = (CLIPSocketSession *)findSocket(client->getSocketID()))!=NULL)
	   	{
		// *************************************************************
		// * If a tagMap has been provided... use it to update
		// * the tagMap stored in the SocketSession object.
		// *************************************************************
		if(message->getTagMap()!=NULL)
			{
			socket->TagMap().updateTagMap(*message->getTagMap());
			message->setTagMap(NULL);
			}
			
		// *************************************************************
		// * Pass the data and context objects through the tag map to 
		// * convert their tags from the remote integers to the local 
		// * integers.
		// *************************************************************
		if(message->getData()!=NULL) 
			socket->TagMap().remoteToLocal(*message->getData());
		if(message->getContext()!=NULL) 
			socket->TagMap().remoteToLocal(*message->getContext());

		// *************************************************************
		// * If a context has been provided by the client side 
		// * of the connection - perform the following steps...
		// *
		// * 1) Add the context to the cdevContextMap for this 
		// *	socketID if it does not already exist, and 
		// *	obtain the index that identifies the new 
		// *	context.
		// *
		// * 2)	Delete the context from within the message.
		// *	
		// * 3) Set the context within the message to the 
		// *	current context that is specified in the 
		// *	ClientSession object.
		// *
		// * 4) Set the saveContext flag in the message to 
		// *	prevent its inadvertant destruction when the 
		// *	message is deleted.
		// *************************************************************
		if(message->getContext()!=NULL) 
			{
			cdevData * lastContext = client->getContext();
			if(lastContext==NULL || *lastContext!=*message->getContext())
				{
				int contextID = socket->ContextMap().insert(*message->getContext());
				client->setContext(socket->ContextMap().find(contextID));
				}
			}
		message->setContext(client->getContext(), 1);

		// *************************************************************
		// * Toggle the local and foreign data indices.
		// *************************************************************
		unsigned int index = message->getForeignDataIndex();
		message->setForeignDataIndex(message->getLocalDataIndex());
		message->setLocalDataIndex  (index);
		}		

	return message;
	}


// *****************************************************************************
// * cdevSessionManager::encodePacket :
// *	This method is used to perform postprocessing on a packet that has been
// *	enqueued to be sent to a client.  This method allows the developer to 
// *	perform special preparations on the packet before providing it to the 
// *	client.
// *****************************************************************************
cdevPacketBinary * cdevServer::encodePacket ( cdevPacket * input )
	{
	cdevPacketBinary * packet = NULL;
	
	if(input!=NULL)
		{
		switch(input->getVersion())
			{
			case cdevMessage::CDEV_PACKET_VERSION:
			packet = encodePacket ((cdevMessage *)input);
			break;

			default:
			packet = cdevSessionManager::encodePacket(input);
			break;
			}
		if(serverInfo) serverInfo->sendPktCnt++;
		}
	return packet;
	}
			

// *****************************************************************************
// * cdevServer::encodePacket :
// *	This encodePacket method is designed specifically to postprocess the 
// *	data associated with a cdevMessage object.
// *****************************************************************************	
cdevPacketBinary * cdevServer::encodePacket (cdevMessage * message )
	{
	cdevPacketBinary  * result = NULL;
	CLIPClientSession * client = NULL;
	CLIPSocketSession * socket = NULL;
		
	if(message!=NULL && 
	   message->getClientID()>0 &&
	   message->getTransIndex()>0 &&
	   (client = (CLIPClientSession *)findLocalClient(message->getClientID()))!=NULL &&
	   (socket = (CLIPSocketSession *)findSocket(client->getSocketID()))!=NULL)
		{
		char *             binary    = NULL;
		size_t             binaryLen = 0;
		static cdevMessage outBound;
				
		// *************************************************************
		// * Remap the data to its foreign design.
		// *************************************************************
		if(message->getData()!=NULL) 
			socket->TagMap().localToRemote(*message->getData());	

		// *************************************************************
		// * Transfer the critical data items into the class.  Note that 
		// * we are not returning the deviceList, message, context, 
		// * cancelTransIndex, or operationCode with the return packet.
		// *
		// * Also note that the cdevData object is marked as permanent 
		// * and is using a pointer to the same cdevData object that is 
		// * in the message object.
		// *************************************************************
		outBound.clear();
		outBound.setClientID         (client->getClientID()&0xFFFF);
		outBound.setTransIndex       (message->getTransIndex());
		outBound.setLocalDataIndex   (message->getForeignDataIndex());
		outBound.setForeignDataIndex (message->getLocalDataIndex());
		outBound.setOperationCode    (message->getOperationCode());
		outBound.setCompletionCode   (message->getCompletionCode());
		outBound.setData             (message->getData(), 1);
		
		// *************************************************************
		// * Create a binary stream from the cdevMessage object and then
		// * place it into the outbound queue system.
		// *************************************************************
		outBound.streamOut(&binary, &binaryLen);
		outBound.clear ();

		result = new cdevPacketBinary;
		result->attachData(binary, binaryLen);
		
		// *************************************************************
		// * Remap the data to its local design.
		// *************************************************************
		if(message->getData()!=NULL)
			socket->TagMap().remoteToLocal(*message->getData());
		}
	return result;	
	}

