/*
 * This source file is Copyright 1995 by Evan Scott.
 * All rights reserved.
 * Permission is granted to distribute this file provided no
 * fees beyond distribution costs are levied.
 */

/*
 * a message passing API for amitcp
 */

#include <exec/types.h>
#include <exec/ports.h>

#include <dos/dos.h>
#include <dos/dostags.h>

#include <proto/exec.h>
#include <proto/dos.h>

#define INLINES_AS_MACROS 1	/* SAS doesn't seem to do inlines properly */
#include <proto/socket.h>

/* these particularly need to be the amitcp includes */
#include <sys/errno.h>
#include <sys/ioctl.h>

#include <netdb.h>
#include <string.h>

#include "evtypes.h"
#include "verify.h"

#include "tcp.h"

tcpmessage *new_tcpmessage(struct MsgPort *reply_port)
{
	tcpmessage *z;
	
	z = (tcpmessage *)allocate(sizeof(*z), V_tcpmessage);
	if (!z) return nil;
	
	ensure(z, V_tcpmessage);
	
	z->header.mn_Node.ln_Type = NT_MESSAGE;
	z->header.mn_Node.ln_Pri = 0;
	z->header.mn_Node.ln_Name = "TCPMessage";
	z->header.mn_ReplyPort = reply_port;
	z->header.mn_Length = sizeof(*z);
	
	z->command = TCP_NOOP;
	z->ident = nil;
	z->address.l = 0;
	z->port.w = 0;
	
	z->data = nil;
	z->interrupt = nil;
	z->length = 0;
	z->result = 0;
	z->error = NO_ERROR;
	z->flags = 0;
	
	return z;
}

void free_tcpmessage(tcpmessage *tm)
{
	verify(tm, V_tcpmessage);
	
	ensure(tm, 0);
	
	deallocate(tm, V_tcpmessage);
	
	return;
}

tcpident *new_tcpident(sb32 fd)
{
	tcpident *ti;

	ti = (tcpident *)allocate(sizeof(*ti), V_tcpident);
	if (!ti) return nil;

	ensure(ti, V_tcpident);

	ti->fd = fd;
	ti->eof = false;

	return ti;
}

void free_tcpident(tcpident *ti)
{
	verify(ti, V_tcpident);
	
	ensure(ti, 0);

	deallocate(ti, V_tcpident);

	return;
}

void fix_read_set(struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
	tcpmessage *tm;
	tcpident *ti;
	
	FD_ZERO(reads);
	*max_fd = -1;
	
	for (tm = (tcpmessage *)wait_list->lh_Head;
			tm->header.mn_Node.ln_Succ;
			tm = (tcpmessage *)tm->header.mn_Node.ln_Succ) {
		ti = tm->ident;
		verify(ti, V_tcpident);
		
		if (ti->fd > *max_fd) *max_fd = ti->fd;
		
		if (tm->command == TCP_LISTEN || tm->command == TCP_READ) {
			FD_SET(ti->fd, reads);
			break;
		}
	}
}

void fix_write_set(struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
	tcpmessage *tm;
	tcpident *ti;
	
	FD_ZERO(writes);
	*max_fd = -1;
	
	for (tm = (tcpmessage *)wait_list->lh_Head;
			tm->header.mn_Node.ln_Succ;
			tm = (tcpmessage *)tm->header.mn_Node.ln_Succ) {
		ti = tm->ident;
		verify(ti, V_tcpident);
		
		if (ti->fd > *max_fd) *max_fd = ti->fd;
		
		if (tm->command == TCP_WRITE) {
			FD_SET(ti->fd, writes);
			break;
		}
	}
}

void non_blocking(struct Library *SocketBase, sb32 fd)
{
	long one;
	
	one = 1;
	
	IoctlSocket(fd, FIONBIO, (void *)&one);
}

void unique_name(void *tp, b8 *s, b8 *buffer)
{
	b32 task;
	
	task = (b32)tp;

	buffer[0] = (task >> 28) & 0xf;
	buffer[1] = (task >> 24) & 0xf;
	buffer[2] = (task >> 20) & 0xf;
	buffer[3] = (task >> 16) & 0xf;
	buffer[4] = (task >> 12) & 0xf;
	buffer[5] = (task >> 8) & 0xf;
	buffer[6] = (task >> 4) & 0xf;
	buffer[7] = task & 0xf;
	
	if (buffer[0] > 9) buffer[0] += 'a' - 10; else buffer[0] += '0';
	if (buffer[1] > 9) buffer[1] += 'a' - 10; else buffer[1] += '0';
	if (buffer[2] > 9) buffer[2] += 'a' - 10; else buffer[2] += '0';
	if (buffer[3] > 9) buffer[3] += 'a' - 10; else buffer[3] += '0';
	if (buffer[4] > 9) buffer[4] += 'a' - 10; else buffer[4] += '0';
	if (buffer[5] > 9) buffer[5] += 'a' - 10; else buffer[5] += '0';
	if (buffer[6] > 9) buffer[6] += 'a' - 10; else buffer[6] += '0';
	if (buffer[7] > 9) buffer[7] += 'a' - 10; else buffer[7] += '0';
	
	strcpy(&buffer[8], s);
}

