/* Agent Tcl
   Bob Gray
   26 June 1995

   genConnection.cc

   This file implements the classes that handle message-oriented connections.

   Copyright (c) 1995-1998, Robert S. Gray Dartmouth College

   See the file "agent.terms" for information on usage and redistribution
   of this file and for a DISCLAIMER OF ALL WARRANTIES.
*/

#ifndef NO_PRAGMAS
#pragma implementation
#endif

#include "platPorting.h"	// platform-specific definitions
#include "platDelete.h"		// DELETE_OBJECT and DELETE_ARRAY_OBJECT
#include "genMesgDriver.h"	// MessageDriver

#include "platCharact.h"
#include "platExclusion.h"
#include "platSigio.h"		// SigioTrap
#include "suppStrings.h"
#include "mesgTcpip.h"
#include "mesgMachId.h"		// MachineId
#include "genAgentIO.h"		// AgentIO
#include "genDefaults.h"	// AGENT_UNIX_ENV and AGENT_TCPIP_ENV
#include "genLocation.h"	// LOCATION

#include "genAgent.h"
#include "genAgentConn.h"
#include "suppDBuffer.h"
#include "genEncrypt.h"
#include "genMasks.h"
#include "genMessage.h"
#include "genTransmit.h"

    /* static data */

UINT_32 AgentConnection::m_port
	= 0;

char *AgentConnection::m_unixsocket
	= NULL;

AgentConnectionProxy::~AgentConnectionProxy ()
{
    if (m_connectionOwner) {
	DELETE_OBJECT(m_connection);
    }
}

AgentConnectionProxy *AgentConnectionProxy::makeConnection 
    (struct timeval stop, 
     AGENT *agent, 
     const MachineId& machine, 
     const BASE_SECURITY &security,
     int &errorCode,
     AgentConnectionProxy::BackgroundHandler backgroundHandler
    )
{
    int rc;
    AgentIO *io;
    LOCATION *location;
    AgentConnection *connection;
    AgentConnectionProxy *proxy;
    MachineId scratchMachine = machine;
	
	/* background connection is not always allowed */

    if (backgroundHandler == e_BACKGROUND_OKAY) {

	    /* get the IP address of the machine */

	TcpipErrors tcpipRc;

        if ((tcpipRc = tcpip_getIP (scratchMachine, stop)) == e_TCPIP_TIMEOUT) {
	    errorCode = MESSAGE_TIMEOUT;
	    return NULL;
        } else if (tcpipRc != e_TCPIP_OK) {
	    errorCode = MESSAGE_NOT_FOUND;
	    return NULL;
        }

	    /* get the AGENT_LOCATION */

        location = agent -> get_location ();
        assert (location != NULL);

	    /* get the actual IP address of the machine */

        if (scratchMachine.getIp() == location -> getLocalhostIp()) {
	    if (location -> getMachineIp() != UNKNOWN_IP) {
	        scratchMachine.setIp (location -> getMachineIp());
	    }
        }

	    /* see if we can use the background connection */

        if (scratchMachine.getIp() == location -> getLocalIp()) {

	    io = agent -> get_io();
	    assert (io != NULL);

	    if ((connection = io -> getBackgroundConnection ()) != NULL) {
	        proxy = new AgentConnectionProxy (connection);
	        return (proxy);
	    }
        }
    }

	/* otherwise we have to create a new connection */

    connection = new AgentConnection (agent, scratchMachine, security);

    if ((rc = connection -> connect (stop)) != MESSAGE_OK) {
	DELETE_OBJECT(connection);
	errorCode = rc;
	return (NULL);
    }

    proxy = new AgentConnectionProxy (connection, e_TRUE);
    errorCode = MESSAGE_OK;
    return (proxy);
}

class SocketInitializer: public OnceTrap
{
    char *&m_unixSocket;	    // reference to m_unixsocket above
    unsigned int &m_tcpipSocket;    // reference to m_port above

public:

    SocketInitializer (char *&unixSocket, unsigned int &tcpipSocket):
	m_unixSocket (unixSocket),
	m_tcpipSocket (tcpipSocket)
    {
	// empty
    }

    void initialize (void) {

	/* initialize the TCP/IP port number and the Unix socket filename */

	if (m_unixSocket == NULL) {

	    char *ptr;

	    if ((ptr = getenv(AGENT_UNIX_ENV)) == NULL) {
		m_unixSocket = UNIX_SOCKET;
	    } else {
	        m_unixSocket = strcpyWithAlloc (ptr);
	    }

	    if ((ptr = getenv(AGENT_TCPIP_ENV)) == NULL) {
	        m_tcpipSocket = SERVER_TCP_PORT;
	    } else if (sscanf (ptr, "%u", &m_tcpipSocket) < 1) {
	        fprintf (stderr, "\nFATAL ERROR: environment variable \"%s\" must contain an integer port number!\n\n", AGENT_TCPIP_ENV);
	        exit (1);
	    }
	}
    }
};

AgentConnection::AgentConnection (AGENT *agent, const MachineId& machine, const BASE_SECURITY &security):
    m_agent (agent),
    m_machine (machine),
    m_security (security),
    m_connection (NULL),
    m_backgroundConnection (e_FALSE),
    m_trap (NULL),
    m_lock (NULL),
    m_locks (NULL)
{
	// assertions

    assert (m_agent != NULL); 

	// figure out the Unix socket name and the TCP/IP port

    static MonitorOnce::OnceControl onceControl = MonitorOnce::e_NEVER;

    if (onceControl != MonitorOnce::e_DONE) {
	SocketInitializer socketInitializer (m_unixsocket, m_port);
	MonitorOnce::doOnce (onceControl, socketInitializer);
    }

	/* initialize locking structures */

#ifdef FIX_LATER
	/* even in a multi-threaded environment, we only need a lock */
	/* for the one background connection                         */
#endif

#ifdef MULTIPLE_THREADS
    m_lock = new MonitorLock ();
    m_locks = new MonitorGroup ();
    m_locks -> push (agent -> getMonitorLock());
    m_locks -> push (*m_lock);
#endif
}

AgentConnection::AgentConnection (int fd, AGENT *agent, const MachineId& machine, const BASE_SECURITY &security, AgentConnection::FdDisposition fdDisposition):
    m_agent (agent),
    m_machine (machine),
    m_security (security),
    m_connection (NULL),
    m_backgroundConnection (e_FALSE),
    m_trap (NULL),
    m_lock (NULL),
    m_locks (NULL)
{
    assert (m_agent != NULL);

    if (fdDisposition == e_Close) {
	m_connection = new SocketConnection (fd, SocketConnection::e_Close);
    } else {
	m_connection = new SocketConnection (fd, SocketConnection::e_NoClose);
    }

#ifdef MULTIPLE_THREADS
    m_lock = new MonitorLock ();
    m_locks = new MonitorGroup ();
    m_locks -> push (agent -> getMonitorLock());
    m_locks -> push (*m_lock);
#endif
}
 
AgentConnection::~AgentConnection ()
{
    DELETE_OBJECT(m_connection);
}

BOOLEAN AgentConnection::isBackground (void)
{
    return (m_backgroundConnection);
}

void AgentConnection::makeBackground (ConnectionTrap *trap, MonitorGroup group)
{
	/* assertions */

    assert (m_connection != NULL);
    assert (m_trap == NULL);

	/* expand the set of monitor locks if necessary */

#ifdef MULTIPLE_THREADS
    m_lock -> acquire ();
    group.push (*m_lock);
#endif

	/* turn on the background handler */

    m_trap = trap;
    m_connection -> turnBackgroundOn (this, group);
    m_backgroundConnection = e_TRUE;

	/* release our locks */

#ifdef MULTIPLE_THREADS
    m_lock -> release ();
#endif
}

