/* 
 * $RCSfile: sim_ether.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: sim_ether.c,v $
 * Revision 1.64.1.4  1997/04/29 22:47:21  rrp
 * minor function name changes
 *
 * Revision 1.64.1.3  1996/01/30 21:00:47  slm
 * Updated copyright and version.
 *
 * Revision 1.64.1.2.1.3  1994/11/26  00:51:35  hkaram
 * msgDestroy has been removed. It is now taken care of in the
 * buffer pool handler by msgRefresh.
 *
 * Revision 1.64.1.2.1.2  1994/11/22  23:04:45  hkaram
 * Converted to new msg tool
 *
 * Revision 1.64.1.2.1.1  1994/10/27  22:30:47  hkaram
 * New branch
 *
 * Revision 1.64.1.2  1994/03/16  20:43:23  menze
 * ArpBinding and SimAddr structures use MAC48bithosts instead of
 * ETHhosts
 *
 * Revision 1.64.1.1  1994/02/22  00:48:54  menze
 * Added 'iphost' ROM option to override gethostbyname result
 *
 * Revision 1.64  1993/11/16  18:36:41  menze
 * Name of SIMETH_SOCK2ETHADDR control op changed to relatively
 * ethernet-independent SIM_SOCK2ADDR
 */

#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>

#include <sys/types.h>
#include <netinet/in.h>
#include "x_stdio.h"
#include "xkernel.h"
#include "inputProcess.h"
#include "eth.h"
#include "eth_i.h"
#include "sim_ether_i.h"
#include "arp.h"
#include "machine.h"	/* For routines to establish interrupt handler */
#include "config.h"
#include "romopt.h"
#include "sim.h"

int	tracesimethp;

#ifdef XK_DEBUG
#   define SIMETH_STATS	
#endif

/* 
 * Single buffer pool for all simeth instances.
 */
static BufferPool *	simethBufferPool;
static int		numShepherdThreads = SIMETH_SHEPHERD_THREADS;
#ifdef SIMETH_STATS
static int		activeThreads;
static int		activeHighWater;
#endif
static char		dummyBuf[4096];
static ETHhost		ethBcastHost = ETH_BCAST_HOST;
static int		instance;

extern int	gethostname( char *, int );
extern int	getpid( void );
extern int	socket( int, int, int );

static int	arpForEachFunc( ArpBinding *, VOID * );
static int	initSocket( int );
static void	internalDemux( InputBuffer * );
static int	read_ether( int, char *, int );
static XkReturn	readIPhostRom( Protl, char **, int, int, VOID * );
static XkReturn	readPortRom( Protl, char **, int, int, VOID * );
static XkReturn	readPortBackRom( Protl, char **, int, int, VOID * );
static XkReturn	readThreadsRom( char **, int, int, VOID * );
static int	readether2demux( VOID * );
static void	sendOnSocket( int, ETHhost *, char *, int );

typedef struct sockaddr_in SockAddrIn;
static void	simEth2sock( ETHhost, SockAddrIn * );

static int	simethControl( Protl, int, char *, int );
static XkReturn	simethOpenEnable( Protl, Protl, Protl, Part * );
static XkHandle	simethPush( Sessn, Msg * );
static void	sock2simEth( ETHhost *, IPhost, int );
static void	writeBcast( PState *, char *, int );

#ifdef __STDC__

void simeth_init(Protl);

#endif /* __STDC__ */


static ProtlRomOpt protlOpts[] = {
    { "iphost", 3, readIPhostRom },
    { "port", 3, readPortRom },
    { "", 2, readPortBackRom }
};

static RomOpt	shepOpts[] = {
    { "threads", 3, readThreadsRom }
};


/* changes to support full internet addressing in rom files */

