/* $Header: nip.c,v 1.7 89/05/11 14:27:15 mar Exp $ */

#include <types.h>
#include <sys.h>
#include "ether.h"

extern int cksum();

/* flags for the nipinfo structure: */
#define NI_NETADDR	0x01
#define NI_NETMASK	0x02
#define NI_BROADCAST	0x04
#define NI_LOWEST	0x08
#define NI_HIGHEST	0x10
#define NI_RECOMMEND	0x20
#define NI_GATEWAY	0x40

/* actual packet format */
struct nip_pkt {
	struct et_hdr	np_et;	/* with 2 bytes of pad at the front */
	byte		np_source[6];
	unss		np_checksum;
	unss		np_opcode;
	unss		np_version;
	unsl		np_netaddress;
	unsl		np_netmask;
	unsl		np_broadcast;
	unsl		np_lowest;
	unsl		np_highest;
	unsl		np_recommend;
	unsl		np_gateway;
};


/* Opcodes */


/* ethernet type code for NIP */
#ifdef BIG_ENDIAN
#define ETHERTYPE_NIP	0x0801
#define NIP_REQUEST	1
#define NIP_RESPONSE	2

#define NIP_VERSION	1
#else
#define ETHERTYPE_NIP	0x0108
#define NIP_REQUEST	0x100
#define NIP_RESPONSE	0x200

#define NIP_VERSION	0x100
#endif

static struct nip_pkt pkt;
static char in_buf[2+2048];		/* 2048 byte buffer (2 byte padding) */


struct nip_pkt *nip_get(addr)
unsl addr;
{
	register int i, j, len;
	register struct nip_pkt *ipkt = (struct nip_pkt *)in_buf;

	bzero(&pkt, sizeof(pkt));
	etadcpy(etmyaddr, pkt.np_source);
	etadcpy(etmyaddr, pkt.np_et.et_src);
	etadcpy(etbroad, pkt.np_et.et_dst);
#ifdef DEBUG
	printf("NIP\n");
#endif
	
	pkt.np_et.et_type = ETHERTYPE_NIP;
	pkt.np_opcode = NIP_REQUEST;
	pkt.np_version = NIP_VERSION;
	
	pkt.np_checksum = (unsigned short)~cksum(pkt.np_source, (sizeof (struct nip_pkt) - sizeof (struct et_hdr))>>1, 0);

	for (j = 0; j < 5; j++) {
		et_write (pkt.np_et.et_dst, sizeof(pkt)-2);
		for (i=0; i < 1000; i++) {
			if ((len = et_read (in_buf+2, 2048)) >= (sizeof (pkt) - 2)) {
				if ((ipkt->np_et.et_type != ETHERTYPE_NIP) ||
				    (ipkt->np_version != NIP_VERSION) ||
				    (ipkt->np_opcode != NIP_RESPONSE))
					continue;

				if (ipkt->np_checksum &&
				    (unss)~cksum(&ipkt->np_source[0], (sizeof (struct nip_pkt) - sizeof (struct et_hdr))>>1, 0)) {
					continue;
				}
				goto got_reply;
			}
		}
	}
#ifdef ATHENA_INSTALL
	printf("Unable to talk to any other machines, is the ethernet connected?\n");
#else
	printf("Timeout\n");
#endif
	return(0);

got_reply:

	printf ("\nnetaddress "); print_ip_addr(ipkt->np_netaddress);
	printf ("\nnetmask "); print_ip_addr(ipkt->np_netmask);
	printf ("\nbroadcast "); print_ip_addr(ipkt->np_broadcast);
	printf ("\ngateway "); print_ip_addr(ipkt->np_gateway);
	printf ("\nlowest "); print_ip_addr(ipkt->np_lowest);
	printf ("\nhighest "); print_ip_addr(ipkt->np_highest);
	if (ipkt->np_recommend) {
	    printf ("\nrecommend "); print_ip_addr(ipkt->np_recommend);
	}
	printf ("\n");

	return (ipkt);
}

char eth[6];

