/*      Communications Interface code for Remote Procedure Call             ts.c
**      =======================================================
**
**	This code was originally translated from the pascal using the 'ptc'
**	program. It is now maintained separately.
**
**	See also the RPC internals manual, which describes how modules
**	similar to this one work, also giving flow charts and message formats.
**
**  History:
**	30 Apr 90   Transport medium objects separated from this module.
**		    See CMS copy of previous version for all previous history.
**
**  Compilation options:
**
**	ETHERNET	Include code for communication using raw ethernet
**			packets.
**
**	TCP		Include code for handling TCP/IP using the Berkley
**			socket interface.
**
**	TS		Always set. This selects visibility of TS layer
**			interface, and is necessary for TS_INTERNALS:
**
**	TS_INTERNALS	Always set. This selects visibility of TS layer
**			structure socket is its size must be known for malloc!.
**
**      AST             The operating systems supports asynchonous procedures
**                      This is not essential, but provides useful added
**                      functionality.
*/

/*	Module parameters:
**	-----------------
**
**  These may be undefined and redefined by syspec.h
*/
#define LISTEN_BACKLOG 2	/* Number of pending connect requests (TCP)*/

#define INET_VMS_ERROR	0x08558002  /* Add to (errno<<3) to make VMS error */

#define WILDCARD '*'	    /* Wildcard used in addressing */

#define NETCLOSE close	    /* Routine to close a TCP-IP socket		*/
#define NETREAD  read	    /* Routine to read from a TCP-IP socket	*/
#define NETWRITE write	    /* Routine to write to a TCP-IP socket	*/

# define FIRST_TCP_PORT 5000	/* First port number to try to bind to  */
# define LAST_TCP_PORT  5999	/* Last one to try before giving up     */

#include "syspec.h"


/*	Standard Include files:
**
*/

#include <stdio.h>

#define TS		/* Get visibility of TS interface */
#define TS_INTERNALS	/* Get visibility of TS internals for socket size! */
#include "rpcrts.h"	/* Complete TS layer visibility - includes rpc_ts.h */

#include "rpc_code.h"	/* Coding convention macros */

/*      Definitions for case-statements
*/
# define Line __LINE__
PRIVATE void    Caseerror();

/*
**      Definitions for pointers
*/
#define NIL_MESSAGE (rpc_message *)0
extern char *malloc();
extern void free();
extern char *strncpy();

 

/*	Genuine module-global variables:
 */

    rpc_protocol * rpc_protocols;   /* List of protocols */
								    
/*    socket_type	 valid_sockets;	 List of allocated sockets */
 



/*                      External Routines
**			-----------------
*/
 
extern void rpc_new();                  /* Allocate rpc_message */
extern void rpc_dispose();              /* Deallocate rpc_message */
extern void rpc_get_string();		/* Get Pascal or C string parameter */
 
#ifdef AST				/*  !!!!!! */
extern void sys_setast();		/* En/Disable ASTs */
rpc_status ts_when_receive();           /* forward (ASTs only) */
#endif


/*			UTILITY ROUTINES
**
*/

/*	Dump contents of message to screen
**	----------------------------------
*/

 void