int AgentConnection::isMessagePending (int& flags)
{
    Connection::ConnectionErrors rc;

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> acquire ();
    }
#endif

    assert (m_connection != NULL);
    rc = m_connection -> checkReadyStatus (SigioTrap::e_READ_READY, flags);

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> release();
    }
#endif

    return (MessageDriver::connectionCodeToMessageCode(rc));
}

int AgentConnection::connect (struct timeval stop)
{
    int returnCode;
    LOCATION *location;
    TcpipErrors tcpipRc;

	/* get the IP address of the machine */

    if ((tcpipRc = tcpip_getIP (m_machine, stop)) == e_TCPIP_TIMEOUT) {
	return MESSAGE_TIMEOUT;
    } else if (tcpipRc != e_TCPIP_OK) {
	return MESSAGE_NOT_FOUND;
    }

	/* get the AGENT_LOCATION */

    location = m_agent -> get_location ();
    assert (location != NULL);

	/* get the actual IP address of the machine */

    if (m_machine.getIp() == location -> getLocalhostIp()) {
	if (location -> getMachineIp() != UNKNOWN_IP) {
	    m_machine.setIp (location -> getMachineIp());
	}
    }

	/* lock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> acquireAll();
    }
#endif

	/* see if we can use the Unix domain socket */

    if (m_connection != NULL) {

	returnCode = MESSAGE_OK;

#ifndef NO_UNIX_SOCKET

    } else if ((m_machine.getIp() == location -> getLocalhostIp()) || 
	       (m_machine.getIp() == location -> getMachineIp())) {

	UnixClientConnection *connection;
	connection = new UnixClientConnection();
        Connection::ConnectionErrors connectionRc = 
	    connection -> connect (m_unixsocket, stop);
	returnCode = MessageDriver::connectionCodeToMessageCode (connectionRc);
	m_connection = connection;

#endif

    } else {

	TcpipClientConnection *connection;
        connection = new TcpipClientConnection ();
        Connection::ConnectionErrors connectionRc = 
	    connection -> connect (m_machine, m_port, stop);
	returnCode = MessageDriver::connectionCodeToMessageCode (connectionRc);
	m_connection = connection;
    }

	/* unlock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> release();
    }
#endif

    return (returnCode);
}

int AgentConnection::send (MESSAGE &message, struct timeval *stop)
{
    int rc;

	/* security is required only if we are sending over a new TCP/IP */
	/* link; security is *not* required if we are sending over the   */
	/* dedicated background connection				 */

    if (m_backgroundConnection) {

	    // NOTE: we can not have a timeout on the background connection
	    // so we ignore "stop" and pass NULL as the timeout parameter
	    // to unsecureSend

	rc = MessageDriver::unsecureSend 
	    (*m_connection, message, (struct timeval *) NULL);

    } else {

	rc = MessageDriver::secureSend 
	    (*m_connection, m_machine, m_security, message, stop);
    }

    return (rc);
}

int AgentConnection::unsecureSend (MESSAGE &message, struct timeval *stop)
{
    int rc;

	/* Security is required only if we are sending over a new TCP/IP */
	/* link; security is *not* required if we are sending over the   */
	/* dedicated background connection.  Note that in this case      */
	/* security just means sending an explicit indication that we do */
	/* not consider security necessary.                              */

    if (m_backgroundConnection) {

	    // NOTE: we can not have a timeout on the background connection
	    // so we ignore "stop" and pass NULL as the timeout parameter
	    // to unsecureSend

	rc = MessageDriver::unsecureSend 
	    (*m_connection, message, (struct timeval *) NULL);

    } else {

	MESSAGE securityMessage (protectionMessages.messages[PROT_NONE]);
	rc = MessageDriver::unsecureSend 
	    (*m_connection, securityMessage, stop);

	if (rc == MESSAGE_OK) {
	    rc = MessageDriver::unsecureSend 
	    	(*m_connection, message, stop);
	}
    }

    return (rc);
}

