/*		Ethernet Transport Service for RPC		        ts_eth.c
**		==================================
**
**  This module provides a transport service for the RPC system developed
**  by CERN/DD Online group in 1986-90.
**
**  This particular module uses a raw ethernet packet interface, by implementing
**  its own protocol. 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.
**
**  Known defficiencies:
**
**    o	Explicit references to fields within the rpc message body, instead
**	of always using the marshalling macros
**
**  History:
**	30 Apr 90   Code extracted from general ts.c module.
**
**  Compilation switches:
**
**	AST	    The operating system supports asynchronous procedures.
**
**	ETHERNET    This RPC system runs over raw ethernet. Mandatory.
*/

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

# define INITIAL_RETRY_TIME 500
# define BACKOFF_LIMIT 3000

/*	RPC default protocol type numbers:
*/
# define RPC_PTYPE_H 80
# define RPC_PTYPE_L 80
# define PRIO_PTYPE_H 81
# define PRIO_PTYPE_L 80
 

#include <stdio.h>
#include "syspec.h"

#ifndef ETHERNET
# define ETHERNET		/* Force declarations which we will need */
#endif

#define TS			/*  Require visibility of TS data structures */
#define TS_INTERNALS		/*  Require visibility of TS data structures */
#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 */
 
#ifdef AST				/* Always used by CATS !!!!!! */
extern void sys_setast();		/* En/Disable ASTs */
rpc_status ts_when_receive();           /* forward (ASTs only) */
#endif

BOOLEAN      lance_enabled;         /* Comment removed to compile with AST option needed for TSCATS routines */ 

/*
**      Ethernet access routines
**
**  These are the low-level routines which provide access at the packet level.
*/

#ifdef __STDC__
extern rpc_status rpc_eth_init();	    /* Power-on initialise  */
extern rpc_status rpc_eth_open();	    /* Initialise socket    */
extern rpc_status rpc_eth_close();	    /* Deinitialise socket  */
extern rpc_status rpc_eth_send();	    /* Transmit one packet  */
extern rpc_status rpc_eth_receive();	    /* Synchronous receive  */
extern rpc_status rpc_eth_when_receive();   /* Queue async. receive */
extern rpc_status rpc_eth_my_address(	    /* Return host address  */
    rpc_ethernet_address	host);	    /* Buffer host address  */
#else
extern rpc_status rpc_eth_init();	    /* Power-on initialise  */
extern rpc_status rpc_eth_open();	    /* Initialise socket    */
extern rpc_status rpc_eth_close();	    /* Deinitialise socket  */
extern rpc_status rpc_eth_send();	    /* Transmit one packet  */
extern rpc_status rpc_eth_receive();	    /* Synchronous receive  */
extern rpc_status rpc_eth_when_receive();   /* Queue async. receive */
extern rpc_status rpc_eth_my_address();	    /* Return host address  */
#endif

/*			UTILITY ROUTINES
**
*/

/*		I/O routines used for trace output
**
**
**	Print ethernet address  as XX_XX_XX_XX_XX_XX
**	----------------------
**
**  On entry,
**	e	    points to the address in binary as 6 bytes
**	rpc_trace   must be TRUE 
*/
#ifdef ETHERNET
#ifdef __STDC__
void write_ethernet_address(rpc_ethernet_address e)
#else
void write_ethernet_address(e)
        rpc_ethernet_address    e;
#endif
{
    register int        i;

    for (i =0; i<5; i++) {
	    UTRACE(tfp, "%02x_", e[i]);
    }
    UTRACE(tfp, "%02x", e[5]);
}
#endif

/*		    Ethernet-Specific Routines
**		    --------------------------
*/
 
/*      Parse hexadecimal character				     hex_digit()
**	---------------------------
 */
 rpc_byte
hex_digit(pp, pstatus)
    char	**pp;	    /* Pointer is moved on */
    rpc_status	*pstatus;   /* Updated only if bad */
{
	register char ch = *(*pp)++;

	if ((ch>='0')&&(ch<='9')) return (rpc_byte)(ch-'0');
	if ((ch>='A')&&(ch<='F')) return (rpc_byte)((ch-'A')+10);
	if ((ch>='a')&&(ch<='f')) return (rpc_byte)((ch-'a')+10);
        *pstatus = RPC_S_BAD_ETHERNET_ADDRESS;
	return 0;
}

 
/*      Parse hexadecimal byte (two characters)			      hex_byte()
**	----------------------
*/
rpc_byte hex_byte(pp, pstatus)
    char	**pp;	    /* Pointer is moved on */
    rpc_status	*pstatus;   /* Updated only if bad */
{
        rpc_byte        msbyte;
 
        if ((**pp == '_') || (**pp == '-')) (*pp)++;	/* Skip _ or - */
        msbyte = hex_digit(pp, pstatus) * 16;
        return msbyte + hex_digit(pp, pstatus);
}
 

