/*
 * Micro-X -- an X server for DOS
 * Copyright (C) 1994 StarNet Communications Corp.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * StarNet Communications Corp.
 * 550 Lakeside Dr. #3
 * Sunnyvale CA 94086 US
 * http://www.starnet.com
 * x-dos@starnet.com
 */
/*   
*     ARP
*/
#include "pcdefs.h"
#include "mbuf.h"
#include "protocol.h"
#include "config.h"
#include "funcdef.h"

static void _near _fastcall cacheupdate(u_char _near *, u_long);
#ifdef notdef
static void _near queuearp(u_char _near *, u_long, u_int);
#endif

extern struct conf Scon;
extern u_char etherall[DADDLEN];
extern u_char myether[DADDLEN];

static struct arp arpout;
struct acache arpc[NARPC]; /* cache for hardware addresses */

/************************************************************************
*
*   Address Resolution Protocol handling.  This can be looked at as
*   Ethernet-dependent, but the data structure can handle any ARP
*   hardware, with minor changes here.
*
*/
void
arpinit()
{
	(void) memcpy(arpout.d.me, myether, DADDLEN);
	arpout.d.type = EARP;
	arpout.hrd = htons(HTYPE);
	arpout.pro = EIP;
	arpout.hln = DADDLEN;
	arpout.pln = sizeof Scon.myip;
	(void) memcpy(arpout.sha, myether, DADDLEN);
	arpout.spa = Scon.myip;

	if (Scon.myip)
	    sendarp(etherall, Scon.myip, htons(ARPREP));
}

int _near
sendarp(thaptr, tipnum, op)
u_char _near *thaptr;
u_long	tipnum;
u_int	op;
{

	(void) memcpy(arpout.tha, thaptr, DADDLEN);
	if (op == htons(RARPQ)) {
	    arpout.d.type = ERARP;
	    (void) memcpy(arpout.d.dest, etherall, DADDLEN);
	} else
	    (void) memcpy(arpout.d.dest, thaptr, DADDLEN);
	arpout.tpa = tipnum;		/* requester's IP address */
	arpout.op = op;
	return pkxmit(&arpout, sizeof(arpout));
}

/*************************************************************************
* cacheupdate
*  We just received an ARP, or reply to ARP and need to add the information
*  to the cache.
*/
static void _near _fastcall
cacheupdate(hrdptr, ipn)
u_char	_near *hrdptr;
u_long	ipn;
{
	u_int	i;
	u_int	found;
	u_long	timer;

/*
* linear search to see if we already have this entry
*/
	timer = 0x7fffffffL;
	for (i = 1; i < NARPC; i++) {
		if (ipn == arpc[i].ip)
			break;
		if (arpc[i].tm < timer) {
			timer = arpc[i].tm;
			found = i;
		}
	}

/*
*  if that IP number is not already here, take the oldest entry.
*  If it is already here, update the info and reset the timer.
*  These were pre-initialized to 0, so if any are blank, they will be
*  taken first because they are faked to be oldest.
*/
	if (i == NARPC) {
		i = found;
		arpc[found].ip = ipn;
	}
	(void) memcpy(arpc[i].hrd, hrdptr, DADDLEN);
	arpc[i].tm = n_clicks();
}

/*************************************************************************
*  cachelook
*   look up information in the cache
*   returns the cache entry number for the IP number given.
*   Returns -1 on no valid entry, also if the entry present is too old.
*/
u_char _near * _near _fastcall
cachelook(ipn)
u_long	ipn;
{
	int	i;
	static u_long	arptime = 0L;

	for (i = 0; i < NARPC; i++) {
		if (ipn == arpc[i].ip && 
		    arpc[i].tm + CACHETO > n_clicks()) {
			return((u_char _near *)arpc[i].hrd);
		}
	}
/*
*  no valid entry, send an ARP
*/
	/* check time limit */
	if (n_clicks() >= arptime) {
		/* put out a broadcast request */
		(void) sendarp(etherall, ipn, htons(ARPREQ));
		arptime = n_clicks() + ARPTO;
	}
	return(NULL);
}

/************************************************************************
*  interpret ARP packets
*   Look at incoming ARP packet and make required assessment of usefulness,
*   check to see if we requested this packet, clear all appropriate flags.
*/
void _fastcall
arpinterpret(ptr, len)
union rawether *ptr;
u_int	len;
{

	if (Scon.myip && ptr->arp.spa == Scon.myip)
		return;
/*
*  check packet's desired IP address translation to see if it wants
*  me to answer.
*/
	switch (ntohs(ptr->arp.op)) {
	    case ARPREQ:
		if (!Scon.myip)
		    return;
		if (ptr->arp.tpa == Scon.myip) { 
			(void) cacheupdate(ptr->arp.sha, ptr->arp.spa);
			(void) sendarp(ptr->arp.sha, ptr->arp.spa, htons(ARPREP));
		}
		break;
/*
*  Check for a RARP reply.  If present, set my ip address
*/
	    case RARPR:
		if (Scon.myip)
		    return;	/* We already have our IP address. */
		if (!memcmp(ptr->arp.tha, myether, DADDLEN)) {
			/* copy in the dest ip address as my ip address */
			Scon.myip = ptr->arp.tpa;
			arpout.spa = ptr->arp.tpa;
			set_netmask();
#ifdef notdef
			blankip.i.ipsource = ptr->arp.tpa;
			ipout.i.ipsource = ptr->arp.tpa;
#endif
		}
		break;
/* 
*  Check for a reply that I probably asked for.
*/
	    case ARPREP:
		if (!Scon.myip)
		    return;
		if (ptr->arp.tpa == Scon.myip && ptr->arp.hrd == htons(HTYPE) &&
		    ptr->arp.hln == DADDLEN && ptr->arp.pln == 4 ) {
			(void) cacheupdate(ptr->arp.sha, ptr->arp.spa);
		}
		break;
#ifdef notdef
	    default:
dfputs("Unknown ARP op ");
printn(ptr->arp.op);
dfputs(".\r");
#endif
	}
}

u_char *
netdlayer(tipnum)
u_long	tipnum;
{
	u_char *cp;
	u_long	time;

	if (tipnum == 0 || tipnum == -1 || tipnum == Scon.broadcast)
		return etherall;

	time = n_clicks() + ARPWAIT;	/* one second time out */
	do {
		netsleep();
		cp = cachelook(tipnum);
		if (cp)
			return cp;
	} while (n_clicks() < time);
	return 0;
}
