/*		CATS based Transport Service for RPC		       ts_cats.c
**		====================================
**
**  This module provides a transport service for the RPC system developed
**  by CERN/DD Online group in 1986-90.
**
**  This particular module uses the CATS calling sequence to access an ISO-style
**  transport provider.
**  See the RPC User Manual for details of address syntax.
**
**  See also the RPC internals manual, which describes how modules
**  similar to this one work, also giving flow charts and message formats.
**
**  Outstanding:
**
**    -	Use proper TS types as per cats.h
**    -	Async server for unsolicited callback in cats
**
**  History:
**		    Written for DECnet by R.Jones. tested.
**		    Adapted to CATS by I. Martinez, tested.
**		    Converted to C by TBL, not tested.
**	30 Apr 90   Code extracted from general ts.c module.
**		    C Code is UNTESTED at this stage, Pascal code worked.
**
**  Compilation switches:
**
**	AST	System must support asynchronous routines. Mandatory.
**	CATS	Picks up relevant include file structures. Mandatory.
*/

#define CATS
#define AST

/*	Module parameters:
**	-----------------
**
**  These may be undefined and redefined by syspec.h
*/
# define WILDCARD '*'	    /* Wildcard used in addressing */

#include <stdio.h>
#include "syspec.h"
#define TS			/*  Require visibility of TS interface */
#define TS_INTERNALS		/*  Require visibility of TS internals */
#include "rpcrts.h"		/*  Define all general RPC data structures   */
#include "rpc_code.h"		/* Coding convention macros */

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


/*                      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 */

extern void cats_register_errors();
 
#ifdef AST				/* Always used by CATS !!!!!! */
extern void sys_setast();		/* En/Disable ASTs */
rpc_status ts_when_receive();           /* forward (ASTs only) */
#endif

/*	Module-global variables
**	-----------------------
*/
    int    	         always_zero;
    BOOLEAN	         dummy_BOOLEAN;
    rpc_long	 global_efn;


 void
panic()
{
	perror(" Please report internal error in TSCATS module.\n");
	abort();
}

 void
new_socket(s)
	socket_type	*s;
{
	(*s) = (struct socket_struct *)malloc((unsigned)(sizeof(*(*s))));
}

/******************************************************************************
 *
 *		Routines to process Return Codes
 */
 void
check_error(er_code, status, explanation)
	rpc_status 	er_code;
	rpc_status	*status;
	char		*explanation;
{
    *status = er_code;
    if (!(BOOLEAN)(*status & 1)) {
	CTRACE(tfp, 
	   "RPC/TS/CATS: Error %1d (dec) occured in multiclient handling: %s\n",
		er_code, explanation);

    }
}

/*	    Check CATS return Code
**	    ======================
**
**    This one checks against an expected status.  It writes the return
**  code into a status variable too.
*/
 PRIVATE BOOLEAN
cats_error(error_code, status, expected, expected_text)
	rpc_long	error_code;
	rpc_status	*status;
	rpc_status	expected;
	char		*expected_text;
{
    char* errmess;

    *status = error_code;
    if (rpc_trace) {
	switch ((int)*status) {
	  case 138642209:
		errmess = "Good return: normal: success (TS_OK)";break ;
	  case 138643027:
		errmess = "Connect request indication";		break ;
	  case 138643011:
		errmess = "Connect confirm indication";		break ;
	  case 138643067:
		errmess = "Connection response completed";	break ;
	  case 138644610:
		errmess = "Disconnect indication";		break ;
	  case 138643019:
		errmess = "Disconnect confirm";			break ;
	  case 138643043:
		errmess = "Data received indication";		break ;
	  case 138643035:
		errmess = "Data send completed";		break ;
	  case 138643059:
		errmess = "Expedited data indication";		break ;
	  case 138643051:
		errmess = "Expedited data send completed";	break ;
	  case 138643808:
		errmess = "Data overrun. User buffer too small";break ;
	  case 138644634:
		errmess = "Bad connection identifier";		break;
	  case 138644618:
		errmess = "Procedure called out of sequence";	break ;
	  case 138645412:
		errmess = "Failed to allocate virtual memory";	break ;
	  default:
		errmess = NULL;
	    }

	sys_setast(0);
	if (errmess)
		UTRACE(tfp, "RPC/TS/CATS Error: %s, when\n  \"%s\" expected.\n",
			     errmess, expected_text);
	else
		UTRACE(tfp, "RPC/TS/CATS: Non CATS error %lx hex (%ld decimal)\n",
			*status, *status);
	sys_setast(1);
    }
    return (*status != expected);
}