hex_message(p_message, len)
   rpc_message_pointer  p_message;
   int			len;
{
   register int   j;
   register int   i;
   char   ch;

   if (len > RPC_BUFFER_SIZE)
      UTRACE(tfp, " Illegal length of %1d\n", len);
   else
      if (len == 0)
         UTRACE(tfp, "(no data)\n");
      else {
         UTRACE(tfp, " length%5d, contents:\n", len);
         switch ((p_message->body.which)) {
           case CALL_MESSAGE:
            UTRACE(tfp, "  CALL: TID   Package_No. Vers. Proc# Parameters...\n");
            break ;
           case REJECT_MESSAGE:
            UTRACE(tfp, "REJECT: TID   Why?  Status_Code Details...\n");
            break ;
           case RETURN_MESSAGE:
            UTRACE(tfp, "RETURN: TID   Returned_Parameters...\n");
            break ;
           case ABORT_MESSAGE:
            UTRACE(tfp, " ABORT: TID   Code  ...\n");
            break ;
           default:
            UTRACE(tfp, 
            "  Type  (*** Unidentified message type ***)\n");
         }
         UTRACE(tfp, " ");

         for (i=0; i<=(len-1) ; i++) {

	    if (i==128) {		    /* Truncate long messages */
		UTRACE(tfp, "... etc.\n");
		break;
	    }
	    UTRACE(tfp, " %02x", p_message->body.rpc_b[i]);

            if ((i % 16 == 15) || (i == len - 1)) {	/* end of line? */
		for (j = i % 16; j<=14; j++) UTRACE(tfp, "   ");
                UTRACE(tfp, "     ");

		for (j = (16*(i/16));j <= i; j++) {
		    ch = p_message->body.rpc_b[j];
		    if ((ch < ' ') || (ch > '~'))
			ch = '.';
		    UTRACE(tfp, "%c", ch);
		}
                UTRACE(tfp, "\n");
                if (i < len - 1) UTRACE(tfp, " ");
            } /*end if end of line*/

        } /*end for*/
    }
}

/*	Check valid socket descriptor				  check_socket()
**	-----------------------------
**
**  This implementation is trivial at the moment.
**
**  On entry,
**
**	socket	is a socket pointer passed to the TS.
**
**  On exit,
**
**	returns	status indicating, if BAD, that the socket is invalid
**		and should not be used by the TS.
*/
rpc_status check_socket(socket)
   socket_type   socket;
{
   if (socket == NULL)
      return RPC_S_NOSUCHSOCKET;
   else
      return RPC_S_NORMAL;
}

/*	Parse a cardinal value				        parse_cardinal()
**	----------------------
**
** On entry,
**	*pp	    points to first character to be interpreted, terminated by
**		    non 0:9 character.
**	*pstatus    points to status already valid
**	maxvalue    gives the largest allowable value.
**
** On exit,
**	*pp	    points to first unread character
**	*pstatus    points to status updated iff bad
*/
unsigned int parse_cardinal(pstatus, pp, max_value)
   rpc_status		*pstatus;
   char			**pp;
   unsigned int		max_value;
{
    rpc_long   n;
    if ( (**pp<'0') || (**pp>'9')) {	    /* Null string is error */
	*pstatus = RPC_S_SYNTAX_4;  /* No number where one expeceted */
	return 0;
    }

    n=0;
    while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';

    if (n>max_value) {
	*pstatus = RPC_S_SYNTAX_4;  /* Cardinal outside range */
	return 0;
    }

    return n;
}


/*****************************************************************************
**
**      Procedure:      Send a message over the given socket
**
** On entry,
**      socket          gives the socket over which to communicate
**      pmessage        points to a pointer to the message, with fields:
**	    m_next	    (undefined)
**	    m_socket	    valid
**	    m_index	    Length of message = bufsize
**	    m_status	    (undefined)
**      bufsize         gives the number of data bytes to be transferred,
**                      excluding any bytes needed for the protocol.
** On exit,
**      The pointer to the message may have been changed. In this case,
**      it will point to a message with the m_socket and m_status fields
**      the same, but without the data.  ts_send is therefore DESTRUCTIVE.
*/
#ifdef __STDC__
rpc_status ts_send(
    socket_type		socket,	    /*	    IN	Socket			*/
    rpc_message_pointer *ppmessage, /*	    IN	Ptr to ptr to message	*/
    int			bufsize)    /*	    IN	Number of bytes to send */
#else
rpc_status ts_send(socket, ppmessage, bufsize)
    socket_type		socket;
    rpc_message_pointer *ppmessage;
    int			bufsize;
#endif
{
    rpc_status		status;
 
    status = check_socket( socket );
    if (GOOD(status)) {
	register struct socket_struct *soc = socket;

	return (*soc->soc_protocol->write)(ppmessage);
    } else return status;
} 
 
