/* 
 * $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.5  1997/05/16 21:08:06  rrp
 * minor updates
 *
 * Revision 1.4  1997/02/24  21:05:04  rrp
 * added control option support for getting mtu
 *
 * Revision 1.3  1997/01/28  22:28:45  rrp
 * include of arpa/inet.h moved
 *
 * Revision 1.2  1996/05/31  22:52:13  rrp
 * Ported from x3.2.  This version now uses "standard" pthreads.
 *
 * Revision 1.1  1996/01/30  20:57:55  slm
 * Initial revision
 *
 * Revision 1.1  1995/07/29  03:01:06  slm
 * Initial revision
 *
 * 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 <unistd.h>
#include <netdb.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.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;

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(ETHhost ethAddr, struct sockaddr_in *sockAddr)
{
    memset(sockAddr, 0, 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));
}

static void
sock2simEth(ETHhost *ethAddr, IPhost inAddr, int udpPort)
{
    char *cp1, *cp2;
    short tmpshrt;
    int i;
    
    cp1 = (char *) ethAddr;
    cp2 = (char *) &inAddr;
    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(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(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 *) arpForEachFunc;
    afe.v = &m;
    xControlProtl(ps->arp, ARP_FOR_EACH, (char *) &afe, sizeof(ArpForEach));
}	


static void
sendOnSocket(int sock, ETHhost *dest, char *buf, int len)
{  
    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(Protl self, char **str, int nFields, int line, void *arg)
{
    xAssert(arg);
    return str2ipHost((IPhost *) arg, str[2]);
}


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


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


void
simeth_init(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);
    memset(ps, 0, 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 | O_NDELAY)) == -1)
    {
	Kabort("fcntl async");
    }
    if (fcntl(ps->sock, F_SETOWN, getpid()) == -1)
    {
	Kabort("fcntl setown");
    }
    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));
    memcpy(netHdr, hdr, sizeof(ETHhdr));
}


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


static XkHandle
simethPush(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)
    {
	memcpy(bufPtr, data, 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
}

/* 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(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
	     */
	    ps->errorCount++;
	    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
		activeThreads++;
		if (activeThreads > activeHighWater)
		    activeHighWater = activeThreads;
#endif
		semSignal(&(bp->sem));
	    }
	    else
		xkBufferPoolReleaseBlock(bp);
    }
    while (buflen != -1);
    xTrace0(simethp, TR_FUNCTIONAL_TRACE, "readether2demux exit");
    return 0;
}

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

static int
simethControl(Protl s, int op, char *buf, int len)
{
    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
	return 0;

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

      case GETMYHOST:
	checkLen(len, sizeof(ETHhost));
	memcpy(buf, &(ps->myHost), 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;

    }
}
    