/*			    Socket handling
**			    ===============
*/
#ifdef SUPRESSED
/*		Make Socket					   make_socket()
**		-----------
*/
 socket_type
make_socket(status)
	rpc_status	*status;
{
	socket_type	socket;

	*status = RPC_S_NORMAL;
	new_socket(&socket);
	if (valid_sockets == NULL) {
		new_socket(&valid_sockets);
		valid_sockets->soc_next = NULL;
	}
	socket->soc_next = valid_sockets->soc_next;
	valid_sockets->soc_next = socket;
	return socket;
}
#endif
rpc_status close_connection();	/* forward */


/*		Delete Socket					 delete_socket()
**		-------------
*/
 PRIVATE rpc_status
delete_socket(socket)
	socket_type	socket;
{
    rpc_status	status;
    socket_type	next_soc;
    BOOLEAN	found;

    register struct socket_struct *soc = socket;

    if (rpc_trace) {
	sys_setast(0);
	UTRACE(tfp, "RPC/TS/CATS: delete_socket called for chan:\n");
	sys_setast(1);
    }

    if (soc->mdp.soc_cats.soc_master != NULL) {
	next_soc = soc->mdp.soc_cats.soc_master;
	found = FALSE;
	while ((next_soc != NULL) && (!found))
	    if (next_soc->mdp.soc_cats.soc_slaves == socket)
		found = TRUE;
	    else
		next_soc = next_soc->mdp.soc_cats.soc_slaves;
	if (found) {
	    next_soc->mdp.soc_cats.soc_slaves = socket->mdp.soc_cats.soc_slaves;
	    if (rpc_trace) {
		sys_setast(0);
		UTRACE(tfp, "RPC/TS/CATS: dispose of slave socket:\n");
		sys_setast(1);
	    }
	    if (soc->mdp.soc_cats.soc_message != NULL)
		    rpc_dispose(soc->mdp.soc_cats.soc_message);
	    free(socket);
	} else
		check_error(RPC_S_NOSUCHSOCKET, &status,
		"delete_socket call with bad owned socket");
    } else {
	    while ((soc->mdp.soc_cats.soc_slaves != NULL)
		    && (status == RPC_S_NORMAL))
			status = close_connection(soc->mdp.soc_cats.soc_slaves);

	    if (rpc_trace) {
		    sys_setast(0);
		    UTRACE(tfp, "RPC/TS/CATS: dispose of slave:%.40s\n",
			soc->mdp.soc_cats.soc_tsuf.body);
		    sys_setast(1);
	    }
	    if (soc->mdp.soc_cats.soc_message != NULL)
		    rpc_dispose(soc->mdp.soc_cats.soc_message);
	    free(socket);
    }
    return status;
}
 void
cats_read_chan(); /* forward */

/*		Search Channel List			      search_chan_list()
**		-------------------
 *
 *  This routine searches the list of attached slave sockets to find
 *  one with a message waiting. If it fibnds it, it swaps it with the
 *  'message' passed to it.
 *
 */
 BOOLEAN
search_chan_list(socket, message, status)
	socket_type	    *socket;
	rpc_message_pointer *message;
	rpc_status	    *status;
{
	BOOLEAN	found;
	socket_type	next_owned_chan;
	rpc_message_pointer	temp_ptr;

	*status = RPC_S_NORMAL;
	found = FALSE;
	next_owned_chan = (*socket)->mdp.soc_cats.soc_slaves;
	while ((next_owned_chan != NULL) && (!found))
		if (next_owned_chan->mdp.soc_cats.soc_holding_message)
		    found = TRUE;
		else
		    next_owned_chan = next_owned_chan->mdp.soc_cats.soc_slaves;
	if (found) {
		temp_ptr = (*message);
		(*message) = next_owned_chan->mdp.soc_cats.soc_message;
		next_owned_chan->mdp.soc_cats.soc_message = temp_ptr;
		next_owned_chan->mdp.soc_cats.soc_message->m_socket =
							    next_owned_chan;
		next_owned_chan->mdp.soc_cats.soc_holding_message = FALSE;
		cats_read_chan(&next_owned_chan, status);
	}
	return found;
}


/*		Display Event Flags			display_efn()
**
 *
 */
 PRIVATE void
display_efn(efn)
	rpc_long	efn;
{
    /* This was only put in as a trace option */
}

/*		Close a Socket				      close_connection()
**		--------------
**
**  On entry,
**	soc may be a master or slave socket.
**
**  On exit,
**	Any connection associated with the socket is disconnected.
**	Any event flag is released.
**
**	Why no recursion to slave sockets? - TBL
*/
#ifdef __STDC__
 PRIVATE rpc_status