void tcp_read(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
	tcpident *ti;
	sb32 result;
	b8 *s;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(reads != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	ti = tm->ident;

	if (!ti) {
		tm->result = 0;
		tm->error = ERROR_NO_CONNECTION;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	verify(ti, V_tcpident);
	
	if (tm->length == 0) {
		tm->result = 0;
		
		if (ti->eof)
			tm->error = ERROR_EOF;
		else
			tm->error = NO_ERROR;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	/* socket has got to be set to non-blocking */
	
	if (tm->flags & FLAG_READLINE) {
		s = tm->data;
		tm->result = 0;
		
		while (1) {
			result = recv(ti->fd, s, 1, 0);
			if (result == 1) {
				tm->result++;
				if (*s == '\r' || *s == '\n') {
					if (tm->result == 1) {	/* blank line ... skip it */
						tm->result--;
						continue;
					}
					tm->error = NO_ERROR;
					
					ReplyMsg(&tm->header);
					return;
				}
				s++;
				if (tm->result == tm->length) {
					tm->error = NO_ERROR;
					ReplyMsg(&tm->header);
					return;
				}
				continue;
			} else if (result == 0) {	/* got to be EOF */
				ti->eof = true;
				tm->error = ERROR_EOF;
				
				ReplyMsg(&tm->header);
				return;
			} else {
				switch (Errno()) {
				case EINTR:
				case EWOULDBLOCK:	/* nothing there to read yet */
					AddTail(wait_list, (struct Node *)tm);
					FD_SET(ti->fd, reads);
					if (ti->fd > *max_fd) *max_fd = ti->fd;
					return;
				default:		/* something went wrong */
					tm->error = ERROR_LOST_CONNECTION;
			
					ReplyMsg(&tm->header);
				}
				return;
			}
		}
	} else {
		result = recv(ti->fd, tm->data, tm->length, 0);
		if (result == tm->length) {	/* satisfied immediately */
			tm->result = tm->length;
			tm->error = NO_ERROR;
			
			ReplyMsg(&tm->header);
			return;
		}
	}
	
	if (result == 0) {	/* got to be EOF */
		ti->eof = true;
		tm->result = 0;
		tm->error = ERROR_EOF;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	/* from here we have the stuff we have to wait for */
		
	if (result < 0) {
		switch (Errno()) {
		case EINTR:
		case EWOULDBLOCK:	/* nothing there to read yet */
			tm->result = 0;
			AddTail(wait_list, (struct Node *)tm);
			FD_SET(ti->fd, reads);
			if (ti->fd > *max_fd) *max_fd = ti->fd;
			return;
		default:		/* something went wrong */
			tm->result = 0;
			tm->error = ERROR_LOST_CONNECTION;
			
			ReplyMsg(&tm->header);
			return;
		}
	}
	
	truth(result < tm->length);
	
	tm->result = result;
	AddTail(wait_list, (struct Node *)tm);
	FD_SET(ti->fd, reads);
	if (ti->fd > *max_fd) *max_fd = ti->fd;
	return;
}

void tcp_write(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
	tcpident *ti;
	sb32 result;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(writes != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	ti = tm->ident;

	if (!ti) {
		tm->result = 0;
		tm->error = ERROR_NO_CONNECTION;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	verify(ti, V_tcpident);
	
	if (tm->length == 0) {
		tm->result = 0;
		tm->error = NO_ERROR;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	/* socket has got to be set to non-blocking */

	result = send(ti->fd, tm->data, tm->length, 0);
	if (result == tm->length) {	/* satisfied immediately */
		tm->result = tm->length;
		tm->error = NO_ERROR;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	/* from here we have the stuff we have to wait for */
		
	if (result < 0) {
		switch (Errno()) {
		case EWOULDBLOCK:
		case EINTR:		/* write couldn't get through immediately */
			tm->result = 0;
			AddTail(wait_list, (struct Node *)tm);
			FD_SET(ti->fd, writes);
			if (ti->fd > *max_fd) *max_fd = ti->fd;
			return;
		default:		/* something has gone wrong */
			tm->result = 0;
			tm->error = ERROR_LOST_CONNECTION;
			ReplyMsg(&tm->header);
			return;
		}
	}
	
	truth(result < tm->length);
	
	tm->result = result;
	AddTail(wait_list, (struct Node *)tm);
	FD_SET(ti->fd, writes);
	if (ti->fd > *max_fd) *max_fd = ti->fd;
	return;
}

void tcp_read_more(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
	tcpident *ti;
	sb32 result;
	b8 *s;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(reads != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	ti = tm->ident;
	verify(ti, V_tcpident);
	
	if (tm->flags & FLAG_READLINE) {
		s = (b8 *)tm->data + tm->result;
		
		while (1) {
			result = recv(ti->fd, s, 1, 0);
			if (result == 1) {
				tm->result++;
				if (*s == '\r' || *s == '\n') {
					if (tm->result == 1) {	/* blank line ... skip it */
						tm->result--;
						continue;
					}
					tm->error = NO_ERROR;
					
					Remove((struct Node *)tm);
					ReplyMsg(&tm->header);
					
					fix_read_set(wait_list, reads, max_fd);
					return;
				}
				s++;
				if (tm->result == tm->length) {
					tm->error = NO_ERROR;
					
					Remove((struct Node *)tm);
					ReplyMsg(&tm->header);

					fix_read_set(wait_list, reads, max_fd);
					return;
				}
				continue;
			} else if (result == 0) {	/* got to be EOF */
				ti->eof = true;
				tm->error = ERROR_EOF;
				
				Remove((struct Node *)tm);
				ReplyMsg(&tm->header);

				fix_read_set(wait_list, reads, max_fd);
				return;
			} else {
				switch (Errno()) {
				case EINTR:
				case EWOULDBLOCK:	/* nothing there to read yet */
					return;
				default:		/* something went wrong */
					tm->error = ERROR_LOST_CONNECTION;
					
					Remove((struct Node *)tm);
					ReplyMsg(&tm->header);

					fix_read_set(wait_list, reads, max_fd);
					return;
				}
				return;
			}
		}
	} else {
		result = recv(ti->fd, (b8 *)tm->data + tm->result, tm->length - tm->result, 0);
		if (result == tm->length - tm->result) {	/* satisfied! */
			tm->result = tm->length;
			tm->error = NO_ERROR;
		
			Remove((struct Node *)tm);	/* remove from wait_list */
			ReplyMsg(&tm->header);		/* send it back completed */
		
			fix_read_set(wait_list, reads, max_fd);

			return;
		}
	
		if (result == 0) {	/* got to be EOF */
			ti->eof = true;
			tm->error = ERROR_EOF;
		
			Remove((struct Node *)tm);	/* as above */
			ReplyMsg(&tm->header);
		
			fix_read_set(wait_list, reads, max_fd);

			return;
		}
	
		/* have to wait some more */
		
		if (result < 0) {
			switch (Errno()) {
			case EINTR:
			case EWOULDBLOCK:	/* nothing more to read yet */
				return;
			default:		/* something went wrong */
				tm->error = ERROR_LOST_CONNECTION;
			
				Remove((struct Node *)tm);
				ReplyMsg(&tm->header);
			
				fix_read_set(wait_list, reads, max_fd);

				return;
			}
		}
	
		truth(result < tm->length - tm->result);
	
		tm->result += result;
		return;				/* keep waiting */
	}
}

void tcp_write_more(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
	tcpident *ti;
	sb32 result;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(writes != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	ti = tm->ident;
	verify(ti, V_tcpident);
	
	result = send(ti->fd, (b8 *)tm->data + tm->result, tm->length - tm->result, 0);
	if (result == tm->length - tm->result) {	/* satisfied! */
		tm->result = tm->length;
		tm->error = NO_ERROR;
		
		Remove((struct Node *)tm);
		ReplyMsg(&tm->header);
		
		fix_write_set(wait_list, writes, max_fd);

		return;
	}
	
	/* from here we have the stuff we have to wait some more for */
		
	if (result < 0) {
		switch (Errno()) {
		case EWOULDBLOCK:
		case EINTR:		/* write blocked again */
			return;
		default:		/* something has gone wrong */
			tm->error = ERROR_LOST_CONNECTION;
			
			Remove((struct Node *)tm);
			ReplyMsg(&tm->header);
			
			fix_write_set(wait_list, writes, max_fd);

			return;
		}
	}
	
	truth(result < tm->length - tm->result);
	
	tm->result += result;
	return;
}

void tcp_listen(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
	tcpident *ti;
	sb32 result, socklen;
	struct sockaddr_in sin;
	struct hostent *he;
	tcpmessage *wait_tm;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(reads != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	if (tm->ident) {
		tm->result = false;
		tm->error = ERROR_ALREADY_CONNECTED;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	memset(&sin, 0, sizeof(sin));
	
	he = gethostbyname("localhost");
	if (!he) {
		tm->result = false;
		tm->error = ERROR_UNKNOWN_HOST;
		
		ReplyMsg(&tm->header);
		return;
	}

	sin.sin_family = he->h_addrtype;
	sin.sin_port = tm->port.w;
	memcpy(&sin.sin_addr.s_addr, he->h_addr_list[0], he->h_length);

	ti = new_tcpident(0);	/* filled in later */
	if (ti) {
		wait_tm = new_tcpmessage(nil);
		if (wait_tm) {
			result = socket(AF_INET, SOCK_STREAM, 0);
			if (result >= 0) {
				ti->fd = result;
			
				result = bind(ti->fd, (struct sockaddr *)&sin, sizeof(sin));
				if (result == 0) {
					result = listen(ti->fd, 5);	/* random msq queue length */
					if (result == 0) {
						/*
						 * strictly, we shouldn't need this 
						 * ... but, we might (better safe etc)
						 */
						non_blocking(SocketBase, ti->fd);
						
						socklen = sizeof(sin);
						result = getsockname(ti->fd, (struct sockaddr *)&sin, &socklen);
						
						/* we are listening! */
						tm->ident = ti;
						tm->port.w = sin.sin_port;
						
						wait_tm->ident = ti;
						
						wait_tm->command = TCP_LISTEN;
						wait_tm->port.w = sin.sin_port;	/* just for curiosity */
						
						/* note the reply port so we can send future accepts
						   to the same place */
						wait_tm->header.mn_ReplyPort = tm->header.mn_ReplyPort;
					
						if (ti->fd > *max_fd) *max_fd = ti->fd;
						FD_SET(ti->fd, reads);
						AddTail(wait_list, (struct Node *)wait_tm);
						
						tm->result = true;
						tm->error = NO_ERROR;
						ReplyMsg(&tm->header);
						return;
					} else {
						tm->error = ERROR_ACCESS_DENIED;
					}
				} else {
					tm->error = ERROR_ACCESS_DENIED;
				}
				CloseSocket(ti->fd);
			} else tm->error = ERROR_OOM;
			free_tcpmessage(wait_tm);
		} else tm->error = ERROR_OOM;
		free_tcpident(ti);
	} else tm->error = ERROR_OOM;

	tm->result = false;
	
	ReplyMsg(&tm->header);
	return;
}

void tcp_accept(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, struct MsgPort *replies)
{
	tcpident	*ti, *accept_ti;
	tcpmessage	*accept_tm, *wait_tm;
	struct sockaddr_in sin;
	sb32		sin_len, result;
	
	truth(SocketBase != nil);
	truth(wait_list != nil);
	truth(replies != nil);
	
	verify(tm, V_tcpmessage);
	
	ti = tm->ident;
	verify(ti, V_tcpident);
	
	sin_len = sizeof(sin);
	
	result = accept(ti->fd, (struct sockaddr *)&sin, &sin_len);
	if (result < 0) {	/* this should never really happen I don't think, but ... */
		/* nothing need be done */
		return;
	}
	
	non_blocking(SocketBase, result);
	
	accept_tm = new_tcpmessage(tm->header.mn_ReplyPort);
	if (accept_tm) {
		wait_tm = new_tcpmessage(nil);
		if (wait_tm) {
			accept_ti = new_tcpident(result);
			if (accept_ti) {
				/* all systems go! */
				
				/* tell the parent that we have a new connection ... */
				accept_tm->command = TCP_ACCEPTED;
				accept_tm->address.l = sin.sin_addr.s_addr;
				accept_tm->port.w = sin.sin_port;
				
				accept_tm->ident = accept_ti;
				accept_tm->result = true;
				accept_tm->error = NO_ERROR;
				
				PutMsg(tm->header.mn_ReplyPort, &accept_tm->header);
				
				/* keep a copy on our files for book keeping purposes */
				wait_tm->command = TCP_CONNECTED;
				wait_tm->address.l = sin.sin_addr.s_addr;
				wait_tm->port.w = sin.sin_port;
				
				wait_tm->ident = accept_ti;
				
				/* this is a bit nasty ... it has to be on the head because
				   we are traversing the list forward */
				AddHead(wait_list, (struct Node *)wait_tm);
				return;
			}
			free_tcpmessage(wait_tm);
		}
		free_tcpmessage(accept_tm);
	}
	
	/* this is a bit sad ... it accepts and then it just closes for no 
	   apparent reason ... I don't see any alternative however */
	
	CloseSocket(result);
	return;
}

void tcp_close(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, fd_set *writes, sb32 *max_fd)
{
	tcpident *ti, *iti;
	tcpmessage *itm, *nitm;
	int ncons = 0, nlis = 0;

	truth(SocketBase != nil);
	truth(max_fd != nil);
	truth(reads != nil);
	truth(wait_list != nil);

	verify(tm, V_tcpmessage);
	
	ti = tm->ident;
	if (!ti || ti->connecting_port) {
		tm->result = false;
		tm->error = ERROR_NO_CONNECTION;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	verify(ti, V_tcpident);
	
	/* 
	 * search through the waiting list and:
	 * 	abort reads and writes that match
	 * 	close TCP_CONNECTEDs
	 *	close TCP_LISTENs	
	 */

	for (itm = (tcpmessage *)wait_list->lh_Head;
			nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
			itm = nitm) {
		
		verify(itm, V_tcpmessage);

		switch (itm->command) {
		case TCP_READ:
		case TCP_WRITE:
			iti = itm->ident;
			verify(iti, V_tcpident);
			
			if (iti == ti) {
				itm->error = ERROR_INTERRUPTED;
				itm->ident = nil;

				Remove((struct Node *)itm);
				ReplyMsg(&itm->header);
			}
			break;
		case TCP_LISTEN:
			iti = itm->ident;
			verify(iti, V_tcpident);
			
			if (iti == ti) {
				nlis++;
				
				Remove((struct Node *)itm);
				free_tcpmessage(itm);
			}
			break;
		case TCP_CONNECTED:
			iti = itm->ident;
			verify(iti, V_tcpident);
			
			if (iti == ti) {
				ncons++;
				
				Remove((struct Node *)itm);
				free_tcpmessage(itm);
			}
			break;
		}
	}

	truth(ncons + nlis == 1);	/* one, and only one connection!!! */
	
	if (ti->fd >= 0)
		CloseSocket(ti->fd);

	free_tcpident(ti);
	
	tm->ident = nil;
	tm->result = true;
	tm->error = NO_ERROR;
	
	ReplyMsg(&tm->header);
	
	fix_read_set(wait_list, reads, max_fd);
	fix_write_set(wait_list, writes, max_fd);

	return;
}

void do_connect(struct Library *SocketBase, tcpmessage *mess, struct MsgPort *pport, struct MsgPort *myport)
{
	/* bits of the following inspired by the source to NcFTP ... thanks go to Mike Gleason */
	struct sockaddr_in sin;
	struct hostent *he;
	sb32 result, s;
	
	truth(SocketBase != nil);
	truth(pport != nil);
	truth(myport != nil);
	verify(mess, V_tcpmessage);
	
	memset((void *)&sin, 0, sizeof(&sin));	/* not sure this is necessary ... ncftp does this.  JIC */
	
	sin.sin_port = mess->port.w;
	sin.sin_family = AF_INET;

	if (mess->data) {	/* if they don't send a string, their address must be right */

		sin.sin_addr.s_addr = inet_addr(mess->data);
		if (sin.sin_addr.s_addr == -1) {	/* not a direct dotted IP number */
			he = gethostbyname(mess->data);
			if (!he) {	/* oh well ... */
				mess->result = 0;
				mess->error = ERROR_UNKNOWN_HOST;
				
				PutMsg(pport, &mess->header);
				
				WaitPort(myport);
				GetMsg(myport);
				return;
			}
			
			sin.sin_family = he->h_addrtype;
			memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);
		}
	} else {
		sin.sin_addr.s_addr = mess->address.l;
	}

	/* we have the address, now try the connect */
	
	s = socket(sin.sin_family, SOCK_STREAM, 0);	/* 0 for protocol legal? ncftp does it */
	if (s < 0) {
		mess->result = 0;
		mess->error = ERROR_OOM;
		
		PutMsg(pport, &mess->header);
		
		WaitPort(myport);
		GetMsg(myport);
		return;
	}
	
	result = connect(s, (struct sockaddr *)&sin, sizeof(sin));
	if (result < 0) {
		/* perhaps we should try the backup addresses, but nahhhh */
		switch (Errno()) {
		case ENETDOWN:
		case ENETUNREACH:
			mess->error = ERROR_UNREACHABLE;
			break;
		case ECONNREFUSED:
			mess->error = ERROR_CONNECT_REFUSED;
			break;
		default:
			mess->error = ERROR_CANT_CONNECT;
			break;
		}
		
		CloseSocket(s);
		
		mess->result = 0;
		
		PutMsg(pport, &mess->header);
		
		WaitPort(myport);
		GetMsg(myport);
		return;
	}
	
	/* hurrah */
	
	/* now we have to transfer our socket to our parents "library domain" */
	
	result = ReleaseSocket(s, UNIQUE_ID);
	if (result < 0) {	/* BUGGER!  just as everything was going so well too :( */
		mess->result = 0;
		mess->error = ERROR_OOM;

		CloseSocket(s);
	} else {
		mess->result = result;
		mess->error = NO_ERROR;
	}
	
	PutMsg(pport, &mess->header);

	WaitPort(myport);
	GetMsg(myport);
	return;
}

void __saveds __asm connect_child(register __a0 b8 *parent_port)
{
	struct Library *SocketBase;
	struct MsgPort *pport, *myport;
	tcpmessage *mess;
	
	pport = FindPort(parent_port);
	if (!pport) {
		/* not a fuck of a lot we can do */
		return;
	}
	
	myport = CreatePort(0, 0);
	if (myport) {
		mess = new_tcpmessage(myport);
		if (mess) {
			SocketBase = OpenLibrary("bsdsocket.library", 0);
			if (SocketBase) {
				/* tell them we are going */
				mess->command = TCP_STARTUP;
				mess->result = true;
				mess->error = NO_ERROR;
			
				PutMsg(pport, &mess->header);
			
				WaitPort(myport);
				GetMsg(myport);		/* should be guaranteed to be mess back */
				
				pport = mess->header.mn_ReplyPort;	/* may be a different port */
				mess->command = TCP_CONNECTED;
				mess->header.mn_ReplyPort = myport;	/* GOT TO BE THE SAME !!! (for ident purposes) */
			
				do_connect(SocketBase, mess, pport, myport);
				
				CloseLibrary(SocketBase);
			} else {
				/* tell them we are NOT going */
				mess->command = TCP_STARTUP;
				mess->result = false;
				mess->error = ERROR_NO_CONNECTION;
				
				PutMsg(pport, &mess->header);
				
				WaitPort(myport);
				GetMsg(myport);
			}
			free_tcpmessage(mess);
			DeletePort(myport);
			return;
		}
		DeletePort(myport);
	}
	
	/* our half-hearted way of telling the parent something is wrong */
	Signal(pport->mp_SigTask, 1 << pport->mp_SigBit);
	
	return;
}

void tcp_connect(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, struct MsgPort *replies)
{
	struct Process *child;
	b8 buffer[30];
	struct MsgPort *tmp_port;
	tcpmessage *child_tm;

	unique_name(FindTask(0), ": Evans TCP Handler", buffer);
	
	if (tm->ident) {
		tm->result = false;
		tm->error = ERROR_ALREADY_CONNECTED;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	tm->ident = new_tcpident(-1);
	if (!tm->ident) {
		tm->result = false;
		tm->error = ERROR_OOM;
		
		ReplyMsg(&tm->header);
		return;
	}

	tmp_port = CreatePort(buffer, 0);
	if (!tmp_port) {
		free_tcpident(tm->ident);
		tm->ident = nil;

		tm->result = false;
		tm->error = ERROR_OOM;
		
		ReplyMsg(&tm->header);
		return;
	}

	/* start child */
	
	child = CreateNewProcTags(
		NP_Entry,	connect_child,
		NP_Name,	"TCP Connect Handler",
		NP_Arguments,	buffer,
		TAG_END,	0
	);

	if (!child) {
		tm->result = false;
		tm->error = ERROR_OOM;
		
		DeletePort(tmp_port);
		free_tcpident(tm->ident);
		tm->ident = nil;

		ReplyMsg(&tm->header);
		return;
	}
	
	/* the child should get back to us immediately ... so 
	   synchronously wait for the startup message */
	
	Wait(1 << tmp_port->mp_SigBit);
	
	child_tm = (tcpmessage *)GetMsg(tmp_port);
	if (!child_tm) {	/* they had a problem, and failed */
		tm->result = false;
		tm->error = ERROR_OOM;
		
		DeletePort(tmp_port);
		free_tcpident(tm->ident);
		tm->ident = nil;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	if (!child_tm->result) {	/* ditto */
		tm->error = child_tm->error;

		ReplyMsg((struct Message *)child_tm);
		
		tm->result = false;
		
		DeletePort(tmp_port);
		free_tcpident(tm->ident);
		tm->ident = nil;
		
		ReplyMsg(&tm->header);
		return;
	}

	/* well, all should be ok now */
	
	DeletePort(tmp_port);
	
	tmp_port = child_tm->header.mn_ReplyPort;

	child_tm->header.mn_ReplyPort = replies;
	child_tm->data = tm->data;
	child_tm->flags = tm->flags;
	child_tm->port = tm->port;
	child_tm->address = tm->address;
	
	PutMsg(tmp_port, &child_tm->header);
	
	/* that will come back when the child has resolved the connect */
	
	((tcpident *)tm->ident)->connecting_port = tmp_port;	/* note: this is a special case for TCP_CONNECT ONLY */
	
	AddTail(wait_list, (struct Node *)tm);
	
	return;
}

void tcp_connected(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list)
{
	tcpident *ti;
	tcpmessage *itm, *nitm, *wait_tm;
	struct MsgPort *their_port;	/* to identify which child */
	sb32 s;
	
	truth(SocketBase != nil);
	truth(wait_list != nil);
	verify(tm, V_tcpmessage);
	
	their_port = tm->header.mn_ReplyPort;
	
	if (tm->error == NO_ERROR) {
		s = ObtainSocket(tm->result, AF_INET, SOCK_STREAM, 0);	/* shudder */
	} else {
		s = -1;
	}

	/* it may have failed ... but we have to check after we have found the TCP_CONNECT */
	
	/* see which TCP_CONNECT in the wait list it corresponds to ... */

	for (itm = (tcpmessage *)wait_list->lh_Head;
			nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
			itm = nitm) {
		if (itm->command == TCP_CONNECT) {
			ti = itm->ident;
			verify(ti, V_tcpident);
			
			if (ti->connecting_port == (void *)their_port) {
				Remove((struct Node *)itm);
				
				if ((tm->error != NO_ERROR) || (s < 0)) {
					if (tm->error != NO_ERROR) {
						itm->result = false;
						itm->error = tm->error;
					} else {		/* this case is our Obtain failing */
						itm->result = false;
						/* bizarre error for bizarre case */
						itm->error = ERROR_LOST_CONNECTION;
					}
					
					ReplyMsg(&tm->header);
					
					/* NB: reusing tm */
					tm = itm->interrupt;
					itm->interrupt = nil;
					
					free_tcpident(ti);
					itm->ident = nil;
					
					ReplyMsg(&itm->header);
					if (tm) {
						verify(tm, V_tcpmessage);
						ReplyMsg(&tm->header);
					}
					return;
				}
				
				non_blocking(SocketBase, s);
				
				/* ok, have to make a tcpmessage to wait here */
				
				wait_tm = new_tcpmessage(nil);
				if (wait_tm) {
					ti->connecting_port = nil;
					ti->fd = s;
					
					wait_tm->ident = ti;
					wait_tm->command = TCP_CONNECTED;
					
					wait_tm->address = tm->address;
					wait_tm->port = tm->port;
						
					itm->address = tm->address;
					itm->port = tm->port;
						
					itm->result = true;
					itm->error = NO_ERROR;
						
					ReplyMsg(&tm->header);
						
					tm = itm->interrupt;
					itm->interrupt = nil;
						
					ReplyMsg(&itm->header);
						
					if (tm) {
						verify(tm, V_tcpmessage);
						ReplyMsg(&tm->header);
					}
						
					AddTail(wait_list, (struct Node *)wait_tm);
						
					return;
				} else itm->error = ERROR_OOM;
				
				free_tcpident(ti);
				itm->ident = nil;
				
				CloseSocket(s);
				itm->result = false;
				
				ReplyMsg(&tm->header);
				
				tm = itm->interrupt;
				itm->interrupt = nil;
				
				ReplyMsg(&itm->header);
				if (tm) {
					verify(tm, V_tcpmessage);
					ReplyMsg(&tm->header);
				}
				
				return;
			}
		}
	}
	
	/* hmmm, strange things are happening */
	if (s >= 0) CloseSocket(s);
	
	ReplyMsg(&tm->header);
	return;
}

void tcp_interrupt(tcpmessage *tm, struct List *wait_list, fd_set *reads, fd_set *writes, sb32 *max_fd)
{
	tcpident *ti;
	tcpmessage *itm, *nitm;
	struct MsgPort *port;
	
	verify(tm, V_tcpmessage);
	
	for (itm = (tcpmessage *)wait_list->lh_Head;
			nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
			itm = nitm) {
		
		verify(itm, V_tcpmessage);

		if (itm == tm->interrupt) {
			switch (itm->command) {
			case TCP_READ:
				Remove((struct Node *)itm);
				
				itm->error = ERROR_INTERRUPTED;
				ReplyMsg(&itm->header);
				
				tm->result = true;
				tm->error = NO_ERROR;
				
				ReplyMsg(&tm->header);
				
				fix_read_set(wait_list, reads, max_fd);
				return;
			case TCP_WRITE:
				Remove((struct Node *)itm);
				
				itm->error = ERROR_INTERRUPTED;
				ReplyMsg(&itm->header);
				
				tm->result = true;
				tm->error = NO_ERROR;
				
				ReplyMsg(&tm->header);
				
				fix_write_set(wait_list, writes, max_fd);
				return;
			case TCP_CONNECT:
				/* this is the fun one */
				ti = itm->ident;
				verify(ti, V_tcpident);
				
				port = ti->connecting_port;
				
				itm->interrupt = tm;
				Signal(port->mp_SigTask, SIGBREAKF_CTRL_C);
				return;
			}
		}
	}
	
	tm->result = false;
	tm->error = ERROR_NO_CONNECTION;
	
	ReplyMsg(&tm->header);
	return;
}

void tcp_peername(struct Library *SocketBase, tcpmessage *tm)
/*
 * this one is a strange one ... it may have the potential to block
 * I don't really want to write a bloody child process thingy just for
 * this trivial bloody function 
 */
{
	tcpident *ti;
	struct sockaddr_in sin;
	struct hostent *he;
	sb32 sin_len, len;

	truth(SocketBase != nil);
	verify(tm, V_tcpmessage);
	
	ti = tm->ident;
	if (!ti) {
		tm->result = false;
		tm->error = ERROR_NO_CONNECTION;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	verify(ti, V_tcpident);
	
	sin_len = sizeof(sin);
	
	if (getpeername(ti->fd, (struct sockaddr *)&sin, &sin_len) < 0) {
		if (Errno() == ENOTCONN) {
			tm->error = ERROR_NO_CONNECTION;
		} else {
			tm->error = ERROR_OOM;
		}
		tm->result = false;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	he = gethostbyaddr((void *)&sin.sin_addr.s_addr, 4, AF_INET);
	if (!he) {
		tm->error = ERROR_UNKNOWN_HOST;
		tm->result = false;
		
		ReplyMsg(&tm->header);
		return;
	}
	
	len = strlen(he->h_name);
	if (len >= tm->length) {
		len = tm->length - 1;
	}
	
	memcpy(tm->data, he->h_name, len);
	((b8 *)tm->data)[len] = 0;
	
	tm->error = NO_ERROR;
	tm->result = true;
	
	ReplyMsg(&tm->header);
	return;
}

void tcp_service(struct Library *SocketBase, tcpmessage *tm)
{
	struct servent *se;
	
	truth(SocketBase != nil);
	verify(tm, V_tcpmessage);

	se = getservbyname(tm->data, "tcp");
	if (!se) {
		tm->result = false;
		tm->error = ERROR_UNKNOWN_COMMAND;
	} else {
		tm->result = true;
		tm->error = NO_ERROR;
		
		tm->port.w = se->s_port;
	}
	
	ReplyMsg(&tm->header);
	return;
}

struct MsgPort *running_running(tcpmessage *emergency, struct MsgPort *commands)
{
	b32		tcp_signal, signal_tmp;
	tcpmessage	*tm, *tm2;
	tcpident	*ti;
	struct Library	*SocketBase = nil;
	struct List	waiting;
	fd_set		read_set, write_set, read_tmp, write_tmp;
	sb32		max_fd, n;
	struct MsgPort	*death_port;
	
	NewList(&waiting);	/* list of waiting requests */
	
	FD_ZERO(&read_set);
	FD_ZERO(&write_set);
	
	max_fd = -1;
	
	tcp_signal = (1 << commands->mp_SigBit);
	
	while (1) {
		if (SocketBase) {
			read_tmp = read_set;
			write_tmp = write_set;
			signal_tmp = tcp_signal;
			
			n = WaitSelect(max_fd + 1, &read_tmp, &write_tmp, nil, nil, &signal_tmp);
		} else {
			n = 0;
			Wait(tcp_signal);
		}
		
		while (tm = (tcpmessage *)GetMsg(commands)) {
			/* a message from our parent (sponsor?) */

			verify(tm, V_tcpmessage);
			
			if (tm->header.mn_ReplyPort == commands) {
				/* its been ReplyMsg'd to us */
				free_tcpmessage(tm);
				continue;
			}
			
			switch (tm->command) {
			case TCP_NOOP:
				tm->result = true;
				ReplyMsg(&tm->header);
				break;
			case TCP_CONNECT:
				if (!SocketBase) {
					SocketBase = OpenLibrary("bsdsocket.library", 0);
					if (!SocketBase) {
						tm->result = false;
						tm->error = ERROR_NO_CONNECTION;
						
						ReplyMsg(&tm->header);
						break;
					}
				}
				tcp_connect(SocketBase, tm, &waiting, commands);
				break;
			case TCP_LISTEN:
				if (!SocketBase) {
					SocketBase = OpenLibrary("bsdsocket.library", 0);
					if (!SocketBase) {
						tm->result = false;
						tm->error = ERROR_NO_CONNECTION;
						
						ReplyMsg(&tm->header);
						break;
					}
				}
				tcp_listen(SocketBase, tm, &waiting, &read_set, &max_fd);
				break;
			case TCP_READ:
				if (!SocketBase) {	/* someone is frigging us around */
					tm->result = 0;
					tm->error = ERROR_NO_CONNECTION;
					
					ReplyMsg(&tm->header);
					break;
				}
				tcp_read(SocketBase, tm, &waiting, &read_set, &max_fd);
				break;
			case TCP_WRITE:
				if (!SocketBase) {	/* someone is frigging us around */
					tm->result = 0;
					tm->error = ERROR_NO_CONNECTION;
					
					ReplyMsg(&tm->header);
					break;
				}
				tcp_write(SocketBase, tm, &waiting, &write_set, &max_fd);
				break;
			case TCP_CLOSE:
				if (!SocketBase) {	/* someone is frigging us around */
					tm->result = false;
					tm->error = ERROR_NO_CONNECTION;
					
					ReplyMsg(&tm->header);
					break;
				}
				tcp_close(SocketBase, tm, &waiting, &read_set, &write_set, &max_fd);
				
				if (IsListEmpty(&waiting)) {
					/* absolutely everything has closed.  We can close
					   the socket library now in safety */
					CloseLibrary(SocketBase);
					SocketBase = nil;
				}
				break;
			case TCP_CONNECTED:
				/* ahhh, a child is reporting back! */
				
				if (!SocketBase) {	/* someone is frigging us around */
					tm->result = false;
					tm->error = ERROR_NO_CONNECTION;
					
					ReplyMsg(&tm->header);
					break;
				}
				
				tcp_connected(SocketBase, tm, &waiting);

				if (IsListEmpty(&waiting)) {
					/* absolutely everything has closed.  We can close
					   the socket library now in safety */
					CloseLibrary(SocketBase);
					SocketBase = nil;
				}
				break;
			case TCP_INTERRUPT:
				tcp_interrupt(tm, &waiting, &read_set, &write_set, &max_fd);
				break;
			case TCP_NEWMESSAGE:
				tm2 = new_tcpmessage(tm->header.mn_ReplyPort);
				if (!tm2) {
					tm->result = false;
					tm->error = ERROR_OOM;
					ReplyMsg(&tm->header);
					break;
				}
				
				tm2->ident = tm->ident;
				
				tm->data = tm2;
				tm->result = true;
				tm->error = NO_ERROR;
				
				ReplyMsg(&tm->header);
				break;
			case TCP_DISPOSE:
				free_tcpmessage(tm);
				break;
			case TCP_DIE:
				/* we have to assume they've done the right thing and
				   disposed of all messages (closing all connections) */
				/* all we have to do is to free whatever is left over
				   and exit */
				
				death_port = tm->header.mn_ReplyPort;

				free_tcpmessage(tm);
				
				if (SocketBase) {
					CloseLibrary(SocketBase);
					SocketBase = nil;
				}
				
				while (tm = (tcpmessage *)RemHead(&waiting)) {
					if (tm->ident && tm->command != TCP_CONNECT)
						free_tcpident(tm->ident);
					free_tcpmessage(tm);
				}
				
				/* perhaps should do a little more ... */
				
				return death_port;
			case TCP_PEERNAME:
				if (!SocketBase) {	/* someone is yankin' our chain */
					tm->result = false;
					tm->error = ERROR_NO_CONNECTION;
					
					ReplyMsg(&tm->header);
					break;
				}
				tcp_peername(SocketBase, tm);
				break;
			case TCP_SERVICE:
				if (!SocketBase) {
					SocketBase = OpenLibrary("bsdsocket.library", 0);
					if (!SocketBase) {
						tm->result = false;
						tm->error = ERROR_NO_CONNECTION;
						
						ReplyMsg(&tm->header);
						break;
					}
				}
				tcp_service(SocketBase, tm);
				if (IsListEmpty(&waiting)) {
					/* absolutely everything has closed.  We can close
					   the socket library now in safety */
					CloseLibrary(SocketBase);
					SocketBase = nil;
				}
				break;
			default:
				tm->result = false;
				tm->error = ERROR_UNKNOWN_COMMAND;
				ReplyMsg(&tm->header);
				break;
			}
		}
		
		if (n < 0) {	/* hmmm ... this seems to happen when they close the library underneath us */
			/* should send back any waiting reads/writes/listens */
			CloseLibrary(SocketBase);
			SocketBase = nil;
			
			continue;
		}
		
#ifdef SDLFKJ
		truth (n >= 0);		/* I don't _think_ we should ever get SIGINTR ... */
#endif

		if (n <= 0) continue;	/* no fds are ready, so don't look through list */
		
		for (tm = (tcpmessage *)waiting.lh_Head; 
				tm2 = (tcpmessage *)tm->header.mn_Node.ln_Succ; 
				tm = tm2) {

			switch (tm->command) {
			case TCP_LISTEN:
				ti = tm->ident;
				verify(ti, V_tcpident);

				if (FD_ISSET(ti->fd, &read_tmp)) {	/* ready to accept */
					tcp_accept(SocketBase, tm, &waiting, commands);
				}
				break;
			case TCP_READ:
				ti = tm->ident;
				verify(ti, V_tcpident);

				if (FD_ISSET(ti->fd, &read_tmp)) {	/* ready to continue reading */
					tcp_read_more(SocketBase, tm, &waiting, &read_set, &max_fd);
				}
				break;
			case TCP_WRITE:
				ti = tm->ident;
				verify(ti, V_tcpident);

				if (FD_ISSET(ti->fd, &write_tmp)) {	/* ready to continue writing */
					tcp_write_more(SocketBase, tm, &waiting, &write_set, &max_fd);
				}
				break;
			}
		}
	}
}

void __saveds __asm tcp_handler(register __a0 b8 *parent_port)
{
	struct MsgPort *mp, *tcp_commands;
	tcpmessage *tm, *new_tm;
	
	mem_tracking_on();
	
	mp = FindPort(parent_port);
	if (!mp) {
		truth(FindPort(parent_port) != nil);	/* will display an alert */
		return;	/* OOOPS - not overly much we can do here */
	}
	
	tcp_commands = CreatePort(0, 0);
	if (!tcp_commands) {
		/* this is a signal jobbie because we can't send a message 
		   so when they wait for the signal, and then can't GetMsg
		   they should guess that something has gone wrong */
		Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
		return;
	}
	
	tm = new_tcpmessage(tcp_commands);
	if (!tm) {
		/* again ... our message doesn't exist, so just signal */
		Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
		DeletePort(tcp_commands);
		return;
	}
	
	new_tm = new_tcpmessage(tcp_commands);
	if (!new_tm) {
		Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
		DeletePort(tcp_commands);
		free_tcpmessage(tm);
		return;
	}
	
	new_tm->command = TCP_NEWMESSAGE;
	new_tm->header.mn_ReplyPort = mp;	
		/* just a guess ... if they want something different, they'se gonna haveta do it themselves */

	/* ok, we have enough to communicate ... tell them we started ok */
	
	tm->command = TCP_STARTUP;
	tm->data = tcp_commands;	/* this tells them where to send commands */
	tm->result = true;		/* not necessary but wth */
	
	PutMsg(mp, &tm->header);
	
	/* wait for it to come back */
	
	Wait(1 << tcp_commands->mp_SigBit);
	GetMsg(tcp_commands);	/* should be guaranteed to be tm back ;) */
	
	/* ok, time to carry on */
	/* send them a message to bootstrap their message system */
	
	PutMsg(mp, &new_tm->header);
	
	/* Reusing mp here for the death port */

	mp = running_running(tm, tcp_commands);
	
	DeletePort(tcp_commands);
	free_tcpmessage(tm);
	
	check_memory();
	
	Forbid();	/* once we are RemTask'd the Forbid will be lifted */
	
	Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);	/* signal parent we are dead */
	
	return;
}