static void
simEth2sock(ethAddr, sockAddr)
    ETHhost ethAddr;
    struct sockaddr_in *sockAddr;
{
  bzero((char *)sockAddr, sizeof (struct sockaddr_in));
  sockAddr->sin_family = AF_INET;
  /* 
   * IP address is in the first 4 bytes of the ethernet address
   * UDP port is in the 5th and 6th bytes of the ethernet address
   */
  sockAddr->sin_addr = *(struct in_addr *)&ethAddr;
  sockAddr->sin_port = (*(u_short *)((char *)&ethAddr + 4));
}


/* changed inAddr to pass-by-value to make sparc and 
   sun3 compatible - cjt 5/15 */

static void
sock2simEth(ethAddr, inAddr, udpPort)
     ETHhost *ethAddr;
     IPhost inAddr;
     int udpPort;
{
    char *cp1, *cp2;
    short tmpshrt;
    int i;
    
    cp1 = (char *)ethAddr;
    cp2 = (char *) &inAddr;		/* passed by value now - cjt */
    for (i=0; i<4; i++) *cp1++ = *cp2++;
    tmpshrt = htons((u_short)udpPort);
    cp2 = (char *) &tmpshrt;
    for (i=0; i<2; i++) *cp1++ = *cp2++;
}


static int
arpForEachFunc( ab, arg )
    ArpBinding	*ab;
    VOID 	*arg;
{
    EthMsg	*m = (EthMsg *)arg;

    sendOnSocket(m->sock, (ETHhost *)&ab->hw, m->buf, m->len);
    return TRUE;
}


/* 
 * We simulate broadcast by having ARP perform a callback
 * for every host it has in its table.  We then do a direct send for
 * each ARP entry.
 */
static void
writeBcast( ps, buf, len )
    PState	*ps;
    char	*buf;
    int		len;
{
    EthMsg	m;
    ArpForEach	afe;
    
    if ( ! ps->arp ) {
	xError("eth bcast write fails -- no arp protocol");
	return;
    }
    m.buf = buf;
    m.len = len;
    m.sock = ps->sock;
    afe.f = arpForEachFunc;
    afe.v = &m;
    xControlProtl(ps->arp, ARP_FOR_EACH, (char *)&afe, sizeof(ArpForEach));
}	


static void
sendOnSocket( sock, dest, buf, len )
    int		sock;
    ETHhost	*dest;
    char	*buf;
    int		len;
{  
    extern unsigned long inet_addr( char * );
    struct  sockaddr_in	addr;

    simEth2sock(*dest, &addr);
    xTrace3(simethp, TR_FUNCTIONAL_TRACE,
	    "write_ether: sending %d bytes to <%d,%s>",
	    len, ntohs(addr.sin_port), inet_ntoa(addr.sin_addr));
    
    while (sendto(sock, buf, len, 0, (struct sockaddr *)&addr,
		  sizeof(struct sockaddr)) != len) {
	xTrace0(simethp, TR_ERRORS, "write_ether: error in sendto");
	xError("sim_ether: sendto");
	/*    exit(1);  */
    }
}


static int
read_ether( int sock, char *msg, int len )
{
    struct sockaddr_in	from;
    int			size, n;
    
    xTrace0(simethp, TR_EVENTS, "read_ether");
    size = sizeof(from);
    if ((n = recvfrom(sock, msg, len, 0, (struct sockaddr *)&from, &size)) < 0)
      return -1;
    xTrace3(simethp, TR_FUNCTIONAL_TRACE,
	    "read_ether: receiving %d bytes from <%d,%s>",
	    n, ntohs(from.sin_port), inet_ntoa(from.sin_addr));
    return n;
}


static int
initSocket( port )
    int	port;
{
    int			s, bufSize, bufSizeSize;
    struct sockaddr_in	addr;
    int			on = 1;

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons((u_short)port);
    if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	Kabort("init_ether: cannot open socket");
    }	
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on));
#ifdef SO_BROADCAST
    setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof (on));