/*	Parse Ethernet Address				         eth_parse()
**	----------------------
**
**  This sets the ethernet addres, protocol type, and the masks from the
**  ethernet address string.  The obsolete form "123456123456.priority"
**  is allowed for "123456123456_5150".
**
**  Note that the hex_byte routine stored any hex errors into the status
**  word as we go.
*/
#ifdef __STDC__
PRIVATE rpc_status eth_parse(socket_type soc, char * tsap)
#else
PRIVATE rpc_status eth_parse(soc, tsap)
    socket_type	    soc;
    char	    *tsap;
#endif

{   int		j;			    /* Byte counter	    */
    char	*p =	    tsap;	    /* Pointer into string  */
    rpc_status	status =    RPC_S_NORMAL;

/*      Wildcard address:
*/
    if (*p == WILDCARD) {
	p++;
	for (j = 0; j<6; j++) {
		    soc->mdp.soc_ether.soc_addr_filter[j] = 0;
		    soc->mdp.soc_ether.soc_addr_mask[j] = 0;
	} /* for */

/*      Specific address:
*/
    } else {
	for (j = 0; j<6 ; j++) {
		soc->mdp.soc_ether.soc_rx_dest_addr[j] =
			hex_byte(&p, &status);
		soc->mdp.soc_ether.soc_addr_filter[j] =
		   soc->mdp.soc_ether.soc_rx_dest_addr[j];
		soc->mdp.soc_ether.soc_addr_mask[j] = 255;
	} /*for*/
    } /*if else*/

/*      Parse protocol type:
*/
    soc->mdp.soc_ether.soc_type_mask = -1;
    if (*p == 0) { 
	soc->mdp.soc_ether.soc_type_filter.l = RPC_PTYPE_L;
	soc->mdp.soc_ether.soc_type_filter.h = RPC_PTYPE_H;
    } else if (*p == '.') {
	soc->mdp.soc_ether.soc_type_filter.l = PRIO_PTYPE_L;
	soc->mdp.soc_ether.soc_type_filter.h = PRIO_PTYPE_H;
    } else if (*p == WILDCARD) {
	soc->mdp.soc_ether.soc_type_filter.l = 0;
	soc->mdp.soc_ether.soc_type_filter.h = 0;
	soc->mdp.soc_ether.soc_type_mask = 0;
    } else {
	soc->mdp.soc_ether.soc_type_filter.h =
			    hex_byte(&p, &status);
	soc->mdp.soc_ether.soc_type_filter.l =
			    hex_byte(&p, &status);
    } /*else*/
    return status;
} /* eth_parse */

/*	Return RPC address of this socket		        eth_my_address()
**	---------------------------------
**
**  This routine returns a string indicating the RPC address of the socket.
**  Do not confuse this with rpc_eth_my_address which gives the ethernet
**  address in binary of the node.
**
**  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__
rpc_status eth_my_address(
    socket_type	    soc,
    chr *	    addrstr,
    int		    addrlen)
#else
rpc_status eth_my_address(soc, addrstr, addrlen)
    socket_type	    soc;
    char *	    addrstr;
    int		    addrlen;
#endif
{
    rpc_ethernet_address    host;
    rpc_status		    status;

/*  Format:	AA0004002F58_5051   is 17 characters +1 for terminator */

    if (addrlen < 18) return RPC_S_BUFFER_OVERFLOW;

    status = rpc_eth_my_address(host);	/* Get binary of host address */
    if (BAD(status)) return status;

    sprintf(addrstr,
	    "%02x%02x%02x%02x%02x%02x_%02x%02x",
	    host[0], host[1], host[2], host[3], host[4], host[5],
	    soc->mdp.soc_ether.soc_type_filter.h,
	    soc->mdp.soc_ether.soc_type_filter.l);

    addrstr[17]=0;	    /* terminate it */

    return status;
}

