/*
 *	Inet utility function. They deal with creating, destroing,
 *	putting into, removing from and looking up inet socket datas.
 *
 *	At the momenet all datas belonging to one proto are kept in
 *	a list. This slows down searching for a special one, perhaps
 *	we should use some hashing scheme ?
 *
 *	01/21/94, kay roemer.
 */

#include "config.h"
#include "file.h"
#include "kerbind.h"
#include "atarierr.h"
#include "sockerr.h"
#include "socket.h"
#include "net.h"
#include "inet.h"
#include "in.h"
#include "port.h"
#include "buf.h"
#include "ip.h"
#include "if.h"
#include "util.h"

extern void	*memset (void *, int, unsigned long);

struct in_proto *allinetprotos = 0;

void
in_proto_register (protonum, proto)
	short protonum;
	struct in_proto *proto;
{
	proto->next = allinetprotos;
	allinetprotos = proto;
	ip_register (&proto->ipops);
}

struct in_proto *
in_proto_lookup (protocol)
	short protocol;
{
	struct in_proto *p;

	for (p = allinetprotos; p; p = p->next) {
		if (p->proto == protocol)
			return p;
	}
	return 0;
}

struct in_data *
in_data_create (void)
{
	struct in_data *data;

	data = kmalloc (sizeof (struct in_data));
	if (!data) {
		DEBUG (("in_data_create: Out of mem"));
		return 0;
	}
	data->protonum = 0;
	data->proto = 0;
	data->sock = 0;
	data->next = 0;
	data->pcb = 0;

	memset (&data->opts, 0, sizeof (data->opts));
	data->opts.ttl = IP_DEFAULT_TTL;
	data->opts.tos = IP_DEFAULT_TOS;

	data->snd.maxdatalen = IN_DEFAULT_WSPACE;
	data->snd.curdatalen = 0;
	data->snd.lowat = 0;
	data->snd.hiwat = 0;
	data->snd.flags = 0;
	data->snd.qfirst = 0;
	data->snd.qlast = 0;

	data->rcv.maxdatalen = IN_DEFAULT_RSPACE;
	data->rcv.curdatalen = 0;
	data->rcv.lowat = 0;
	data->rcv.hiwat = 0;
	data->rcv.flags = 0;
	data->rcv.qfirst = 0;
	data->rcv.qlast = 0;

	data->src.port = 0;
	data->src.addr = 0;

	data->dst.port = 0;
	data->dst.addr = 0;

	data->flags = IN_BROADCAST;
	data->backlog = 0;
	data->linger = 0;
	data->err = 0;

	return data;
}

long
in_data_destroy (data, wait)
	struct in_data *data;
	short wait;
{
	short proto;

	if (!data) {
		DEBUG (("in_data_destroy: data == NULL"));
		return 0;
	}
	proto = data->protonum;
	if (wait && data->flags & IN_LINGER) {
		while (data->snd.curdatalen > 0) {
			if (isleep (IO_Q, (long)data->sock)) {
				DEBUG (("in_data_destroy: interrupted."));
				if (!in_data_find (proto, data))
					return EINTR;
			}
		}
	}
	if (data->proto) {
		in_data_remove (data);
	}
	in_data_flush (data);
	kfree (data);
	return 0;
}

void
in_data_flush (data)
	struct in_data *data;
{
	BUF *buf, *nextbuf;

	for (buf = data->rcv.qfirst; buf; buf = nextbuf) {
		nextbuf = buf->next;
		buf_deref (buf, BUF_NORMAL);
	}
	data->rcv.qfirst = data->rcv.qlast = 0;
	data->rcv.curdatalen = 0;
	
	for (buf = data->snd.qfirst; buf; buf = nextbuf) {
		nextbuf = buf->next;
		buf_deref (buf, BUF_NORMAL);
	}
	data->snd.qfirst = data->snd.qlast = 0;
	data->snd.curdatalen = 0;
}

void
in_data_put (data)
	struct in_data *data;
{
	data->next = data->proto->datas;
	data->proto->datas = data;
}