close_connection(socket_type soc)
#else
 PRIVATE rpc_status
close_connection(soc)
	socket_type	soc;
#endif
{
    rpc_status	status;

    if (rpc_trace) {
	sys_setast(0);
	UTRACE(tfp, "RPC/TS/CATS: close_connection called for conn :%1d\n",
	    soc->mdp.soc_cats.soc_conn_id);
	UTRACE(tfp, "   Freeing EF :%1d\n", soc->soc_efn);
	sys_setast(1);
    }

    if (soc->mdp.soc_cats.soc_role == master)
	    (void)lib_free_ef(&soc->soc_efn);

    (void)cats_error(tsdsrw(&soc->mdp.soc_cats.soc_conn_id,
			    &soc->mdp.soc_cats.soc_systat),
		      &status,
		      soc->mdp.soc_cats.soc_connected ? ts_dscf : ts_dsid,
		      "disconnect confirm completion");

    soc->mdp.soc_cats.soc_connected = FALSE;

    return status;
}

/*******************************************************************************
 *
 *		Completion Routine for Asynchrounous Reads on slave socket
 */
 void
cats_read_completion(ci, st, event, le, sy, socket)
	rpc_long	*ci;
	rpc_long	*st;
	rpc_long	*event;
	TS_length	*le;
	TS_systat	*sy;
	socket_type	*socket;
{
    rpc_status	status;
    rpc_message_pointer	full_msg;

    {
	register struct socket_struct *soc = (*socket);

	if (rpc_trace) {
		UTRACE(tfp, 
		    "CATS message received on chan :%1d completion status :%1d\n", (*ci), (*st));
		hex_message(soc->mdp.soc_cats.soc_message, (*le));
	}
	if (((*st) == ts_dtaid) || (*st == ts_dtaovr)) {
/*	    soc->mdp.soc_cats.soc_async_read_queued = FALSE; */
	    if (soc->mdp.soc_cats.soc_master->
			mdp.soc_cats.soc_async_read_queued)
	    {
		register socket_type own = soc->mdp.soc_cats.soc_master;
		full_msg = soc->mdp.soc_cats.soc_message;
		soc->mdp.soc_cats.soc_message = soc->mdp.soc_cats.soc_master->mdp.soc_cats.soc_message;
		soc->mdp.soc_cats.soc_message->m_socket = (*socket);
		own->mdp.soc_cats.soc_message = NULL;
		own->mdp.soc_cats.soc_async_read_queued = no_read;

		/* Call user AST: */
		(*(soc->mdp.soc_cats.soc_master->soc_user_ast))(full_msg);

		cats_read_chan(socket, &status);    /* Requeue the read */

	    } else {
		soc->mdp.soc_cats.soc_holding_message = TRUE;
		soc->mdp.soc_cats.soc_master->mdp.soc_cats.soc_retlen = (*le);
		CTRACE(tfp, 
		     "set event flag %1d\n",
		     soc->mdp.soc_cats.soc_master->soc_efn);
		check_error(sys_setef(
			    soc->mdp.soc_cats.soc_master->soc_efn),
			    &status,
			    "from cats set event flag");
	    }
	} else
		if ((*st) == ts_dsid) {
			soc->mdp.soc_cats.soc_connected = FALSE;
			status = close_connection(soc);
			delete_socket(&status, *socket);
		} else
			if (rpc_trace) { UTRACE(tfp, 
"RPC/TS/CATS: Unexpected event on conn:%1d status:%1d length:%1d soc_systat[1]:%1d soc_systat[2]:%1d\n",
		      (*ci), (*st), (*le), (*sy)[0], (*sy)[1]);
				panic();
			    }
    }
}

/*******************************************************************************
 *
 *  Procedure:	    Queue an asynchronous read on a socket.
 *
 *  If there is no message, one is allocated.
 */
 void