/***************************************************************************
 *
 *      Procedure:      OPEN TRANSPORT CONNECTION
 *                      -------------------------
 *
 * On entry,
 *      The service parameter gives details of the notional connection to be
 *      set up, as a string, in a format defined in the RPC user manual.
 *      This defines the medium to be used (after the '.'), and the network
 *      address or subaddress etc within that medium.
 * On exit,
 *      The function value is the status (error or normal) of the operation.
 *      If successful (function value odd), the socket number returned may
 *              be used for future calls on the new notional connection.
 *
 * Non-reentrant, because of the use of global variables
 */
 
/*______________________________________________________________________

	Match given name against template string

    Exact match required, case insensitive
*/
static char upper_case(ch)
    char ch;
{
    if ((ch>='a')&&(ch<='z')) return (ch-'a'+'A');
    return ch;
}

/*		Match name against string			    name_match()
**		-------------------------
**
**  On entry,
**  
**    given   - a 40 character character array
**    start   - the first position in 'given' to compare
**    template- a zero-terminated string to be compared against.
**
**  On exit,
**	retuns TRUE iff the names match exactly except for upper/lower case.
**
*/
int name_match(given, template)
    char *	given;
    char *	template;
{
    register int i;

    for (i=0; template[i] && given[i]; i++)
	if (upper_case(given[i]) != upper_case(template[i])) return FALSE;

    return (BOOLEAN) (!template[i] && !given[i]); /* mismatch or diff. lengths */
}

/*	Register Transport Protocol				       rpc_use()
**	---------------------------
**
**  If the same name is registered twice, the last registered will be
**  found first. This allows users to override a default offering.
**
**  On entry,
**	protocol    points to a protocol structure which may or may not
**		    already be registered.
**
**  On exit,
**		    The protocol is on the list rpc_protocols.
*/
#ifdef __STDC__
rpc_status rpc_use(rpc_protocol * protocol)
#else
rpc_status rpc_use(protocol)
    rpc_protocol * protocol;
#endif
{
    rpc_protocol * scan;

/*  For safety, we first check that the protocol is not registered.
*/
    for (scan = rpc_protocols; scan; scan = scan->next);
	if (scan==protocol) return RPC_S_NORMAL;

/*  Then, we add it to the head of the queue of protocols.
*/

    CRITICAL
    protocol->next = rpc_protocols;
    rpc_protocols = protocol;
    NONCRITICAL

    return RPC_S_NORMAL;
}

/*	Unregister a protocol				             rpc_unuse()
**	---------------------
**
**  This is provided for completeness though it may never be used.
**  It unregisters a protocol if it had been registered.
**
**  On entry,
**	protocol    points to a protocol object
**
**  On exit,
**	protocol is not on the list of rpc_protocols.
*/
#ifdef __STDC__
rpc_status rpc_unuse(rpc_protocol * protocol)
#else
rpc_status rpc_unuse(protocol)
    rpc_protocol * protocol;
#endif
{
    rpc_protocol * scan;
    rpc_protocol * last;	/* Trails scan by one link */

    CRITICAL
    for (last=(rpc_protocol *)(&rpc_protocols),
	    scan=last->next; scan; last=scan, scan=last->next)
	if (scan==protocol) {
	    last->next = scan->next;	/* Remove from queue */
	    NONCRITICAL
	    return RPC_S_NORMAL;
	}
    NONCRITICAL
    return RPC_S_NORMAL;		/* Wasn't on it. */
}

/*	Open Transport Connection				    ts_open()
**	_________________________
**
** On entry,
**	service	    is a 40-character blank filled or zero-terminated string
**		    which gives the complete address, including medium name.
**
**	socket	    points to a location to receive the connection identifier.
**
** On exit,
**
**	returns	    a status code.
**
**	*socket	    If status is good, then *socket is a valid connection
**		    identifier to be used in future calls to this module.
**		    It should eventually be returned using ts_close().
**
**		    If status is bad, then *socket is undefined, and may not
**		    be used for future calls.
*/
#ifdef __STDC__
rpc_status ts_open(
        socket_type	*psocket,   /*	    socket returned		*/
        rpc_name        service)    /*	    communications address	*/
#else
rpc_status ts_open(psocket, service)
        socket_type	*psocket;
        rpc_name        service;
#endif