nip_verify (address, defend)
	unsl address;
	int defend;
{
	struct adr adr;
	struct adr *padr2 = (struct adr *)in_buf;
	int i, j, cc;
	
#ifdef DEBUG
	printf("verifying address "); print_ip_addr(address); printf("\n");
#endif
	bzero (&adr, sizeof(adr));
	etadcpy (etbroad, adr.ar_et.et_dst);
	etadcpy (etmyaddr, adr.ar_et.et_src);
	adr.ar_et.et_type = ETHER_ARP;
	adr.ar_hd = ETHER_HTYPE;
	adr.ar_pro = ETHER_IP;
	adr.ar_hln = ETHER_HLEN;
	adr.ar_pln = 4;
	adr.ar_op = ARP_REQ;
	etadcpy(etmyaddr, adr.ar_sha);
	ipadcpy (&address, adr.ar_tpa);
	if (defend)
		ipadcpy (&address, adr.ar_spa);
	
	for (i=0 ; i < 5; i++) {
		et_write (&adr.ar_et.et_dst[0], sizeof (struct adr)-2);
		for (j=0; j < 500; j++) {
			/*
			 * We need to be able to answer our own ARP
			 * requests, so we use net_read() after we
			 * initialize our IP address.
			 */
			if (defend)
				cc = net_read (&padr2->ar_et.et_dst[0],
					       sizeof(in_buf)-2);
			else
				cc = et_read (&padr2->ar_et.et_dst[0],
					      sizeof(in_buf)-2);
			if (cc = 0) continue;
			if (padr2->ar_et.et_type != ETHER_ARP) continue;
			if (padr2->ar_op != ARP_REP) continue;
			if (bcmp(padr2->ar_spa, &address, 4)) continue;
			print_ip_addr(address); printf (" not verified\n");
			return 0;
		}
	}
#ifdef DEBUG
	printf("address not in use.\n");
#endif
	return 1;
}

nip_set_address(addr)
long addr;
{
	register struct nip_pkt *np;
	long address, low, high;
	long count = 0;
	extern long gateway;
	
	np = nip_get(addr);
	if (np == 0 && addr) {
	    printf("No, NIP response, ");
	    address = addr;

	    /* Set network information based on the specified address */
	    gateway = net_mask = 0;
	    switch((swabl(addr) & 0xE0000000)) {
	    case 0xE0000000:
		printf("Address ");
		print_ip_addr(addr);
		printf(" is not a class A, B, or C network address\n");
		break;
		
		/* Class C networks */
	    case 0xC0000000:
		net_mask = 0xFFFFFF00;
		break;
		
		/* Class B networks */
	    case 0xA0000000:
	    case 0x80000000:
		net_mask = 0xFFFF0000;
		break;
		
		/* Class A networks */
	    default:
		switch ((unsb)(swabl(addr)>>24)) {
		case 18:
		    net_mask = 0xFFFF0000;
		    break;
		default:
		    net_mask = 0xFF000000;
		    break;
		}
		break;
	    }
	    goto gotit;
	}
	if (np == 0) {
	    printf("Unable to find an address to use.\n");
	    return(1);
	}
	low = np->np_lowest;
	high = np->np_highest;
	net_mask = np->np_netmask;
	if (np->np_gateway)
	    gateway = np->np_gateway;

	if (addr) {
	    address = addr;
	    if (addr & np->np_netmask != np->np_netaddress) {
		printf("Address ");
		print_ip_addr(addr);
		printf(" is not on this network!\n");
	    } else if (nip_verify(address, 0))
		goto gotit;
	}
	if (np->np_recommend) {
	    address = np->np_recommend;
	    if (nip_verify (address, 0)) goto gotit;
	}
lostit:
	low = swabl(low);
	high = swabl(high);
	for (; count < high - low; count++) {
		address = hash (etmyaddr, low, high, count);
		if (nip_verify (address, 0)) goto gotit;
	}
	printf("Unable to find an address to use.\n");
	return(1);
	
gotit:
	printf("IP address is "); print_ip_addr(address); printf("\n");
	in_me = address;
	if (!nip_verify (address, 1)) {
		printf ("Somebody else did, too\n");
		in_me = 0;
		goto lostit;
	}
#ifdef DEBUG
	printf("On the air.\n");
#endif
	return(0);
}

hash(pa, low, high, x)
	byte	*pa;
	long low, high;
	int	x;
{
	unsl tmp;
	/*
	 * Hash based on the manufacturer-assigned
	 * half of the ethernet address.
	 */
	tmp = pa[3] + x * pa[4] + pa[5];
	if (pa[4] == 0)
		tmp += x;
	tmp %= high - low;
	return swabl(low + tmp);
}