cats_read_chan(socket, status)
	socket_type	*socket;
	rpc_status	*status;
{
    TS_length	buffer_length;

    {
	register struct socket_struct *soc = (*socket);

	if (soc->mdp.soc_cats.soc_holding_message)
		panic();
	else {
		if (soc->mdp.soc_cats.soc_message == NULL)
			rpc_new(&soc->mdp.soc_cats.soc_message, RPC_BUFFER_SIZE);
		soc->mdp.soc_cats.soc_message->m_socket = (*socket);
		if (rpc_trace) {
			sys_setast(0);
			UTRACE(tfp, 
			    "RPC/TS/CATS: queue read on chan:%1d\n",
			    soc->mdp.soc_cats.soc_conn_id);
			sys_setast(1);
		}

		buffer_length = RPC_BUFFER_SIZE;
		if (soc->mdp.soc_cats.soc_connected)
		    (void)cats_error(
			tsrcv(
			    &soc->mdp.soc_cats.soc_conn_id,
			    &soc->mdp.soc_cats.soc_message->body.rpc_b,
			    &buffer_length,
			    &soc->mdp.soc_cats.soc_systat,
			    cats_read_completion,
			    &soc->mdp.soc_cats.soc_self),
			status,
			ts_ok,
			" TSRCV initiation: read queued completed");
		else {
		    if (rpc_trace) {
			sys_setast(0);
			UTRACE(tfp, 
		  "RPC/TS/CATS: Error, channel is not active. Releasing ...\n");
			sys_setast(1);
		    }
		    status = close_connection(*socket);
		    delete_socket(status, *socket);
		}
	}
    }
}

/*******************************************************************************
 *
 *	Completion routine for TSCRS - Handle Connection Confimation
 */

 void
accept_ast(conn_id, status, event, len, systat, master_socket)
	rpc_long	*conn_id;
	rpc_long	*status;
	rpc_long	*event;
	TS_length	*len;
	TS_systat	*systat;
	socket_type	*master_socket;
{
	rpc_message_pointer	full_msg;
	socket_type	new_soc;
	rpc_status	my_status;

	if (*status & 1) {
		new_socket(&new_soc);
		{
			register struct socket_struct *soc = new_soc;

			*new_soc = **master_socket;
			soc->mdp.soc_cats.soc_master = (*master_socket);
			soc->mdp.soc_cats.soc_conn_id = (*conn_id);
			soc->mdp.soc_cats.soc_self = new_soc;
			soc->mdp.soc_cats.soc_role = slave;
			soc->soc_next = NULL;
			soc->soc_user_ast = 0;
			soc->soc_efn = 0;
			soc->mdp.soc_cats.soc_slaves = NULL;
			soc->mdp.soc_cats.soc_holding_message = FALSE;
			soc->mdp.soc_cats.soc_message = NULL;
			soc->mdp.soc_cats.soc_connected = FALSE;
			soc->mdp.soc_cats.soc_connected = TRUE;
			soc->mdp.soc_cats.soc_slaves = (*master_socket)->mdp.soc_cats.soc_slaves;
			(*master_socket)->mdp.soc_cats.soc_slaves = new_soc;
			if (rpc_trace) {
				UTRACE(tfp, "TSCATS: New cats connection:\n");
				UTRACE(tfp, 
				  "  soc_conn_id:   %1d\n", new_soc->mdp.soc_cats.soc_conn_id);
			}
			cats_read_chan(&new_soc, &my_status);
		}
	} else
		if (rpc_trace) {
			UTRACE(tfp, "TSCATS ACCEPT_AST: accept failed. \n");
			UTRACE(tfp, "  conn_id:   %1d\n", (*conn_id));
		}
}

/*******************************************************************************
 *
 *	    Completion routine for TSLST:   Handle an incoming connection
 */
 void
listen_ast(ci, st, event, length, systat, master_socket)
    TS_id		*ci;
    TS_status		*st;
    TS_status		*event;
    TS_length		*length;
    TS_systat		*systat;
    socket_type		*master_socket;
{
    rpc_status	status;

    (*master_socket)->mdp.soc_cats.soc_conn_id = *ci;
    CTRACE(tfp, "CATS- request on master conn: %1d connection status: %1d\n", 
	      (*master_socket)->mdp.soc_cats.soc_conn_id, (*st));
    if ((*event) == ts_cnid)
	    (void)cats_error(tscrs(&(*ci),
			    &(*master_socket)->mdp.soc_cats.soc_systat,
			    accept_ast, &(*master_socket)), 
			     &status, ts_cnrsok,
			    "connection response initiation");
}

/*******************************************************************************
 *
 *	LISTEN: Establish a TSAP and a connection request handler
 */
 void
listen_master(socket, status)
	socket_type	*socket;
	rpc_status	*status;
{
    register struct socket_struct *soc = (*socket);

    (void)cats_error(
	tslst(soc->mdp.soc_cats.soc_tsuf.body,
	    &soc->mdp.soc_cats.soc_tsuf.length,
	    &soc->mdp.soc_cats.soc_systat,
	    listen_ast, 
	    &soc->mdp.soc_cats.soc_self),
	status,
	ts_ok, 
	"Initiation of listen (tslst) on master socket");
}

