/*****************************************************************************
*   Sockets routines to handle socket io of objects.			     *
******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                *
******************************************************************************
* Written by:  Gershon Elber				Ver 0.1, June 1993.  *
*****************************************************************************/

#include <stdio.h>
#include <sys/types.h>

#ifdef __UNIX__
#if (defined(ultrix) && defined(mips)) || defined(_AIX) || defined(sgi)
#    include <fcntl.h>
#else
#    include <sys/fcntl.h>
#endif /* (ultrix && mips) || _AIX || sgi */
#include <sys/socket.h>
#if defined(__hpux) || defined(SUN4)
#    include <sys/file.h>
#endif /* __hpux || SUN4 */
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#endif /* __UNIX__ */

#ifdef OS2GCC
#    include <os2.h>
#endif /* OS2GCC */

#ifdef __WINNT__
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <io.h>
#endif /* __WINNT__ */

#ifdef AMIGA
#include <string.h>
#include <exec/types.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <proto/dos.h>
#include <proto/exec.h>
#endif /* AMIGA */

#include "irit_sm.h"
#include "prsr_loc.h"
#include "irit_soc.h"

/* #define DEBUG_ECHO */

static void SocketPrintError(char *Str);
static void SocUnReadChar(int Handler, char c);
static int SocReadObjPrefix(int Handler);

/*****************************************************************************
* DESCRIPTION:                                                               *
* IO error print routine.						     *
*                                                                            *
* PARAMETERS:                                                                *
*   Str:      To output to stderr as a message.                              *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void SocketPrintError(char *Str)
{
#   if defined(__UNIX__) || defined(OS2GCC) || defined(AMIGA)
	perror(Str);
#   endif /* __UNIX__ || OS2GCC */
#   ifdef __WINNT__
	fprintf(stderr, "iritclient: %s error %d\n", Str, WSAGetLastError());
