/* Copyright 1984 by the Massachusetts Institute of Technology */

/* This file implements Plummer's adderss resolution protocol. It does
 * the translation from internet addresses to ethernet addresses.
 */

#include	<types.h>
#include	"../../sys/include/sys.h"
#include	"../../sys/include/mosu.h"
#include	"../src/const.h"
#include	"../src/param.h"
#include	"../src/defs.h"
#include	"../src/macs.h"
#include	"../src/net.h"
#include	"../src/ext.h"
#include	"../in/in.h"
#include	"../in/inext.h"

#include	"ether.h"
#include	"ares.h"

static char arbdhrd[] =	"AR_FWD Bad hrd. field %x\n";
static char arbdpro[] =	"AR_FWD Bad protocol field %x\n";
static char arnosnd[] =	"AR_FWD Err in sending rply\n";
static char argpkt[] =	"AR_FWD opcode %x frm Eth hst %x,%x,%x,%x,%x,%x IN addr %d.%d.%d.%d\n";
static char arsrply[] =	"AR_FWD rply to Eth hst %x,%x,%x,%x,%x,%x\n";
static char arngb[] =	"AR_SEND Couln't get iorb for hst %d.%d.%d.%d\n";
static char arerrq[] =	"AR_SEND Err in sending pkt\n";
static char arsrq[] =	"AR_SEND Sent request for %d.%d.%d.%d\n";

ethent	ethintbl[ETHINSIZ];	/* EtherNet to internet table */
		

ext	byte	ETHBRD[];
ext	dct	*DCTVT[];

ethent	*get_entry();


/* Startup routine for the address resolution protocol. It just clears
 * the internet to EtherNet address table.
 */
AddRes()
{
	int	i;
	
	printf("Address resolution: init\n");
	for (i = 0; i < ETHINSIZ; i++)
		ethintbl[i].eent_flag = 0;
}


/* Recieves add res packets. It looks up the internet address in the
 * translation table. If it is there, the entry is updated. If it isn't, the
 * entry is added.
 */
res_in(iob)
reg	iorb	*iob;
{
	reg	respkt	*pkt;
	reg	ethent	*entry;
	int	i;
	net	*netp;
	inaddr	hisaddr;
	inaddr	*myaddr;
	byte	*myethaddr;

	netp = mknet(iob->i_usr2);
	pkt = mkrespkt(iob->i_addr);
	if (pkt->res_hrd != RHRD_ETHERNET) {
		niflog(L_ERRU)
			dolog(arbdhrd, pkt->res_hrd);
		freebuf(bufsiz, iob);
		return;
	}
	if (pkt->res_pro != ET_IN) {
		niflog(L_INFC)
			dolog(arbdpro, pkt->res_pro);
		freebuf(bufsiz, iob);
		return;
	}

	entry = get_entry(pkt->res_spa);
	if (entry->eent_flag == 0) {
		copy(pkt->res_spa, &entry->eent_in, sizeof(inaddr));
		entry->eent_flag = 1;
	}
	copy(pkt->res_sha, entry->eent_eth, 6);

	niflog(L_TRCO)
		dolog(argpkt, pkt->res_op, mkethadd((byte *)pkt->res_sha), mksina(mkina(pkt->res_spa)));
	myaddr = (inaddr *)iniatlst[netp->n_net]->inia_addr;
	if (myaddr->i_long != mkina(pkt->res_tpa)->i_long) {
		freebuf(bufsiz, iob);
		return;
	}
		
	if (pkt->res_op == ROP_REQUEST) {
		pkt->res_op = ROP_REPLY;
		copy(pkt->res_sha, pkt->res_tha, sizeof(inaddr) + 6);
		myethaddr = (byte *)((DCTVT[netp->n_idev])->d_dev1);
		copy(myethaddr, pkt->res_sha, 6);
		hisaddr.i_long = mkina(pkt->res_spa)->i_long;
		mkina(pkt->res_spa)->i_long = myaddr->i_long;
		iob->i_breq = sizeof(respkt);
		if ((*netp->n_send)(iob, netp, P_ARES, &hisaddr) != D_OK) {
			niflog(L_ERRU)
				dolog(arnosnd);
			freebuf(bufsiz, iob);
		}
		else
			niflog(L_TRCO)
				dolog(arsrply, mkethadd((byte *)pkt->res_tha));
		return;
	}
	freebuf(bufsiz, iob);
}