/*******************************************************************************
 *
 *	Append C string to rpc_name
 */
 void
append_string_to_name(dest, source)
	rpc_name	dest;
	char *		source;
{
    register int	i, j;

    for(i=RPC_NAME_LENGTH; i && (dest[i-1]==' '); i--); /* Find last non-blank*/
    for (j=0; (i<RPC_NAME_LENGTH) && source[j]; i++)
		dest[i++]=source[j++];			/* Copy string */
}

/*******************************************************************************
 *
 *	Procedure:  Get address
 */
 void
rpc_cats_get_address(status, socket, addrstr)
	rpc_status	*status;
	socket_type	socket;
	rpc_name	addrstr;
{
	register int	i;

	strncopy(addrstr,socket->mdp.soc_cats.soc_tsuf.body, RPC_NAME_LENGTH);
	for (i = socket->mdp.soc_cats.soc_tsuf.length; i<RPC_NAME_LENGTH ; i++)
				addrstr[i] = ' ';   /* Blank fill */

	append_string_to_name(addrstr, medium_details[socket->soc_medium].name);

	*status = RPC_S_NORMAL;
}

 void
/*******************************************************************************
 *
 *	Send a message to partner task
 */
PRIVATE rpc_status cats_write(mes)
	rpc_message_pointer	mes;
/*******************************************************************************
 *
 *			Dummy completion routine
 */
 void
dummy_ast(ci, st, event, le, sy, socket)
	rpc_long	*ci;
	rpc_long	*st;
	rpc_long	*event;
	TS_length 	*le;
	TS_systat	*sy;
	socket_type	*socket;
{
}

/*******************************************************************************
 *
 *	Receive a message on a given socket
 */
 void
rpc_cats_get_message(status, socket, message, rx_size, timeout)
	rpc_status	    *status;
	socket_type	    *socket;
	rpc_message_pointer *message;
	TS_length	    rx_size;
	TS_timeout	    timeout;
{
    rpc_long	conn_xx;
    rpc_long	event_mask, system_mask;
    rpc_status	event;

    event_mask = 16;
    system_mask = 0;
    if (rpc_trace) {
	    sys_setast(0);
	    UTRACE(tfp, "Waiting for cats message...\n");
	    FLUSH_TRACE
	    sys_setast(1);
    }
    {
	register struct socket_struct *soc = (*socket);
	register rpc_message *mes = (*message);

	if (soc->mdp.soc_cats.soc_role != master) {
	    (void)cats_error(
		tsrcvw(&soc->mdp.soc_cats.soc_conn_id,
			&(*message)->body.rpc_b, &rx_size,
			&soc->mdp.soc_cats.soc_retlen, 
			&soc->mdp.soc_cats.soc_systat),
		status,
		ts_dtaid, 
		"Synchronous read TSrcvW completion");
	    if (soc->mdp.soc_cats.soc_role == transient)
		    status = close_connection(*socket);

	} else /* master */
	    if (soc->mdp.soc_cats.soc_async_read_queued)
		    check_error(RPC_S_ALREADY_READING,
				status,
				"cats read already queued");
	    else {
		check_error(sys_clref(soc->soc_efn), status,
			"cats clear event flag");
		if (!search_chan_list(socket, message, status)) {
		    if (rpc_trace) {
			sys_setast(0);
			UTRACE(tfp, 
		"Cleared, & now waiting on event flag %1d by server %.40s\n",
		soc->soc_efn, soc->mdp.soc_cats.soc_tsuf.body);
			sys_setast(1);
		    }
		    check_error(sys_waitfr(soc->soc_efn), status,
				"cats wait on event flag");
		    if (!search_chan_list(socket, message, status))
			if (rpc_trace) {
			    sys_setast(0);
				UTRACE(tfp, 
				"cats flag set but no msg received\n");
			    sys_setast(1);
			}
/*		    soc->mdp.soc_cats.soc_reading = no_read;   old */
		}
	    }
	if (rpc_trace) {
		sys_setast(0);
		UTRACE(tfp, "cats message received");
		hex_message((*message), soc->mdp.soc_cats.soc_retlen);
		sys_setast(1);
	}
    }
}

/*******************************************************************************
 *
 *	Completion routine for TSRCV:  Handle incoming message (simple socket)
 */
 void