/* AgentConnection::unsecureReceiveNoncommNolock and
   AgentConnection::unsecureReceiveNonComm

    Output: The procedure returns MESSAGE_LOST_CONNECTION if the connection
            broke before the message could be received, MESSAGE_TIMEOUT if
	    the timeout expired before the message could be received, 
	    MESSAGE_INVALID if the incoming message is invalid (e.g., a
	    negative size), MESSAGE_SYS_ERROR on system error (e.g., unable
	    to allocate enough memory for the incoming message), and MESSAGE_OK
	    on success.  On success, the "receivedMessage" parameter 
	    (MESSAGE *&) is set to a dynamically allocated MESSAGE instance 
	    that contains the received message.
*/

int AgentConnection::unsecureReceiveNoncommNolock (MESSAGE *&receivedMessage, const MESSAGE_SET& set, struct timeval *stop)
{
    int messageRc;

	/* we can not have a timeout on the background connection */

    if (m_backgroundConnection) {
	stop = NULL;
    }

	/* assume that we will fail to receive a message */

    receivedMessage = NULL;

	/* get the masks */

    AgentMasks *masks = m_agent -> get_masks ();
    assert (masks != NULL);

	/* loop until we have the desired message -- handle any RESP_EVENT, */
	/* RESP_MEETING or RESP_MESSAGE messages that are received *before* */
	/* the desired message arrives                                      */

    while (1) {

	if ((messageRc = MessageDriver::unsecureReceive (receivedMessage, *m_connection, set, stop)) != MESSAGE_OK) {
	    break;
	}

	UINT_8 flag = receivedMessage -> getFlag();

	if ((flag != RESP_MEETING) && (flag != RESP_MESSAGE) && (flag != RESP_EVENT)) {
	    break;
	}

	masks -> handleIncoming_noLock (receivedMessage);
	DELETE_OBJECT(receivedMessage);
	receivedMessage = NULL;
    }

	/* bail if we did not receive a message */

    if (messageRc != MESSAGE_OK) {
	return (messageRc);
    }

	/* If we are the background connection, handle any remaining */
	/* RESP_EVENT, RESP_MESSAGE or RESP_MEETING messages -- this */
	/* is critical since we will not get a SIGIO alarm for any   */
	/* messages that arrived while we were receiving previous    */
	/* messages.  Note that we skip this part if we are in a     */
	/* multi-threaded environment since we do not use SIGIO in a */
	/* multi-threaded environment.                               */

#ifndef MULTIPLE_THREADS

    if (m_backgroundConnection) {

        int flags;
	int hiddenMessageRc;
        MESSAGE *otherResponse;

        while (!isMessagePending(flags) && (flags & SigioTrap::e_READ_READY)) {

	        /* receive the message */

	    if ((hiddenMessageRc = MessageDriver::unsecureReceive (otherResponse, *m_connection, set, stop)) != MESSAGE_OK) {
	        break;
	    }

	        /* fatal system bug if we did not get a RESP_MESSAGE, RESP_EVENT */
	        /* or RESP_MEETING message                                       */

	    UINT_8 flag = otherResponse -> getFlag ();

	    if ((flag != RESP_MEETING) && (flag != RESP_MESSAGE) && (flag != RESP_EVENT)) {
		printf ("message flag ==> %d\n", flag);
	        abort_with_message ("expected (but did not get) a RESP_MEETING, RESP_EVENT or RESP_MESSAGE message");
	    }

	        /* handle the message */
 
	    masks -> handleIncoming_noLock (otherResponse);
	    DELETE_OBJECT(otherResponse);
	    otherResponse = NULL;
	}
    }

#endif

	/* forget about the received message if we have an error */

    if (messageRc != MESSAGE_OK) {
	DELETE_OBJECT(receivedMessage);
	receivedMessage = NULL;
    }

    return (messageRc);
}