#endif
    /*
     * increase receive buffer sizes from default
     */
    bufSize = RCVBUFSIZE;
    if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&bufSize,
		   sizeof(bufSize))) {
	xTrace1(simethp, TR_ERRORS,
		"Could not set size of ethernet receive buffer to %d",
		bufSize);
    }
    bufSizeSize = sizeof(bufSize);
    if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&bufSize, &bufSizeSize)) {
	xTrace0(simethp, TR_ERRORS,
		"Could not get size of ethernet receive buffer");
    } else {
	xTrace1(simethp, TR_GROSS_EVENTS,
		"Receive buffer of ethernet socket: %d", bufSize);
    }
    if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&bufSize, &bufSizeSize)) {
	xTrace0(simethp, TR_ERRORS,
		"Could not get size of ethernet send buffer");
    } else {
	xTrace1(simethp, TR_GROSS_EVENTS,
		"Send buffer of ethernet socket: %d", bufSize);
    }
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr))) {
	Kabort("init_ether: cannot bind socket");
    }
    return s;
}


static XkReturn
readIPhostRom( self, str, nFields, line, arg )
    Protl	self;
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    xAssert(arg);
    return str2ipHost((IPhost *)arg, str[2]);
}


static XkReturn
readPortRom( self, str, nFields, line, arg )
    Protl	self;
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    PState	*ps = (PState *)self->state;
    return sscanf(str[2], "%d", &ps->port) >= 1 ? XK_SUCCESS : XK_FAILURE;
}


/* 
 * Backward compatibility ... no opcode field
 */
static XkReturn
readPortBackRom( self, str, nFields, line, arg )
    Protl	self;
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    PState	*ps = (PState *)self->state;
    return sscanf(str[1], "%d", &ps->port) >= 1 ? XK_SUCCESS : XK_FAILURE;
}


static XkReturn
readThreadsRom( str, nFields, line, arg )
    char	**str;
    int		nFields, line;
    VOID	*arg;
{
    return sscanf(str[2], "%d", &numShepherdThreads) >= 1 ?
      		XK_SUCCESS : XK_FAILURE;
}


void
simeth_init( self )
    Protl	self;
{
    struct hostent	*h;
    char		name[100];
    int			namelen=100;
    PState		*ps;
    IPhost		myIpHost;
    
    xTrace0(simethp, TR_MAJOR_EVENTS, "init_ether");
    if ( instance > SIMETH_MAX_INSTANCES ) {
	Kabort("simeth -- too many instances");
    }
    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    self->state = (VOID *)ps;
    ps->mtu = ETH_MTU_SIZE;
    ps->port = -1;
    /* 
     * Determine the IPhost component of my host address.  This may be
     * overridden by a ROM entry
     */
    gethostname(name,namelen);
    h = gethostbyname(name);
    myIpHost = *(IPhost *) h->h_addr;
    findProtlRomOpts(self, protlOpts, sizeof(protlOpts)/sizeof(ProtlRomOpt), &myIpHost);
    if ( ps->port == -1 ) {
	xTrace1(simethp, TR_ERRORS,
		"No port specified for simeth instance %d", instance);
	sprintf(errBuf, "%s -- no UDP port specified (check RomFile)",
		self->fullName);
	Kabort(errBuf);
    }
    if ( instance == 0 ) {
	findRomOpts("shepherd", shepOpts, sizeof(shepOpts)/sizeof(RomOpt), 0);
	simethBufferPool = xkBufferPoolInit(numShepherdThreads, MAX_ETH_SIZE,
					    internalDemux);
    }
    xTrace1(simethp, TR_MAJOR_EVENTS,
	    "init_ether: listening on port %d", ps->port);
    if ( (ps->sock = initSocket(ps->port)) == 0 ) {
	Kabort("init_ether: problems creating socket");
    }
    installSignalHandler(ps->sock, readether2demux, self);
    if ( fcntl(ps->sock, F_SETFL, (FASYNC | FNDELAY)) == -1 ) {
	Kabort("fcntl async");
    }
    if ( fcntl(ps->sock, F_SETOWN, getpid()) == -1 ) {
	Kabort("fcntl setown");
    }
    xTrace1 (simethp, TR_EVENTS, "init_ether: ip started with address %s\n", ipHostStr (&myIpHost));
    sock2simEth(&ps->myHost, myIpHost, ps->port);
    xTrace1(simethp, TR_GROSS_EVENTS,
	    "init_ether: ethernet started with address %s",
	    ethHostStr(&ps->myHost));

    self->push = simethPush;
    self->controlprotl = simethControl;
    self->openenable  = simethOpenEnable;
    self->up = 0;
    instance++;
}