rpc_cats_ast_service(ci, st, event, le, sy, socket)
	TS_id		*ci;
	TS_status	*st;
	TS_status	*event;
	TS_length	*le;
	TS_systat	*sy;
	socket_type	*socket;
{
	register rpc_message_pointer	pmessage;

	pmessage = (*socket)->mdp.soc_cats.soc_message;

	if (!(BOOLEAN)(((*st)) & 1))
	    CTRACE(tfp, "RPC/TS/CATS: Bad async I/O :%10d (dec)\n", (*st));
	else {
		if (rpc_trace) {
			UTRACE(tfp, "Asynchonous message received\n");
			hex_message(pmessage, (*le));
		}
		(*(pmessage->m_socket->soc_user_ast))(pmessage);
	}
}

/*	Parse address string and setup socket  (CATS)
**	-------------------------------------
**
** On entry,
**	socket  is already valid
**	s	is the entire service name
*/

rpc_status ts_open_CATS(socket, tsap)
	socket_type	socket;
	char *		tsap;
{
    int			i, l;
    register int	j;
    rpc_status		status;
    rpc_long		st;

    int			ind, tl;
    device_name_type	d;
    char *		sys_dollar_net = "SYS$NET.";

    {
	register struct socket_struct *soc = socket;

	soc->soc_efn = 0;

/*  Determine socket role:
**
**  On omitted TSAP, or 'SYS$NET' means the process is a passive one started
**  by a daemon.
*/
	i = 1;
	switch ((int)(tsap[0])) {

		  case 0 :
			soc->mdp.soc_cats.soc_role = passive;
			break;

		  case '*':
			soc->mdp.soc_cats.soc_role = master;
			i++;
			break ;

		  case '#':
			soc->mdp.soc_cats.soc_role = transient;
			i++;
			break ;

		  default:
			soc->mdp.soc_cats.soc_role = passive;
			for (j = 0; j<7 ; j++)
			    if (tsap[j] != sys_dollar_net[j])
				soc->mdp.soc_cats.soc_role = single;
	} /*end switch*/

/*  Extract Transport Suffix (object name):
*/

	if (strlen(tsap)>device_length) status = RPC_SYNTAX_4; /*?*/
	else {
	    struct device_struct *tsf = &socket->mdp.soc_cats.soc_tsuf;
	    strcpy(tsf->body, tsap);
	    tsf->length = strlen(tsap);
	}

/*  Perform the remaining initialisation:
*/
	if (GOOD(status)) {
	    TS_length	tsapl;
	    TS_opts	zero = 0;

	    soc->mdp.soc_cats.soc_connected = TRUE;
	    soc->mdp.soc_cats.soc_self = socket;
	    soc->mdp.soc_cats.soc_master = NULL;
	    soc->mdp.soc_cats.soc_slaves = NULL;
	    tsapl = soc->mdp.soc_cats.soc_tsuf.length;
	    switch ((int)(soc->mdp.soc_cats.soc_role)) {
	      case passive:
		    status = RPC_S_NOT_IMPLEMENTED;
		    break ;

	      case single:
		    (void)cats_error(
				  tscnrw(&soc->mdp.soc_cats.soc_conn_id,
				      soc->mdp.soc_cats.soc_tsuf.body,
				      &soc->mdp.soc_cats.soc_tsuf.length,
				      &zero, 
				      &soc->mdp.soc_cats.soc_systat),
				  &status,
				  ts_cncf, 
				  "Connect confirm completion");
		    break ;

	      case transient:
		    soc->mdp.soc_cats.soc_connected = FALSE;
		    break ;

	      case master:
		    (void) lib_get_ef(&soc->soc_efn);
		    soc->mdp.soc_cats.soc_async_read_queued = FALSE;
		    listen_master(&socket, &status);
		    if (rpc_trace) {
			    sys_setast(0);
			    UTRACE(tfp, "new cats server initialised:\n");
			    UTRACE(tfp, "  device name: %.40s\n",
					    soc->mdp.soc_cats.soc_tsuf.body);
			    UTRACE(tfp, "  connection: %1d\n",
				    soc->mdp.soc_cats.soc_conn_id);
			    UTRACE(tfp, "  lib_GET_EF : %1d\n", soc->soc_efn);
			    sys_setast(1);
		    }
		    break ;

	    } /*switch */

	    if (GOOD(status))
		    soc->mdp.soc_cats.soc_connected = FALSE;

	} /* if parse ok */
    } /* with soc */

    return status;
}

/*	Return RPC address of this socket		        cats_my_address()
**	---------------------------------
**
**  This routine returns a string indicating the RPC address of the socket.
**
**  On entry,
**	soc	    should be a valid socket pointer
**	addrstr	    points to a buffer to contain a string.
**	addrlen    indicates the length of the string buffer
**
**  On exit,
**	*addrstr    has the contact address for an external node wishing to contact
**		    this socket written into it, zero terminated, without the
**		    protocol on the end.
*/
#ifdef __STDC__
PRIVATE rpc_status cats_my_address(
    socket_type	    soc,
    chr *	    addrstr,
    int		    addrlen)