/* Routine which broadcasts an address request packet to find out the
 * EtherNet address which goes with the passed internet address.
 */

res_send(oiob)
iorb	*oiob;
{
	reg	iorb	*iob;
	reg	respkt	*pkt;
	reg	net	*netp;
		inaddr	*addr;

	netp = mknet(oiob->i_usr2);
	addr = mkina(oiob->i_usr6);
	freebuf(bufsiz, oiob);	/* sometime I should implement a scheme */
				/* to not throw the packet away yet */
	niflog(L_INFC)
		dolog("Sending add res pkt for IN addr %d.%d.%d.%d\n",
			mksina(addr));

	if ((iob = getbuf(bufsiz)) == NULL) {
		niflog(L_ERRU)
			dolog(arngb, mksina(addr));
		return;
	}
	iob->i_breq = sizeof(respkt);
	iob->i_addr = ((byte *) iob) + pktoff;
	pkt = mkrespkt(iob->i_addr);
	pkt->res_hrd = RHRD_ETHERNET;
	pkt->res_pro = ET_IN;
	pkt->res_hln = 6;
	pkt->res_pln = sizeof(inaddr);
	pkt->res_op = ROP_REQUEST;
	copy((DCTVT[netp->n_idev])->d_dev1, pkt->res_sha, 6);
	mkina(pkt->res_spa)->i_long =
		((inaddr *)(iniatlst[netp->n_net]->inia_addr))->i_long;
	mkina(pkt->res_tpa)->i_long = addr->i_long;
	if ((*netp->n_send)(iob, netp, P_ARES, ANYHOST) != D_OK){
		niflog(L_ERRU)
			dolog(arerrq);
		freebuf(bufsiz, iob);
	}
	else
		niflog(L_TRCO)
			dolog(arsrq, mksina(addr));
	return;
}


/* The internet address pointed to by nm is used to lookup the corresponding
 * EtherNet address which is stuck in memory at addr and 0 is returned.
 * If nm is ANYHOST, the EtherNet broadcast address is used. If there is no
 * entry for the internet address then an address resolution packet is
 * sent out and a 1 is returned.
 */

word	set_addr(nm, addr, iob)
reg	inaddr	*nm;
reg	unsb	*addr;
iorb		*iob;
{
	int	i;
	reg	byte	*res;
	ethent	*entry;

	if ((word)nm == ANYHOST)
		res = ETHBRD;
	else {
		entry = get_entry(nm);
		if (entry->eent_flag == 0) {
			entry->eent_in.i_long = (*nm).i_long;
			iob->i_usr6 = (word)&entry->eent_in;
			addtsk(syshnd, RESPRIO, res_send, iob);
			return(1);
		}
		res = (unsb *)entry->eent_eth;
	}

	for (i = 0; i < 6; i++)
		addr[i] = res[i];
	return(0);
}


/* Hashes the internet address and looks up its EtherNet address in
 * the table. It returns a pointer to the entry in the table.
 */
ethent	*get_entry(addr)
reg	inaddr	*addr;
{
    reg	ethent	*entry;
    ethent *best_entry;
    reg	int	i;
    
    i = ((addr->i_byte.i_bytel + addr->i_byte.i_bytem +
	  addr->i_byte.i_byteh + addr->i_byte.i_byteu) & 0377) %
	    ETHINSIZ;
    best_entry = entry = &ethintbl[i];
    
    for (i = 0; i < ETHINSIZ; i++) {
	if (addr->i_long == entry->eent_in.i_long ||
	    entry->eent_flag == 0)
	  return(entry);
	else 
	  if (++entry == &ethintbl[ETHINSIZ])
	    entry = &ethintbl[0];
    }

    /* If the hash table is full, then clear the best_entry for that
       internet address and use it. */
    best_entry->eent_flag = 0;
    return (best_entry);
}