static void
ethMsgStore( void *hdr, char *netHdr, long len, void *arg )
{
    xAssert(len == sizeof(ETHhdr));
    bcopy(hdr, netHdr, sizeof(ETHhdr));
}


static long
ethMsgLoad( void *hdr, char *netHdr, long len, void *arg )
{
    xAssert(len == sizeof(ETHhdr));
    bcopy(netHdr, (char *)hdr, sizeof(ETHhdr));
    return sizeof(ETHhdr);
}


static XkHandle
simethPush( self, msg )
    Sessn	self;
    Msg 	*msg;
{
    ETHhdr	*hdr = msgGetAttr(msg, 0);
    char	buffer[EMAXPAK];
    char	*bufPtr = buffer;
    int		len;
    int         dataLen;
    PState	*ps = (PState *)self->state;
    VOID        *buf;
    MsgWalk  mi;
    u_char      *data;

    xTrace0(simethp, TR_EVENTS, "simethPush");
    xAssert(hdr);

    buf = msgPush(msg, sizeof(ETHhdr));
    xAssert(buf);
    ethMsgStore(hdr, buf, sizeof(ETHhdr), 0);

    if ( (len = msgLength(msg)) > EMAXPAK ) {
	xTrace2(simethp, TR_SOFT_ERRORS,
		"sim ether driver: msgLength (%d) is larger than max (%d)",
		len, EMAXPAK);
	return XMSG_ERR_HANDLE;
    }
    /*
     * Place message contents in buffer
     */
    msgWalkInit(&mi, msg);
    while ((data = msgWalkNext(&mi, &dataLen)) != 0) {
      bcopy(data, bufPtr, dataLen);
      bufPtr += dataLen;
    }
    msgWalkDone(&mi);

    if ( ETH_ADS_EQUAL(hdr->dst, ethBcastHost) ) {
	writeBcast(ps, buffer, len);
    } else {
	sendOnSocket(ps->sock, &hdr->dst, buffer, len);
    }
    return XMSG_NULL_HANDLE;
}    


static void
internalDemux( InputBuffer *blockp )
{
    ETHhdr	hdr;
    VOID       *buf;
    Msg		*msg = &blockp->msg;
    
    xTrace0(simethp, TR_EVENTS, "in eth internal demux");
    
    buf = msgPop(msg, sizeof(ETHhdr));
    if (buf == NULL) {
	xTrace0(simethp, TR_SOFT_ERRORS,
		"eth_demux: incoming message too small!");
	return;
    }
    ethMsgLoad(&hdr, buf, sizeof(ETHhdr), 0);

    if ( ! blockp->self->up ) {
	xTrace0(simethp, TR_ERRORS, "eth_demux: no upper protocol!");
	return;
    }
    msgSetAttr(msg, 0, &hdr, sizeof(ETHhdr));
    xDemux(xGetUp(blockp->self), blockp->self, msg);
#ifdef SIMETH_STATS
    if ( --activeThreads > activeHighWater ) {
	activeHighWater = activeThreads;
    }
#endif
}



/* sparc version: Must pass each field of the msg structure by
   value.  Look at eth_demux to see where these are put back together.
*/