/*	Return RPC address of peer		              eth_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__
rpc_status eth_peer_address(
    socket_type	    soc,
    char * 	    addrstr,
    int		    addrlen)
#else
rpc_status eth_peer_address(soc, addrstr, addrlen)
    socket_type	    soc;
    char *	    addrstr;
    int		    addrlen;
#endif
{
    rpc_byte		    *peer;

    if (addrlen < 19) return RPC_S_BUFFER_OVERFLOW;

    if (soc->mdp.soc_ether.soc_addr_filter[0])	/* Not wildcard */
	peer = soc->mdp.soc_ether.soc_addr_filter;
    else
	peer = soc->mdp.soc_ether.soc_rx_source_addr;    /* If wild, last sender */

    sprintf(addrstr,
	    "%02x%02x%02x%02x%02x%02x_%02x%02x",
	    peer[0], peer[1], peer[2], peer[3], peer[4], peer[5],
	    soc->mdp.soc_ether.soc_type_filter.h,
	    soc->mdp.soc_ether.soc_type_filter.l);

    addrstr[17]=0;	    /* terminate it */

    return RPC_S_NORMAL;
}

/*      Check whether two ethernet addresses are the same
**
**
*/
 BOOLEAN
same_address(a, b)
        rpc_ethernet_address    a, b;
{
        return (BOOLEAN)(
                   (a[0] == b[0])
                && (a[1] == b[1])
                && (a[2] == b[2])
                && (a[3] == b[3])
                && (a[4] == b[4])
                && (a[5] == b[5]));
}

/***************************************************************************
**
**      Function:       Check whether a message arriving is a duplicate,
**                      and if so, take the apropriate action.
**
*/

#ifdef __STDC__
BOOLEAN duplicate(
    rpc_message_pointer     pmessage)
#else
BOOLEAN duplicate(pmessage)
    rpc_message_pointer     pmessage;
#endif
{
    rpc_message_pointer     scan;
    register rpc_message *mes = pmessage;

    if (   (mes->body.which == RETURN_MESSAGE)
	|| (mes->body.which == REJECT_MESSAGE)) {
	if (mes->body.cal.call_transaction_id.h == 1)
	    {
		register struct socket_struct *soc = mes->m_socket;

		if (soc->soc_last_call_sent != NIL_MESSAGE)
		    if (mes->body.cal.call_transaction_id.l != soc->soc_last_call_sent->body.cal.call_transaction_id.l)
			return TRUE;
	    }
    } else {
	if (mes->body.which == CALL_MESSAGE) {
	    if (mes->body.cal.call_transaction_id.h == 1) {
		scan = mes->m_socket->soc_last_reply_sent;

		for(;;) {
		    if (scan) {
			if (same_address(scan->header.ether.dest,
				    mes->header.ether.source)) {
			    break;
			} else {
			    scan = scan->m_next;
			}
		    } else
			break;
		} /*for*/;

		if (scan)
		    if (mes->body.cal.call_transaction_id.l != 0)
			if (mes->body.cal.call_transaction_id.l
			   == scan->body.ret.return_transaction_id.l) {
			    CTRACE(tfp, "RPC/TS: Retransmitting reply\n");
			    mes->m_status = rpc_eth_send(scan);
			    return TRUE;
			}
		return FALSE;
	    }
	}
    }
    return FALSE;
}

#ifdef AST
/*      Service Arrival of Asynchronous Message		       eth_ast_service()
**	---------------------------------------
**
 * This procedure is called asynchonously when a message arrives.
 * The procedure deals with duplicate messages, and calls the user AST
 * service procedure if the message is not a duplicate.
 *
 * On entry,
 *      The only parameter is the received message, with the status and
 *      socket number filled in.
 */
PUBLIC void
eth_ast_service(message)
        rpc_message     *message;
{
        socket_type     socket;
        rpc_message_pointer     pmessage;
 
        {
                register rpc_message * mes = message;
                register struct socket_struct *soc = mes->m_socket;
 
                CTRACE(tfp, "RPC/TS/Eth: Async. message received.\n");
                pmessage = 0;
                if (duplicate(pmessage)) {
		    mes->m_index = RPC_BUFFER_SIZE;
		    (void) rpc_eth_when_receive(mes);	/* Ignore status */
                } else {
		    memcpy(soc->mdp.soc_ether.soc_rx_dest_addr,
			  mes->header.ether.dest, 6);
		    memcpy(soc->mdp.soc_ether.soc_rx_source_addr,
				mes->header.ether.source, 6);
		    memcpy(&soc->mdp.soc_ether.soc_rx_protocol_type,
				&mes->header.ether.ptype, 2);

		    (*soc->soc_user_ast)(message);  /* Call USER AST */

                }
        }
}
#endif 

