/* Reverse Address Resolution Protocol (ARP) functions. Sits between IP and
 * Level 2, mapping Level 2 addresses to IP.
 */
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "timer.h"
#include "iface.h"
#include "socket.h"
#include "ax25.h"
#include "arp.h"
#include "netuser.h"
#include "cmdparse.h"
#include "pktdrvr.h"

struct arp_stat Rarp_stat;
static int Rwaiting = 0;	/* Semaphore used when waiting for a reply */
static void arp_output __ARGS((struct iface *iface,int16 hardware,char *hwaddr,char *target));
static int dorarpquery __ARGS((int argc,char *argv[],void *p));
static void rarpstat __ARGS((void));
static void rarp_output __ARGS((struct iface *iface,int16 hardware,char *hwaddr,char *target));

static struct cmds Rarpcmds[] = {
	"query", dorarpquery, 0, 3,
#ifdef fixed
	"rarp query <interface> <ether addr|callsign> [<ether addr|callsign>]",
#else
	"rarp query <interface> <callsign> [<callsign>]",
#endif
	NULLCHAR
};

int
dorarp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc < 2){
		rarpstat();
		return 0;
	}
	return subcmd(Rarpcmds,argc,argv,p);
}

static int
dorarpquery(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct iface *ifp;
	char hwaddr[MAXHWALEN], qhwaddr[MAXHWALEN];
	int16 hardware, t;
	static char *errmsg = "Illegal hardware address\n";
	if((ifp = if_lookup(argv[1])) == NULLIF){
	     tprintf("Interface %s unknown\n",argv[1]);
	     return -1;
	}
	switch(ifp->iftype->type) {
	case CL_AX25:
	     hardware = ARP_AX25;
	     break;
#ifdef fixed
	case CL_ETHERNET:
	     hardware = ARP_ETHER;
	     break;
	default:
	     tputs("Only Ethernet and AX25 interfaces allowed\n");
#else
	default:
	     tputs("Only AX25 interfaces allowed\n");
#endif
	     return -1;
	}
	if((*ifp->iftype->scan)(hwaddr,argv[2]) == -1) {
	     tputs(errmsg);
	     return -1;
	}
	if(argc > 3 && (*ifp->iftype->scan)(qhwaddr,argv[3]) == -1) {
	     tputs(errmsg);
	     return -1;
	}
	if(argc == 3)
	     memcpy(qhwaddr,hwaddr,ifp->iftype->hwalen);
	rarp_output(ifp,hardware,hwaddr,qhwaddr);

	t = Arp_type[hardware].pendtime;
	++Rwaiting;
	tprintf("Trying...   %2d",t);
	while(t--) {
	     alarm(1000L);
	     if(pwait(&Rwaiting) != EALARM) {
		  alarm(0);
		  --Rwaiting;
		  return 0;
	     }
	     tprintf("\b\b%2d",t);
	}
	tputs("\b\bTimeout.\n");
	--Rwaiting;
	return 0;
}

/* Handle incoming RARP packets according to RFC 903.
 */
void
rarp_input(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
	struct arp rarp;
	struct arp_type *at;
	char shwaddr[MAXHWALEN];
	
	Rarp_stat.recv++;
	if(ntoharp(&rarp,&bp) == -1)	/* Convert into host format */
		return;
	if(rarp.hardware >= NHWTYPES){
		/* Unknown hardware type, ignore */
		Rarp_stat.badtype++;
		return;
	}
	at = &Arp_type[rarp.hardware];
	if(rarp.protocol != at->iptype){
		/* Unsupported protocol type, ignore */
		Rarp_stat.badtype++;
		return;
	}
	if(uchar(rarp.hwalen) > MAXHWALEN || uchar(rarp.pralen) != sizeof(int32)){
		/* Incorrect protocol addr length (different hw addr lengths
		 * are OK since AX.25 addresses can be of variable length)
		 */
		Rarp_stat.badlen++;
		return;
	}
	if(memcmp(rarp.shwaddr,at->bdcst,at->hwalen) == 0){
		/* This guy is trying to say he's got the broadcast address! */
		Rarp_stat.badaddr++;
		return;
	}
	if(rarp.opcode == REVARP_REQUEST) {
		/* We are not a server, so we can only answer requests for a
		 * hardware address that is our own. But would be possible to
		 * use the ARP table to answer requests for someone elses
		 * IP address.
		 */
		if(memcmp(rarp.thwaddr,iface->hwaddr,at->hwalen) == 0) {
			memcpy(shwaddr,rarp.shwaddr,at->hwalen);
			/* Mark the end of the sender's AX.25 address
			 * in case he didn't
			 */
			if(rarp.hardware == ARP_AX25)
				rarp.thwaddr[uchar(rarp.hwalen)-1] |= E;
			memcpy(rarp.shwaddr,iface->hwaddr,at->hwalen);
			rarp.sprotaddr = iface->addr;
			rarp.tprotaddr = iface->addr;
			rarp.opcode = REVARP_REPLY;

			if((bp = htonarp(&rarp)) == NULLBUF)
				return;

			if(iface->forw != NULLIF)
			     (*iface->forw->output)(iface->forw,shwaddr,
					iface->forw->hwaddr,at->rarptype,bp);
			else
			     (*iface->output)(iface,shwaddr,
					iface->hwaddr,at->rarptype,bp);
			Rarp_stat.inreq++;
		}
	} else {
		Rarp_stat.replies++;
		if(Rwaiting) {
		     psignal(&Rwaiting,1);
		     tprintf("\nRARP Reply: %s %s\n",
			     (*at->format)(shwaddr,rarp.thwaddr),
			     inet_ntoa(rarp.tprotaddr));
		}
	}
}

/* Send a RARP request to target to resolve hardware address hwaddr */
static void
rarp_output(iface,hardware,hwaddr,target)
struct iface *iface;
int16 hardware;
char *hwaddr;
char *target;
{
	struct arp rarp;
	struct mbuf *bp;
	struct arp_type *at;

	at = &Arp_type[hardware];
	if(iface->output == NULLFP)
		return;
	
	rarp.hardware = hardware;
	rarp.protocol = at->iptype;
	rarp.hwalen = at->hwalen;
	rarp.pralen = sizeof(int32);
	rarp.opcode = REVARP_REQUEST;
	memcpy(rarp.shwaddr,iface->hwaddr,at->hwalen);
	rarp.sprotaddr = 0;
	memcpy(rarp.thwaddr,hwaddr,at->hwalen);
	rarp.tprotaddr = 0;
	if((bp = htonarp(&rarp)) == NULLBUF)
		return;
	(*iface->output)(iface,target,
		iface->hwaddr,at->rarptype,bp);
	Rarp_stat.outreq++;
}

static void
rarpstat()
{
	tprintf("received %u badtype %u bogus addr %u reqst in %u replies %u reqst out %u\n",
	 Rarp_stat.recv,Rarp_stat.badtype,Rarp_stat.badaddr,Rarp_stat.inreq,
	 Rarp_stat.replies,Rarp_stat.outreq);
}