int interrupts = 0;
int checked = 0;

/* 
 * readether2demux -- interrupt handler
 *
 * Note that if only one packet is available, two blocks will be used
 * (the read into the second buffer will fail.)  With an even number
 * of blocks, this may result in every other block never transporting
 * a packet.  This is not a problem (i.e., they will be used if they
 * are needed.)
 */
static int
readether2demux( arg )
    VOID	*arg;
{
    Sessn	self = (Sessn)arg;
    int		buflen = 0;
    InputBuffer	*bp;
    PState	*ps = (PState *)self->state;
    
    xTrace0(simethp, TR_FUNCTIONAL_TRACE, "readether2demux");
    do {
	if ( ! (bp = xkBufferPoolNextBlock(simethBufferPool)) ) {
	    xError("sim_ether ERROR: Can't get next buffer, dropping incoming packet");
	    xIfTrace(simethp, TR_SOFT_ERRORS) {
		xkBufferPoolDump(simethBufferPool);
	    }
	    /* 
	     * Drop this packet
	     */
	    if (++ps->errorCount > MAX_ERROR_COUNT) {
		xAssert(0);
	    } else {
		read_ether(ps->sock, dummyBuf, EMAXPAK);
		return -1;
	    }
	} else if ((buflen = read_ether(ps->sock, bp->data, EMAXPAK)) != -1) {
	    msgTruncate(&bp->msg, buflen);
	    bp->self = (Sessn)arg;
#ifdef SIMETH_STATS
	    if ( ++activeThreads > activeHighWater ) {
		activeHighWater = activeThreads;
	    }
#endif
	    semSignal(&bp->sem);
	} else xkBufferPoolReleaseBlock(bp);
    } while (buflen != -1);
    
    return 0;
}


static XkReturn
simethOpenEnable( self, hlp, hlpType, p )
    Protl	self, hlp, hlpType;
    Part	*p;
{
    if ( self->up ) {
	xError("simethOpenEnable called multiple times!");
	return XK_FAILURE;
    }
    self->up = hlp;
    return XK_SUCCESS;
}


static int
simethControl( s, op, buf, len )
    Protl	s;
    int 	op, len;
    char 	*buf;
{
    PState	*ps = (PState *)s->state;

    switch (op) {

      case ETH_REGISTER_ARP:
	/* 
	 * ARP registers itself with us so we can ask it to perform
	 * callbacks in order to simulate hardware broadcast.  
	 */
	checkLen(len, sizeof(Protl));
	ps->arp = *(Protl *)buf;
	return 0;

      case ETH_DUMP_STATS:
#ifdef SIMETH_STATS	
	{
	    xTrace0(simethp, TR_ALWAYS, "SIMETH statistics:");
	    xTrace3(simethp, TR_ALWAYS,
		    "\tthreads: %d\tactive: %d\thigh-water: %d",
		    numShepherdThreads, activeThreads, activeHighWater);
	}
#endif

      case GETMAXPACKET:
      case GETOPTPACKET:
        checkLen(len, sizeof(int));
        *(int *) buf = ps->mtu;
        return (sizeof(ps->mtu));

      case GETMYHOST:
	xTrace1 (simethp, TR_EVENTS, "simeth_control: myhost: %s\n", 
		 ethHostStr ((ETHhost *)buf));
	checkLen(len, sizeof(ETHhost));
	bcopy((char *) &ps->myHost, buf, sizeof(ETHhost));
	return (sizeof(ETHhost));

      case SIM_SOCK2ADDR:
	{
	    SimAddrBuf *aBuf = (SimAddrBuf *)buf;

	    checkLen(len, sizeof(SimAddrBuf));
	    sock2simEth((ETHhost *)&aBuf->genericHost,
			aBuf->ipHost, aBuf->udpPort); 
	    return sizeof(SimAddrBuf);
	}

      default:
	return -1;

    }
}
    