/*      Send a message over the given socket			     eth_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 eth_write(rpc_message_pointer *ppmessage)
#else
PRIVATE rpc_status eth_write(ppmessage)
    rpc_message_pointer *ppmessage;
#endif
{
    rpc_status		status;

    register rpc_message *mes = *ppmessage;
    register struct socket_struct *soc = mes->m_socket;
 
    status = check_socket(soc);
    if (BAD(status)) return status;

    if (rpc_trace) {
	    UTRACE(tfp, "RPC/eth: Sending message, ");
	    hex_message(mes, mes->m_index);
    }

/*  If this is a wildcard socket, send this packet back to the sender
**  of the last packet received. Otherwise, send it to the address for
**  which the socket was set up.
*/
    if (soc->mdp.soc_ether.soc_addr_mask[0] == 0) {
	   memcpy( mes->header.ether.dest,
			soc->mdp.soc_ether.soc_rx_source_addr, 6);
	   memcpy( &mes->header.ether.ptype.h,
			&soc->mdp.soc_ether.soc_rx_protocol_type.h, 2);
    } else  {
	   memcpy( mes->header.ether.dest,
			soc->mdp.soc_ether.soc_addr_filter, 6);
	   memcpy( &mes->header.ether.ptype.h,
			&soc->mdp.soc_ether.soc_type_filter.h, 2);
    }

/*  If the message is an outgoing call, then the transaction identifier
**  if generated.
*/
    if (mes->body.which == CALL_MESSAGE) {
	register courier_word *tid =
	    &mes->body.cal.call_transaction_id;
	tid->h = 1;
	tid->l = soc->soc_next_call_tid;
	soc->soc_next_call_tid =  soc->soc_next_call_tid % 64 + 1;
   }

    status = rpc_eth_send(mes);

/*  If the message was a reply, then we should save it in case we need to
**  retransmit it. Replies are stored on a list, as we must separately keep
**  replies to different addresses.
*/
    if ((mes->body.which == RETURN_MESSAGE)
       || (mes->body.which == REJECT_MESSAGE))   {

	rpc_message_pointer scan, last, temp_pointer;

	last = NIL_MESSAGE;
	temp_pointer = NIL_MESSAGE;
	scan = soc->soc_last_reply_sent;
	do {
	    if (scan == NIL_MESSAGE) {

		if (rpc_trace) {
		    UTRACE(tfp, "RPC/eth: First reply to ");
		    write_ethernet_address(mes->header.ether.dest);
		    UTRACE(tfp, "\n");
		} /*if*/

		temp_pointer = *ppmessage;
		temp_pointer->m_next = NIL_MESSAGE;
		*ppmessage = (rpc_message *)malloc(
			(unsigned)(sizeof(*(*ppmessage))));
	    } else if (same_address((*ppmessage)->header.ether.dest,
			scan->header.ether.dest)) {
		temp_pointer = (*ppmessage);
		(*ppmessage) = scan;
		temp_pointer->m_next = (*ppmessage)->m_next; 
	    } else {
		last = scan;
		scan = scan->m_next;
	    }
	} while (!(temp_pointer != NIL_MESSAGE));

	if (last == NIL_MESSAGE)
	    soc->soc_last_reply_sent = temp_pointer;
	else
	    last->m_next = temp_pointer;
	(*ppmessage)->m_socket = temp_pointer->m_socket;
    }

/*  If the message is an outgoing call, we stored just the last call sent.
**  (If it's not a call or a return, it is not known)
*/
    else if ((mes->body.which == CALL_MESSAGE)) {
	rpc_message_pointer temp_pointer;

	if (soc->soc_last_call_sent == NIL_MESSAGE)
		rpc_new(&soc->soc_last_call_sent, RPC_BUFFER_SIZE);
	temp_pointer = (*ppmessage);
	(*ppmessage) = soc->soc_last_call_sent;
	soc->soc_last_call_sent = temp_pointer;
	(*ppmessage)->m_socket = soc->soc_last_call_sent->m_socket;
    }

    (*ppmessage)->m_status = status;
    return status;
} 