{
    rpc_status      status;	    /* To be returned */
    int	            length;	    /* Name length */
    int		    dotpos;	    /* Position of last "." in name */
    rpc_name	    my_name;	    /* Zero-terminated service name */
    rpc_name        ts_open_tsap;   /* Address without medium on it */

    register socket_type soc;
 
    rpc_get_string(my_name, service);	    /* Pick up string parameter */

    CTRACE(tfp, "RPC/TS: Opening `%s'\n", my_name);

/*  Allocate a new socket:
*/
    soc = (socket_type)malloc(sizeof(socket_descriptor));
    if (!(soc)) return RPC_S_OPERATING_SYSTEM;	/* No memory! */
    *psocket= soc;

/*  Clear common fields in socket:
*/
    soc->soc_last_call_sent = NULL;
    soc->soc_last_reply_sent = NULL;
    soc->soc_next_call_tid = 0;
 
/*  Find position of the dot in the service name, copy service
**  and medium names into separate strings:
*/
    length = strlen(my_name);
    for(dotpos = length;
	    (dotpos > 0) && (my_name[dotpos] != '.'); dotpos--) /*void*/;
    if (my_name[dotpos] != '.')
	    return RPC_S_SYNTAX_2;
		    /* No dot! */
    strncpy(ts_open_tsap, my_name, dotpos);	    /* TSAP */
    ts_open_tsap[dotpos]=0;			    /* Terminate it */
    CTRACE(tfp, "RPC/TS: TSAP = `%s'\n", ts_open_tsap);

/*  Find which medium name follows the last dot:
 */
    {	rpc_protocol * scan;
	for(scan=rpc_protocols; scan; scan=scan->next)
	    if (name_match(&my_name[dotpos+1], scan->name)) break;

	if (!scan) status=RPC_S_SYNTAX_3;
	else {
	    soc->soc_protocol = scan;
	    status = (*scan->open)(soc, ts_open_tsap); /* Open the channel */
	}
    }

    if (BAD(status)) free(soc);   /* Socket is not valid if BAD status */
    return status;
} /* ts_open */

/*      Return the address by which we are known to others
**	--------------------------------------------------
**
**  Note that this routine is callable by FORTRAN and Pascal, giving back
**  a 40-character blank-filled name.
**
**  Bug: returns buffer-overflow status if the name would fir exactly, because
**	it allows space also for its own working terminator.
*/

/*ARGSUSED*/
#ifdef __STDC__
rpc_status ts_my_address(
        socket_type     socket,	    /*	IN  Socket		    */
        rpc_name        addrstr)    /*	OUT address of this socket  */
#else
rpc_status ts_my_address(socket, addrstr)
        socket_type     socket;
        rpc_name        addrstr;
#endif
{
    rpc_status status;

    status = (*socket->soc_protocol->my_address)
				(socket, addrstr, RPC_NAME_LENGTH);
    if (BAD(status)) return status;	

    if ((strlen(addrstr)+strlen(socket->soc_protocol->name)+2) > RPC_NAME_LENGTH)
	return RPC_S_BUFFER_OVERFLOW;

    strcat(addrstr, ".");
    strcat(addrstr, socket->soc_protocol->name);

/*  Blank fill:
*/
    {	int i;
	for(i=strlen(addrstr); i<RPC_NAME_LENGTH; i++) addrstr[i]=' ';
    }
}

/*      Return the address of the partner		       ts_peer_address()
**	---------------------------------
**
**  On entry,
**	soc	A socket which should identify a unique connected partner.
**
**
**  Note that this routine is callable by FORTRAN and Pascal, giving back
**  a 40-character blank-filled name.
**
**  Bug: returns buffer_overflow status if the name would fir exactly, because
**	 it allows space also for its own working terminator.
*/

/*ARGSUSED*/
#ifdef __STDC__
rpc_status ts_peer_address(
        socket_type     socket,	    /*	IN  Socket		    */
        rpc_name        addrstr)    /*	OUT address of this socket  */
#else
rpc_status ts_peer_address(socket, addrstr)
        socket_type     socket;
        rpc_name        addrstr;