int AgentConnection::unsecureReceiveNoncomm (MESSAGE *&receivedMessage, const MESSAGE_SET& set, struct timeval *stop)
{
    int messageRc;

	/* lock, receive the message, unlock and return */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> acquireAll();
    }
#endif

    messageRc = unsecureReceiveNoncommNolock (receivedMessage, set, stop);

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> releaseAll();
    }
#endif

    return (messageRc);
}

MESSAGE *AgentConnection::unsecureReceiveAny (const MESSAGE_SET& set)
{
    int messageRc;
    MESSAGE *response = NULL;

	/* lock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> acquire ();
    }
#endif

	/* receive an incoming message */

    messageRc = MessageDriver::unsecureReceive (response, *m_connection, set);

	/* unlock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> release();
    }
#endif

	/* done */
	    
    return (response);
}

int AgentConnection::RPC (MESSAGE &message, const MESSAGE_SET& messageSet, MESSAGE*& response, struct timeval *stop)
{
    int code;
    int flags;

	/* assume that we will fail to receive the response */

    response = NULL;

	/* we can not have a timeout on the background connection  */
	/* since the background connection will become unusable if */
	/* we do not send the *complete* message and then receive  */
	/* the *complete* response                                 */

    if (m_backgroundConnection) {
	stop = NULL;
    }

	/* lock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> acquireAll();
    }
#endif

	/* send message */

    if ((code = send (message, stop)) == MESSAGE_OK) {

	    /* if sent successfully, receive the response */

	code = unsecureReceiveNoncommNolock (response, messageSet, stop);

    } else {

	    /* if not sent successfully, check if there is a response and   */
	    /* then receive the response if present, since the server might */
	    /* have sent a response and closed the connection without even  */
	    /* receiving our request                                        */

	if (!isMessagePending(flags) && (flags & SigioTrap::e_READ_READY)) {
	    if (unsecureReceiveNoncommNolock (response, messageSet, stop) == MESSAGE_OK) {
		code = MESSAGE_OK;
	    }
	}
    }

	/* unlock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> releaseAll();
    }
#endif

    return (code);
}

int AgentConnection::unsecureRPC (MESSAGE &message, const MESSAGE_SET &messageSet, MESSAGE*& response)
{
    int code;
    int flags;

	/* assume that we will fail to receive the response */

    response = NULL;

	/* lock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> acquireAll();
    }
#endif

	/* send message */

    if ((code = unsecureSend (message)) == MESSAGE_OK) {

	    /* if sent successfully, receive the response */

	code = unsecureReceiveNoncommNolock (response, messageSet);

    } else {

	    /* if not sent successfully, check if there is a response and   */
	    /* then receive the response if present, since the server might */
	    /* have sent a response and closed the connection without       */
	    /* even receiving our request                                   */

	if (!isMessagePending(flags) && (flags & SigioTrap::e_READ_READY)) {
	    if (unsecureReceiveNoncommNolock (response, messageSet) == MESSAGE_OK) {
		code = MESSAGE_OK;
	    }
	}
    }

	/* unlock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_locks -> releaseAll();
    }
#endif

    return (code);
}

int AgentConnection::unsecureSendFromBuffer (DynamicBuffer& buffer)
{
    int code;

	/* lock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> acquire();
    }
#endif

	/* do the send */

    code = MessageDriver::sendFromBuffer (*m_connection, buffer);

	/* unlock if necessary */

#ifdef MULTIPLE_THREADS
    if (m_backgroundConnection) {
	m_lock -> release();
    }
#endif

    return (code);
}

void AgentConnection::connectionAlarm (Connection &connection, Connection::ConnectionStatus status)
{
	/* NOTE: we don't need to acquire the lock since it was */
	/* acquired by the lower-level routines before making   */
	/* the callback				                */

	/* do nothing unless this is a read */

    if (status != Connection::e_READ_READY) {
	return;
    }

	/* make our callback */

    assert (m_trap != NULL);
    m_trap -> connectionAlarm (connection, status);
}