/*      Receive message						      eth_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.
**  (Ethernet does not use slave sockets)
**
** 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__
rpc_status eth_read(
        rpc_message_pointer *ppmessage,
        int		    timeout)
#else
rpc_status eth_read(ppmessage, timeout)
        rpc_message_pointer *ppmessage;
        int		    timeout;
#endif
{
    rpc_status	    status;
    int		    waited;	    /* Time waited so far */
    int		    backoff;	    /* Time to wait next time, which doubles */

    register rpc_message *mes = *ppmessage;
    register struct socket_struct *soc = mes->m_socket;

    if (soc->soc_last_call_sent == NULL) /* If nothing to retransmit */
	backoff = timeout;		     /* Wait for given time */
    else {
	waited = 0;
	backoff = INITIAL_RETRY_TIME;   
    }

    for(;;) {

	if (soc->soc_last_call_sent != NULL)
	 if (timeout >= 0)
	  if (waited + backoff > timeout) {
		  backoff = timeout - waited;
		  waited = waited + backoff;
	  }


/*  Wait for a packet to come in which is not a duplication of one we already
**  have:
*/
       do {
	    CTRACE(tfp, "RPC/eth: Waiting for ethernet packet\n");
	    status = rpc_eth_receive(ppmessage, backoff);
	    if (BAD(status)) break;
       } while (duplicate(*ppmessage));

/*  We take a copy of the last header received
*/
       memcpy(soc->mdp.soc_ether.soc_rx_dest_addr,
	      (*ppmessage)->header.ether.dest, 6+6+2);

/*  Decide whether to retransmit a previous call:
*/
       if (status != RPC_S_TIMEOUT) break;

       if (soc->soc_last_call_sent == NIL_MESSAGE) break;

       if (((timeout < 0) || (waited < timeout))) {
	    if (backoff < BACKOFF_LIMIT / 2)
		backoff = backoff + backoff;
	    else
		backoff = BACKOFF_LIMIT;
	    status = rpc_eth_send(soc->soc_last_call_sent);
	    CTRACE(tfp, "RPC/TS: *** Retransmitted last call.\n");
       } else {
	    break;
       }
    } /*for*/;

/*  When we successfully receive a reply, we can throw away the call:
*/
    if (GOOD(status))
       if (soc->soc_last_call_sent != NIL_MESSAGE)
	  if (((*ppmessage)->body.which == RETURN_MESSAGE)
	    || ((*ppmessage)->body.which == REJECT_MESSAGE)) {
		   rpc_dispose(soc->soc_last_call_sent);
		   soc->soc_last_call_sent = NIL_MESSAGE;
		   }

    if (rpc_trace)
    if (GOOD(status)) {
	    UTRACE(tfp, "RPC/eth: (synchronous) Message received OK, ");
	    hex_message(*ppmessage, (*ppmessage)->m_index);
    } else {
	    UTRACE(tfp, "RPC/eth: Receive FAILED, status = %lx\n", status);
    }

    (*ppmessage)->m_status = status;
    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__
rpc_status eth_aread(
        rpc_message_pointer     pmessage,
        rpc_pointer		action,
        rpc_integer		user_1)
#else
rpc_status eth_aread(pmessage, action, user_1)
        rpc_message_pointer     pmessage;
        rpc_pointer		action;
        rpc_integer		user_1;
#endif
{
#ifdef AST
    pmessage->m_socket->soc_user_ast = action;
    return rpc_eth_when_receive(pmessage);  /* Call system-depenedent code */
#else
    return(RPC_S_NOT_IMPLEMENTED);
#endif
} /* eth_aread */

/*	Open a transport connection				      eth_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 .ETHERNET on it)
*/

#ifdef __STDC__
rpc_status eth_open(socket_type soc, char * tsap)
#else
rpc_status eth_open(soc, tsap)
    socket_type	soc;
    char	*tsap;
#endif
{
    rpc_status	status;

    status = eth_parse(soc, tsap);
    if (BAD(status)) return status;

    return rpc_eth_open(soc);
}

/*	Close a Transport Connection				     eth_close()
**	----------------------------
**
**  On entry,
**	soc	Must be a socket opened by eth_open().
**
**  On exit,
**	returns	a status of the close operation.
*/
/*  In fact rpc_eth_close is used instead of this routine. */

#ifdef NOT_DEFINED
#ifdef __STDC__
rpc_status eth_open(socket_type soc)
#else
rpc_status eth_open(soc)
    socket_type	soc;
#endif
{
    return rpc_eth_close(soc);

} /* eth_close */
#endif

/*	Data structure defining this medium:
**	-----------------------------------
*/

rpc_protocol eth_protocol = {
    0,				/* Link (not yet used) */
    "ethernet",			/* Name of medium used in address strings */
    0,				/* No, not reliable */
    eth_open,
    rpc_eth_close,		/* Here we use the system-dependent routine */
    eth_write,
    eth_read,
    eth_aread,
    eth_my_address,
    eth_peer_address,
};


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

#ifdef __STDC__
rpc_status use_ethernet(void)
#else
rpc_status use_ethernet()
#endif
{
    rpc_eth_init();		/* Initialise any machine-dependent stuff */
    rpc_use(&eth_protocol);
}