void
in_data_remove (data)
	struct in_data *data;
{
	struct in_data *d = data->proto->datas;

	if (d == data) {
		data->proto->datas = data->next;
	} else {
		while (d && d->next != data)
			d = d->next;

		if (d) d->next = data->next;
		else {
			DEBUG (("in_remove: Data not found"));
		}
	}
	data->next = 0;
}

short
in_data_find (proto, data)
	short proto;
	struct in_data *data;
{
	struct in_proto *p = in_proto_lookup (proto);
	struct in_data *d;

	if (p == 0) return 0;
	for (d = p->datas; d; d = d->next) {
		if (data == d) return 1;
	}
	return 0;
}

struct in_data *
in_data_lookup (datas, srcaddr, srcport, dstaddr, dstport)
	struct in_data *datas;
	unsigned long srcaddr, dstaddr;
	unsigned short srcport, dstport;
{
	struct in_data *deflt, *dead, *d = datas;

	for (dead = deflt = 0; d; d = d->next) {
		if (d->flags & IN_ISBOUND && d->src.port == dstport &&
		    ip_same_addr (dstaddr, d->src.addr)) {
			if (d->flags & IN_ISCONNECTED) {
				if (d->dst.port == srcport &&
				    ip_same_addr (srcaddr, d->dst.addr)) {
					if (!(d->flags & IN_DEAD))
				    		break;
					dead = d;
				}
			} else	deflt = d;
		}
	}
	return (d ? d : (deflt ? deflt : dead));
}

/* Incrementally search the next matching data */
struct in_data *
in_data_lookup_next (datas, srcaddr, srcport, dstaddr, dstport, wildcard)
	struct in_data *datas;
	unsigned long srcaddr, dstaddr;
	unsigned short srcport, dstport;
	short wildcard;
{
	struct in_data *d;

	for (d = datas; d; d = d->next) {
		if (d->flags & IN_ISBOUND && d->src.port == dstport &&
		    ip_same_addr (dstaddr, d->src.addr)) {
			if (d->flags & IN_ISCONNECTED) {
				if (d->dst.port == srcport &&
				    ip_same_addr (srcaddr, d->dst.addr)) {
					break;
				}
			} else if (wildcard)
				break;
		}
	}
	return d;
}

/* Generic checksum computation routine.
 * Returns the one's complement of the 16 bit one's complement sum over
 * the `nwords' words starting at `buf'.
 */
short
chksum (buf, nwords)
	void *buf;
	short nwords;
{
	unsigned long sum = 0;

	__asm__("clrl	d0		\n\t"
		"movew	%2, d1		\n\t"
		"lsrw	#1, d1		\n\t"	/* # of longs in buf */
		"bcc	l1		\n\t"	/* multiple of 4 ? */
		"addw	%1@+, %0	\n\t"	/* no, add in extra word */
		"addxw	d0, %0		\n"
		"l1:			\n\t"
		"subqw	#1, d1		\n\t"	/* decrement for dbeq */
		"bmi	l3		\n"
		"l2:			\n\t"
		"addl	%1@+, %0	\n\t"
		"addxl	d0, %0		\n\t"
		"dbra	d1, l2		\n"	/* loop over all longs */
		"l3:			\n\t"
		"movel	%0, d1		\n\t"	/* convert to short */
		"swap	d1		\n\t"
		"addw	d1, %0		\n\t"
		"addxw	d0, %0		\n\t"
		: "=d"(sum), "=a"(buf)
		: "g"(nwords), "1"(buf), "0"(sum)
		: "d0", "d1");

	return (short)(~sum & 0xffff);
}

void
sa_copy (sa1, sa2)
	struct sockaddr *sa1, *sa2;
{
	short todo;
	extern void *memcpy (void *, const void *, unsigned long);

	switch (sa2->sa_family) {
	case AF_INET:
		todo = sizeof (struct sockaddr_in);
		break;
	default:	
		todo = sizeof (struct sockaddr);
	}
	memcpy (sa1, sa2, todo);
}