#endif
{
    rpc_status status;

    status = (*socket->soc_protocol->peer_address)
				(socket, addrstr, RPC_NAME_LENGTH);
    if (BAD(status)) return status;	

    if ((strlen(addrstr)+strlen(socket->soc_protocol->name)+2) > RPC_NAME_LENGTH)
	return RPC_S_BUFFER_OVERFLOW;

    strcat(addrstr, ".");
    strcat(addrstr, socket->soc_protocol->name);

/*  Blank fill:
*/
    {	int i;
	for(i=strlen(addrstr); i<RPC_NAME_LENGTH; i++) addrstr[i]=' ';
    }
}

/*		Close transport connection			      ts_close()
**		--------------------------
**
** This is the complementaty procedure to TS_open.  A status value is
** returned as a function result.
*/
#ifdef __STDC__
rpc_status ts_close(socket_type soc)
#else
rpc_status ts_close(soc)
        socket_type soc;
#endif
{
    rpc_status		status;
    rpc_message_pointer     m;

    CTRACE(tfp, "RPC/TS_close: Called.\n");

    status = check_socket( soc );
    if (BAD(status)) return status;

/*  Protocol-specific close:
*/
    status = (*soc->soc_protocol->close)(soc);

/*  Throw away any spare messages:
*/
    if (soc->soc_last_call_sent != NULL)
	    rpc_dispose(soc->soc_last_call_sent);
    while (soc->soc_last_reply_sent != NULL) {
	    m = soc->soc_last_reply_sent;
	    soc->soc_last_reply_sent = m->m_next;
	    rpc_dispose(m);
    }
    free(soc);	/* Return socket to heap */

    return status;
}

/*      Initialise Transport Service Module			       ts_init()
**	-----------------------------------
**
** This is a one-off initialisation procedure for the whole TS module.
**
**  On entry,
**	No prerequisites.
**
**  On exit,
**	returns	    a status indicating any unrecoverable errrors.	
*/

#ifdef __STDC__
rpc_status ts_init(void)
#else
rpc_status ts_init()
#endif
{

#ifdef ETHERNET
    (void) use_ethernet();
#endif
#ifdef V24
    (void) use_v24();
#endif
#ifdef TCP
    (void) use_tcp();
#endif
#ifdef CATS
    (void) use_cats();
#endif
#ifdef DECNET
    (void) use_decnet();
#endif

    CTRACE(tfp, "RPC/TS: Initialised.\n");

    return RPC_S_NORMAL;
}



/*	Generic Case Bound error routine			    Caseerror()
**	--------------------------------
**
**  On entry,
**	A fatal program error has been found, in that a value used in a
**	switch statement was not any of the expected values.
**
**  Does not return.
*/ 
PRIVATE void Caseerror(n)
        int     n;
{
     PRINT_ERROR(stderr,
	"RPC/TS/Internal error: Missing case limb: line %d\n", n);
     exit(RPC_S_TS_INTERNAL_ERROR);
}

#ifdef AST
/******************************************************************************
 *
 *		    Queue Handling Routines
 *
 * (some of these are not currently used)
 */
#ifdef NOTDEFINED
 void
queue_add_head(queue, item)
   socket_type   *queue;
   socket_type   item;
{
   sys_setast(0);
   item->soc_next = (*queue);
   (*queue) = item;
   sys_setast(1);
}
#endif
/*__________________________________________________
 */
 void
queue_add_tail(queue, item)
   socket_type   *queue;
   socket_type   item;
{
   socket_type   scan;

   sys_setast(0);
   item->soc_next = NULL;
   if ((*queue) == NULL)
      (*queue) = item;
   else {
      scan = (*queue);
      while (scan->soc_next != NULL)
         scan = scan->soc_next;
      scan->soc_next = item;
   }
   sys_setast(1);
}

#ifdef NOTDEFINED
 void
queue_remove(queue, item)
   socket_type   *queue;
   socket_type   item;
{
   socket_type   scan;

   sys_setast(0);
   if (((*queue) == item))
      (*queue) = item->soc_next;
   else {
      scan = (*queue);
      while (scan->soc_next != NULL) {
         if ((scan->soc_next == item))
            scan->soc_next = item->soc_next;
         scan = scan->soc_next;
      }
   }
   sys_setast(1);
}
#endif
#endif