#else
PRIVATE rpc_status cats_my_address(soc, addrstr, addrlen)
    socket_type	    soc;
    char *	    addrstr;
    int		    addrlen;
#endif
{
    @@@
}

/*	Return RPC address of peer		              cats_peer_address()
**	--------------------------
**
**  On entry,
**	soc	    should be a valid socket pointer
**	address	    points to a buffer to contain an rpc_name.
**
**  On exit,
**	*address    has the contact address for the peer node.
**		    blank filled.
**		    26 useful characters in the address.
*/
#ifdef __STDC__
PRIVATE rpc_status cats_peer_address(
    socket_type	    soc,
    char * 	    addrstr,
    int		    addrlen)
#else
PRIVATE rpc_status cats_peer_address(soc, addrstr, addrlen)
    socket_type	    soc;
    char *	    addrstr;
    int		    addrlen;
#endif
{
    @@@
}

/*      Send a message over the given socket			     cats_write()
**	------------------------------------
**
** On entry,
**      pmessage        points to a pointer to the message, with fields:
**	    m_next	    (undefined)
**	    m_socket	    valid
**	    m_index	    Length of message to be sent in bytes,
**			    exclusing any bytes needed for the protocol.
**	    m_status	    (undefined)
**
** 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
**      set, but without the data.  This routine is therefore DESTRUCTIVE.
**
**	returns:	Status of operation.
*/
#ifdef __STDC__
PRIVATE rpc_status cats_write(rpc_message_pointer *ppmessage)
#else
PRIVATE rpc_status cats_write(ppmessage)
    rpc_message_pointer *ppmessage;
#endif
{
    rpc_status		    status;
    TS_length		    tsapl;
    register rpc_message *  mes =	*ppmessage;
    TS_opts		    zero =	0;
    TS_length		    tx_size =	(TS_length)mes->m_index;
    register socket_type    soc =	mes->m_socket;

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

    if (!soc->mdp.soc_cats.soc_connected)
	soc->mdp.soc_cats.soc_connected = (BOOLEAN)
	    (!cats_error(
		tscnrw(&soc->mdp.soc_cats.soc_conn_id, soc->mdp.soc_cats.soc_tsuf.body, 
		    &soc->mdp.soc_cats.soc_tsuf.length,
		    &zero,
		    &soc->mdp.soc_cats.soc_systat),
		&status,
		ts_cncf,
		"Connect confirm for fast client"));

    if (soc->mdp.soc_cats.soc_connected) {
	    if (rpc_trace) {
		    sys_setast(0);
		    UTRACE(tfp, "Cats message to be txd (soc_conn_id = %1d)\n",
				 soc->mdp.soc_cats.soc_conn_id);
		    sys_setast(1);
		    hex_message(mes, m_index);
	    }

	    (void)cats_error(
		       tssndw(
				&soc->mdp.soc_cats.soc_conn_id,
				&mes->body,
				&tx_size,
				&soc->mdp.soc_cats.soc_systat),
		       &status,
		       ts_rdyid, 
		       "TSSNDW completion");
	    if (rpc_trace) {
		    sys_setast(0);
		    UTRACE(tfp, "Cats message transmitted.\n");
		    sys_setast(1);
	    }
    } else
	    if (rpc_trace) {
		    sys_setast(0);
		    UTRACE(tfp, "CATS- Unable to connect to remote before sending in conn:%1d\n", soc->mdp.soc_cats.soc_conn_id);
		    sys_setast(1);
	    }

    mes->m_status = status;
    return status;
}


/*      Receive message						      cats_read()
**	---------------
**
**  This procedure waits for a message to arrive on a socket and returns the
**  message. A suitable message buffer is passed to the routine, but a
**  different one may be returned. If the socket passed is the master socket
**  for mutiple connections, then the message recieved will carry the socket
**  number of the slave socket on which the message actually arrived.
**
** On entry:
**      ppmessage       is pointer to pointer to buffer, fields:
**	    m_socket	    The socket to be used - possibly a master socket
**	    m_index	    don't care
**	    m_status	    don't care
**	    m_next	    don't care
**
**      timeout         -1 for infinite, 0 for poll, else in units of 10ms
**
** On exit,
**	returns		The status of the operation.
**      *pmessage       may have been modified to point to another message
**                      from the pool. Fields:
**	    m_socket	    The (possible slave) socket the msg came on.
**	    m_status	    The status of the operation (= return value).
**	    m_index	    The number of bytes recieved if good status
**	    m_next	    junk.
**
*/
#ifdef __STDC__
PRIVATE rpc_status cats_read(
        rpc_message_pointer *ppmessage,
        int		    timeout)