#   endif /* __WINNT__ */
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*  Sets echo printing of read input.                                         M
*                                                                            *
* PARAMETERS:                                                                M
*   Handler: The socket info handler index.                                  M
*   EchoInput:   TRUE to echo every character read in.                       M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocEchoInput, ipc                                                        M
*****************************************************************************/
void SocEchoInput(int Handler, int EchoInput)
{
    _IPStream[Handler].EchoInput = EchoInput;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Non blocking read of a single character.				     M
*   Returns EOF if no data is found.					     M
*                                                                            *
* PARAMETERS:                                                                M
*   Handler: The socket info handler index.                                  M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:        Read character or EOF if none found.                         M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocReadCharNonBlock, ipc                                                 M
*****************************************************************************/
int SocReadCharNonBlock(int Handler)
{
    int c;
#ifdef AMIGA
    struct IritMessage *msg;
#endif /* AMIGA */

    if (_IPStream[Handler].UnGetChar >= 0) {
	c = _IPStream[Handler].UnGetChar;
	_IPStream[Handler].UnGetChar = -1;
	return c;
    }
    else if (_IPStream[Handler].BufferPtr < _IPStream[Handler].BufferSize) {
	c = _IPStream[Handler].Buffer[_IPStream[Handler].BufferPtr++];
	return c;
    }

#ifdef OS2GCC
    DosRead(_IPStream[Handler].pipIrit, _IPStream[Handler].Buffer,
	    LINE_LEN_LONG, (PULONG) &_IPStream[Handler].BufferSize);
#endif /* OS2GCC */

#ifdef __WINNT__
    if (_IPStream[Handler].MasterSoc < 0) {
	/* Its a named pipe. */
	ReadFile(_IPStream[Handler].pHandle, _IPStream[Handler].Buffer,
		 LINE_LEN_LONG, &_IPStream[Handler].BufferSize, NULL);
    }
    else /* Its a socket. */
	_IPStream[Handler].BufferSize = recv(_IPStream[Handler].CommuSoc,
					     _IPStream[Handler].Buffer,
					     LINE_LEN_LONG, 0);
#endif /* __WINNT__ */

#ifdef __UNIX__
    _IPStream[Handler].BufferSize = recv(_IPStream[Handler].CommuSoc,
					 _IPStream[Handler].Buffer,
					 LINE_LEN_LONG, 0);
#endif /* __UNIX__ */

#ifdef AMIGA
    msg = (struct IritMessage *) GetMsg(_IPStream[Handler].CommuSoc);
    if (msg) {
        _IPStream[Handler].BufferSize = msg->nbytes;
	memcpy(_IPStream[Handler].Buffer, msg->txt, msg->nbytes);
	ReplyMsg((struct Message *)msg);
    } else {
        _IPStream[Handler].BufferSize = 0;
    }
#endif /* AMIGA */

    if (_IPStream[Handler].BufferSize > 0) {
	if (_IPStream[Handler].EchoInput) {
	    int i;
	    unsigned char
		*p = _IPStream[Handler].Buffer;

	    if (_IPStream[Handler].IsBinary) {
		for (i = 0; i < _IPStream[Handler].BufferSize; i++) {
		    if (i % 16 == 0)
			printf("\n%04x: ", i);
		    printf("%02x ", *p++);
		}
		printf("\n");
	    }
	    else
		for (i = 0; i < _IPStream[Handler].BufferSize; i++, p++)
		    putc(*p, stdout);
	}
	_IPStream[Handler].BufferPtr = 0;
	c = _IPStream[Handler].Buffer[_IPStream[Handler].BufferPtr++];
    }
    else
	c = EOF;

    return c;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Unget one chararacter read from client port.                               *
*                                                                            *
* PARAMETERS:                                                                *
*   Handler:  The socket info handler index.                                 *
*   c:        Character to unget.                                            *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*                                                                            *
* KEYWORDS:                                                                  *
*   SocUnReadChar, ipc                                                       *
*****************************************************************************/
static void SocUnReadChar(int Handler, char c)
{
    _IPStream[Handler].UnGetChar = c;
}

/*****************************************************************************
* DESCRIPTION:                                                               *
* Gets a single line for syncronization purposes that will prefix an object. *
*   Returns TRUE if prefix found, FALSE otherwise.                           *
*                                                                            *
* PARAMETERS:                                                                *
*   Handler: The socket info handler index.                                  *
*                                                                            *
* RETURN VALUE:                                                              *
*   int:        TRUE if prefic of object was found, FALSE otherwise          *
*****************************************************************************/
static int SocReadObjPrefix(int Handler)
{
    if (_IPStream[Handler].IsBinary) {
	int c;

	if ((c = SocReadCharNonBlock(Handler)) != EOF) {
	    SocUnReadChar(Handler, (char) c);
	    return TRUE;
	}
    }
    else {
	if (SocReadCharNonBlock(Handler) == '[') {
	    SocUnReadChar(Handler, '[');
	    return TRUE;
	}
    }

    return FALSE;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Non blocking read of a single line. Returns NULL if no line is available.  M
*                                                                            *
* PARAMETERS:                                                                M
*   Handler: The socket info handler index.                                  M
*                                                                            *
* RETURN VALUE:                                                              M
*   char *:     Read line, or NULL if unavailable.                           M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocReadLineNonBlock, ipc                                                 M
*****************************************************************************/
char *SocReadLineNonBlock(int Handler)
{
    static char Line[LINE_LEN_LONG];
    static int
	LineLen = 0;

    while (TRUE) {
	int c;

	if ((c = SocReadCharNonBlock(Handler)) != EOF) {
	    if (c == '\n' || c == '\r') {
		Line[LineLen++] = c;
		Line[LineLen] = 0;

		LineLen = 0;
		return Line;
	    }
	    else if (LineLen >= LINE_LEN_LONG - 1) {
		IritPrsrFatalError("Socket read line too long\n");
		exit(1);
	    }
	    else {
		Line[LineLen++] = c;
	    }
	}
	else
	    return NULL;
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*  Opens the client's socket. Returns handler if succesful.                  M
*                                                                            *
* PARAMETERS:                                                                M
*   BinaryIPC:	 Do we want to communicate text or binary?                   M
*   Read:	 Is this socket for read (TRUE) or write (FALSE).            M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:         Non negative handler if successful, -1 otherwise.           M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocClientCreateSocket, ipc                                               M
*****************************************************************************/
int SocClientCreateSocket(int BinaryIPC, int Read)
{
    int Handler;
#ifdef AMIGA
    int len;
    char *name, buf[4];
    static int timescalled = 0;
#endif /* AMIGA */
#if defined(__UNIX__) || defined(__WINNT__)
    int s;
    char *HostName, *PortNum, HostNameStr[LINE_LEN];
    struct sockaddr_in Sain;
    struct hostent *Host;
#endif /* __UNIX__ || __WINNT__ */
#ifdef OS2GCC
    char *Port;
    ULONG rc,
	ulActionTaken, ulFileSize, ulFileAttribute, ulOpenFlag, ulOpenMode;
#endif /* OS2GCC */

    /* Allocate handler. */
    Handler = IritPrsrOpenStreamFromSocket(0, TRUE, BinaryIPC);

#if defined(__UNIX__) || defined(__WINNT__)
#ifdef __WINNT__
    if ((PortNum = getenv("IRIT_SERVER_PORT")) != NULL &&
	strncmp(PortNum, "\\\\.\\pipe\\irit", 12) == 0) {
	HANDLE pHandle;

	/* We have a named pipe connection. */
	IritSleep(100);
	WaitNamedPipe(PortNum, NMPWAIT_USE_DEFAULT_WAIT);

	if ((pHandle = CreateFile(PortNum, GENERIC_READ | GENERIC_WRITE, 0,
				  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
				  NULL)) == INVALID_HANDLE_VALUE) {
	    Beep(1000, 100);
	    exit(1);
	}

        _IPStream[Handler].CommuSoc = 1;
	_IPStream[Handler].MasterSoc = -1;
	_IPStream[Handler].pHandle = pHandle;
	_IPStream[Handler].IsBinary = BinaryIPC;

	return Handler;
    }
    else {
	WSADATA WSAData;

	_IPStream[Handler].MasterSoc = 1;

	if ((s = WSAStartup(MAKEWORD(1, 1), &WSAData)) != 0 ||
	    LOBYTE(WSAData.wVersion) != 1 ||
	    HIBYTE(WSAData.wVersion) != 1) {
	    fprintf(stderr, "iritclient: WSAStartup: error %d\n",
		    WSAGetLastError());
	    exit(1);
	}
    }
#endif

    /* Get port address. */
    gethostname(HostNameStr, LINE_LEN);
    if ((HostName = getenv("IRIT_SERVER_HOST")) == NULL)
	HostName = HostNameStr;

    ZAP_MEM(&Sain, sizeof(struct sockaddr_in));

#ifdef __WINNT__
    if ((Host = gethostbyname(HostName)) == NULL) {
        unsigned char
	    *Addr = (unsigned char *) &Sain.sin_addr;

        Addr[0] = 127;	           /* 127.0.0.1 is a local machine on WIN95. */
        Addr[1] = 0;
        Addr[2] = 0;
        Addr[3] = 1;
    }
    else
        GEN_COPY(&Sain.sin_addr, Host -> h_addr_list[0], Host -> h_length);
#else
    if ((Host = gethostbyname(HostName)) == NULL) {
	SocketPrintError("iritclient: hostname unknown\n");
	return -1;
    }
    GEN_COPY(&Sain.sin_addr, Host -> h_addr_list[0], Host -> h_length);
#endif /* __WINNT__ */

    Sain.sin_family = AF_INET;

    if ((PortNum = getenv("IRIT_SERVER_PORT")) != NULL) {
#if defined(__WINNT__)
	Sain.sin_port = htons((unsigned short) atoi(PortNum));
#else
	Sain.sin_port = atoi(PortNum);
#endif /* __WINNT__ */
    }
    else {
	SocketPrintError("iritclient: hostport unknown (set IRIT_SERVER_PORT)\n");
	return -1;
    }

    /* Create the socket, make it nonblocking if Read and connect. */
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	SocketPrintError("iritclient: socket");
	return -1;
    }

    if (Read) {
#ifdef __UNIX__
#if defined(__hpux)
        if (fcntl(s, F_SETFL, O_NDELAY) < 0) {
#else
        if (fcntl(s, F_SETFL, FNDELAY) < 0) {
#endif /* __hpux */
	    SocketPrintError("iritclient: fcntl");
	    return -1;
        }
#endif /* __UNIX__ */
    }

    while (connect(s, (struct sockaddr *) &Sain,
		   sizeof(struct sockaddr_in)) < 0) {
#	ifdef __UNIX__
	    if (errno == EINPROGRESS)
		break;
#	endif /* __UNIX__ */
#	ifdef __WINNT__
	    if (WSAGetLastError() == WSAEINPROGRESS)
		break;
#	endif /* __WINNT__ */
	SocketPrintError("iritclient: connect");
	return -1;
    }

    _IPStream[Handler].CommuSoc = s;
#endif /* __UNIX__ || __WINNT__ */

#ifdef OS2GCC
    if ((Port = getenv("IRIT_SERVER_PORT")) == NULL) {
	fprintf(stderr, "No IRIT_SERVER_PORT environment variable, \"\\pipe\\irit_1\" assumed.\n");
	Port = "\\pipe\\irit_1";
    }

    DosWaitNPipe(Port, NP_WAIT);
    ulFileSize = 0;
    ulFileAttribute = 0;
    ulOpenFlag = OPEN_ACTION_OPEN_IF_EXISTS;
    ulOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE;

    rc = DosOpen(Port,
		 &_IPStream[Handler].pipIrit,
		 &ulActionTaken,
		 ulFileSize,
		 ulFileAttribute,
		 ulOpenFlag,
		 ulOpenMode,
		 NULL);

    if (rc != 0) {
    	DosBeep(1000, 100);
    	exit(1);
    }

    _IPStream[Handler].CommuSoc = 1;
#endif /* OS2GCC */

#ifdef AMIGA
    timescalled++;
    if (timescalled % 2) {
	len = GetVar(SERVER_VAR, buf, sizeof(buf), GVF_GLOBAL_ONLY);
	if (len < 0) {
	    SocketPrintError
		("iritclient: cannot get synchronization environment variable");
	    return -1;
	}
	DeleteVar(SERVER_VAR, GVF_GLOBAL_ONLY);
    }

    name = getenv("IRIT_SERVER_PORT");
    if (!name) {
	SocketPrintError("iritclient: hostport unknown (set IRIT_SERVER_PORT)\n");
	return -1;
    }
    Forbid();
    _IPStream[Handler].CommuSoc = FindPort(name);
    Permit();
    if (!_IPStream[Handler].CommuSoc) {
	SocketPrintError("iritclient: FindPort");
	return -1;
    }

    _IPStream[Handler].IsBinary = BinaryIPC;
    buf[0] = '\0';
    if (timescalled % 2) {
	SetVar(CLIENT_VAR, buf, -1, GVF_GLOBAL_ONLY);
	/* This beast busy-waits! Reduce its priority so that we don't hog
	   other tasks, namely irit. */
    }
    _IPStream[Handler].oldpri = SetTaskPri(FindTask(0L), -1);
#endif /* AMIGA */

    _IPStream[Handler].IsBinary = BinaryIPC;

    return Handler;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*  Close the client's socket.                                                M
*                                                                            *
* PARAMETERS:                                                                M
*   Handler: The socket info handler index.                                  *
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocClientCloseSocket, ipc                                                M
*****************************************************************************/
void SocClientCloseSocket(int Handler)
{
#ifndef AMIGA
    if (_IPStream[Handler].CommuSoc >= 0) {
#else
    if (_IPStream[Handler].CommuSoc != 0) {
#endif

#ifdef __UNIX__
	if (close(_IPStream[Handler].CommuSoc) != 0)
	    SocketPrintError("iritclient: close");
#endif /* __UNIX__ */

#ifdef __WINNT__
	if (_IPStream[Handler].MasterSoc < 0)
	    DisconnectNamedPipe(_IPStream[Handler].pHandle);
	else
	    closesocket(_IPStream[Handler].CommuSoc);
#endif /* __WINNT__ */

#ifdef OS2GCC
	DosClose(_IPStream[Handler].pipIrit);
#endif /* OS2GCC */

#ifdef AMIGA
	SetTaskPri(FindTask(0L), _IPStream[Handler].oldpri);
#endif /* AMIGA */

	_IPStream[Handler].CommuSoc = -1;
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Attempts to read (non blocking) an object from socket.		     M
* If read is successful the object is returned, otherwise NULL is returned.  M
*                                                                            *
* PARAMETERS:                                                                M
*   Handler: The socket info handler index.                                  *
*                                                                            *
* RETURN VALUE:                                                              M
*   IPObjectStruct *:  An object if read one, NULL otherwise.                M
*                                                                            *
* KEYWORDS:                                                                  M
*   SocReadOneObject, ipc                                                    M
*****************************************************************************/
IPObjectStruct *SocReadOneObject(int Handler)
{
    char *ErrorMsg;
    IPObjectStruct *PObj;

#ifndef AMIGA
    if (_IPStream[Handler].CommuSoc >= 0 && SocReadObjPrefix(Handler)) {
#else
    if (_IPStream[Handler].CommuSoc != 0 && SocReadObjPrefix(Handler)) {
#endif
	IritPrsrSetReadOneObject(TRUE);

	if (_IPStream[Handler].IsBinary) {
	    PObj = IritPrsrGetBinObject(Handler);
	}
	else {
	    PObj = IritPrsrGetObjects(Handler);
	}
    }
    else
	PObj = NULL;

    if (IritPrsrParseError(_IPStream[Handler].LineNum, &ErrorMsg)) {
	fprintf(stderr, "Socket: %s\n", ErrorMsg);
    }

    return PObj;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Accept a connection. Must be called by clients at the beginning.         M
*                                                                            *
* PARAMETERS:                                                                M
*   PrgmInput:  A handler to the Program's input channel.		     M
*   PrgmOutput: A handler to the Program's output channel.		     M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:        TRUE, if succesful, FALSE otherwise.                         M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritPrsrClntAcceptConnect                                                M
*****************************************************************************/
int IritPrsrClntAcceptConnect(int *PrgmInput, int *PrgmOutput)
{
    static char Line[LINE_LEN];
    int i, Input, Output,
	IsBinary = getenv("IRIT_BIN_IPC") != NULL;
    IPObjectStruct
	*PObj = NULL;

    if ((Input = SocClientCreateSocket(IsBinary, TRUE)) >= 0) {
	for (i = 0; i < SOC_TIME_OUT; i++) {
	    if ((PObj = SocReadOneObject(Input)) != NULL) {
		if (!IP_IS_STR_OBJ(PObj) ||
		    strncmp(PObj -> U.Str, "IRIT_SERVER_PORT=", 17) != 0) {
		    IPFreeObject(PObj);
		    return FALSE;
		}
		strcpy(Line, PObj -> U.Str);
		IPFreeObject(PObj);
		break;
	    }
	    IritSleep(10);
	}
	if (i >= SOC_TIME_OUT)
	    return FALSE;
    }
    else
	return FALSE;

    putenv(Line);

    if ((Output = SocClientCreateSocket(IsBinary, FALSE)) < 0)
	return FALSE;

    *PrgmInput = Input;
    *PrgmOutput = Output;

    return TRUE;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Close a connection. Must be called by clients at the end.                M
*                                                                            *
* PARAMETERS:                                                                M
*   PrgmInput:  A handler to the Program's input channel.		     M
*   PrgmOutput: A handler to the Program's output channel.		     M
*                                                                            *
* RETURN VALUE:                                                              M
*   int:        TRUE, if succesful, FALSE otherwise.                         M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritPrsrClntCloseConnect                                                 M
*****************************************************************************/
int IritPrsrClntCloseConnect(int PrgmInput, int PrgmOutput)
{
    SocClientCloseSocket(PrgmInput);
    SocClientCloseSocket(PrgmOutput);

    return TRUE;
}