#else
PRIVATE rpc_status cats_read(ppmessage, timeout)
        rpc_message_pointer *ppmessage;
        int		    timeout;
#endif
{
    @@@
    return status;
}

/*      Asynchronous Receive
**	--------------------
**
** On entry,
**      pmessage        is the address of a suitable message buffer
**      action          is the address of the routine to be called
**      user_1          is not used at the moment.
**
** On exit,
**      the parameters are stored only.  Exits immediately.
**
** On arrival of the next message:
**      The routine *action() is called, and passed the message as a parameter.
**      If the buffer *pmessage is not used, it will be reused elsewhere and
**      a different buffer passed to *action().
*/
#ifdef __STDC__
PRIVATE rpc_status cats_aread(
        rpc_message_pointer     pmessage,
        rpc_pointer		action,
        rpc_integer		user_1)
#else
PRIVATE rpc_status cats_aread(pmessage, action, user_1)
        rpc_message_pointer     pmessage;
        rpc_pointer		action;
        rpc_integer		user_1;
#endif
{
    rpc_status	    status;
    socket_type	    socket =	pmessage->m_socket;
    TS_length	    rx_size =	RPC_BUFFER_SIZE;

    socket->soc_user_ast = action;
    {
	register struct socket_struct *soc = socket;

	if (soc->mdp.soc_cats.soc_master != NULL)
		socket = soc->mdp.soc_cats.soc_master;
	if (socket->mdp.soc_cats.soc_role != master) {
	    soc->mdp.soc_cats.soc_message = pmessage;
	    (void)cats_error(
		tsrcv(&soc->mdp.soc_cats.soc_conn_id,
		    &pmessage->body.rpc_b,
		    &rx_size,
		    &soc->mdp.soc_cats.soc_systat, 
		    rpc_cats_ast_service,
		    &socket),
		&status,
		ts_ok, 
		"Asynch receive initiated (queued)");
	} else {
	    if (socket->mdp.soc_cats.soc_async_read_queued)
	        status = RPC_S_ALREADY_READING;
	    else
		if (search_chan_list(&socket, &pmessage, &status))
			(*(socket->soc_user_ast))(pmessage);
		else {
			socket->mdp.soc_cats.soc_async_read_queued = TRUE;
			socket->mdp.soc_cats.soc_message = pmessage;
		}
	}
    }
    return status;

} /* cats_aread */

/*	Open a transport connection				      cats_open()
**	---------------------------
**
**  On entry,
**	soc points to a recently created socket, fields as follows:
**
**	    soc_last_call_sent	    NULL
**	    soc_last_reply_sent	    NULL
**	    soc_next-call_tid	    0
**	    soc_protocol		    valid pointer to protocol structure
**
**	    and otherwise uninitialised.
**
**	tsap	points to a zero-terminated string representing the address
**		(with no .cats on it)
*/

#ifdef __STDC__
PRIVATE rpc_status cats_open(socket_type soc, char * tsap)
#else
PRIVATE rpc_status cats_open(soc, tsap)
    socket_type	soc;
    char	*tsap;
#endif
{
    @@@
}

/*	Close a Transport Connection				    cats_close()
**	----------------------------
**
**  On entry,
**	soc	Must be a socket opened by cats_open().
**
**  On exit,
**	returns	a status of the close operation.
*/

#ifdef __STDC__
PRIVATE rpc_status cats_close(socket_type soc)
#else
PRIVATE rpc_status cats_close(soc)
    socket_type	soc;
#endif
{
    return delete_socket(soc);
} /* cats_close */

/*	Data structure defining this medium:
**	-----------------------------------
*/

PRIVATE rpc_protocol cats_protocol = {
    0,				/* Link (not yet used) */
    "cats",			/* Name of medium used in address strings */
    0,				/* No, not reliable */
    cats_open,
    cats_close,
    cats_write,
    cats_read,
    cats_aread,
    cats_my_address,
    cats_peer_address,
};


/*	Register this protocol with the RPC system
**	------------------------------------------
*/

#ifdef __STDC__
rpc_status use_cats(void)
#else
rpc_status use_cats()
#endif
{
    valid_sockets = NULL;
    always_zero = 0;
    cats_register_errors();

    rpc_use(cats_protocol);
}

