/*--------------------------------------------------------------------------*/
/*	File name:	TCP.C							Revision date:	2000.07.08	*/
/*	Revised by:	Ulf Ronald Andersson			Revision start:	1999.01.14	*/
/*	Created by:	Peter Rottengatter				Creation date:	1997.02.19	*/
/*--------------------------------------------------------------------------*/
/*	Project:	TCP high level protocol module for STinG					*/
/*	Module:		Installation routines and top level TCP API					*/
/*--------------------------------------------------------------------------*/
#include <tos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sting\transprt.h>
#include <sting\layer.h>
#include <sting\sys\tcp.h>
/*--------------------------------------------------------------------------*/
#define	M_NAME		"TCP"
#define	M_VERSION	"01.37"
#define	M_YEAR		2000
#define	M_MONTH		7
#define	M_DAY		8
#define	M_AUTHOR	"Peter Rottengatter|     &  STinG Evolution Team"
#define	M_FLAGS		0x10400L
/*--------------------------------------------------------------------------*/
/* RA: the macro below eliminates Supexec from being needed in next_port()	*/
/*--------------------------------------------------------------------------*/
#define	next_port()	((uint16) protect_exec(NULL, pe_next_port))
/*--------------------------------------------------------------------------*/
void	cdecl	timer_function (void);
int16			poll_receive (CONNEC *connec);
int16	cdecl	do_ICMP (IP_DGRAM *dgram);
void			send_sync (CONNEC *connec);
void			send_abort (CONNEC *connec);
void			close_self (CONNEC *connec, int16 reason);
void			flush_queue (NDB **queue);
int32	cdecl	pe_unlink_conn(void *conn);	/* RA: new protect_exec func	*/
void			destroy_conn (CONNEC *connec);
int16			receive (CONNEC *connec, uint8 *buffer, int16 *length, int16 flag);
int16			categorize (CONNEC *connec);

int16	cdecl	TCP_handler (IP_DGRAM *dgram);
void			do_output (CONNEC *connec);

uint32			get_ip_addr (char *field);
long			get_sting_cookie (void);
int16			install (void);
uint16			read_word (char *string);
int32	cdecl	pe_next_port(void *dummy);			/* RA: protect_exec !	*/
int16	cdecl	my_TCP_open (uint32 rem_host, uint16 rem_port, uint16 tos, uint16 size);
int16	cdecl	my_TCP_close (int16 connec, int16 mode, int16 *result);
int16	cdecl	my_TCP_send (int16 connec, void *buffer, int16 length);
int16	cdecl	my_TCP_wait_state (int16 connec, int16 state, int16 timeout);
int16	cdecl	my_TCP_ack_wait (int16 connec, int16 timeout);
int16	cdecl	my_TCP_info (int16 connec, TCPIB *block);
int16	cdecl	my_CNkick (void *connec);
int16	cdecl	my_CNbyte_count (void *connec);
int16	cdecl	my_CNget_char (void *connec);
NDB *	cdecl	my_CNget_NDB (void *connec);
int16	cdecl	my_CNget_block (void *connec, void *buffer, int16 length);
CIB *	cdecl	my_CNgetinfo (void *connec);
int16	cdecl	my_CNgets (void *connec, char *buffer, int16 length, char delimiter);


DRV_LIST  *sting_drivers;
TPL       *tpl;
STX       *stx;
TCP_CONF  my_conf  =  {{  M_NAME, M_VERSION, M_FLAGS, ((M_YEAR-1980) << 9) | (M_MONTH << 5) | M_DAY, 
                          M_AUTHOR, 0, NULL, NULL },
                          2000, 2000, 1500, 64, 6000, 0, 0, 0 };
CN_FUNCS  cn_vectors = {  my_CNkick, my_CNbyte_count, my_CNget_char, my_CNget_NDB,
                          my_CNget_block, my_CNgetinfo, my_CNgets   };
uint16    last_port;
CONNEC    *root_list = NULL;
char      fault[] = "TCP.STX : STinG extension module. Only to be started by STinG !\r\n";
/*--------------------------------------------------------------------------*/
void  main (argc, argv)

int   argc;
char  *argv[];

{
   if (argc != 2) {
        Cconws (fault);   return;
      }
   if (strcmp (argv[1], "STinG_Load") != 0) {
        Cconws (fault);   return;
      }

   sting_drivers = (DRV_LIST *) Supexec (get_sting_cookie);

   if (sting_drivers == 0L)   return;

   if (strcmp (sting_drivers->magic, MAGIC) != 0)
        return;

   tpl = (TPL *) (*sting_drivers->get_dftab) (TRANSPORT_DRIVER);
   stx = (STX *) (*sting_drivers->get_dftab) (MODULE_DRIVER);

   if (tpl != (TPL *) NULL && stx != (STX *) NULL) {
        if (install())
             Ptermres (_PgmSize, 0);
      }
}
/*--------------------------------------------------------------------------*/
long  get_sting_cookie()

{
   long  *work;

   for (work = * (long **) 0x5a0L; *work != 0L; work += 2)
        if (*work == 'STiK')
             return (*++work);

   return (0L);
}
/*--------------------------------------------------------------------------*/
int16  install()
{	LAYER  *layers;
	int16  count;
	char   *config;

	if	(! ICMP_handler (do_ICMP, HNDLR_SET))
		return (FALSE);

	if	(! IP_handler (P_TCP, TCP_handler, HNDLR_SET))
	{	ICMP_handler (do_ICMP, HNDLR_REMOVE);
		return (FALSE);
	}

	if	(! TIMER_call (timer_function, HNDLR_SET))
	{	ICMP_handler (do_ICMP, HNDLR_REMOVE);
		IP_handler (P_TCP, TCP_handler, HNDLR_REMOVE);
		return (FALSE);
	}

	if	(PRTCL_announce (P_TCP))
	{	ICMP_handler (do_ICMP, HNDLR_REMOVE);
		IP_handler (P_TCP, TCP_handler, HNDLR_REMOVE);
		TIMER_call (timer_function, HNDLR_REMOVE);
		return (FALSE);
	}

	my_conf.generic.basepage = _BasPag;

	query_chains (NULL, NULL, (void **) & layers);

	while (layers->next)
		layers = layers->next;

	layers->next = & my_conf.generic;

	tpl->TCP_open       = my_TCP_open;
	tpl->TCP_close      = my_TCP_close;
	tpl->TCP_send       = my_TCP_send;
	tpl->TCP_wait_state = my_TCP_wait_state;
	tpl->TCP_ack_wait   = my_TCP_ack_wait;
	tpl->TCP_info       = my_TCP_info;

	config = getvstr ("TCP_PORT");
	if (config[1])
	{	my_conf.generic.flags &= 0xffff0000ul;
		my_conf.generic.flags |= read_word (config);
	}
	config = getvstr ("TCP_ICMP");
	my_conf.generic.flags &= 0xfffefffful;
	my_conf.generic.flags |= (config[0] != '0') ? 0x10000ul : 0ul;

	config = getvstr ("MSS");
	if (config[1])
		my_conf.mss        = read_word (config);
	config = getvstr ("RCV_WND");
	if (config[1])
		my_conf.rcv_window = read_word (config);
	config = getvstr ("DEF_RTT");
	if (config[1])
		my_conf.def_rtt    = read_word (config);
	config = getvstr ("DEF_TTL");
	if (config[1])
		my_conf.def_ttl    = read_word (config);

	if	((last_port = my_conf.generic.flags & 0xfffful) >= 30000)
		last_port = 29999;

	my_conf.max_slt = 4 * my_conf.def_rtt;

	return (TRUE);
}
/*----------------------*/
/* ends:	install		*/
/*--------------------------------------------------------------------------*/
uint16  read_word (char *string)
{	uint16  result = 0;

	while (*string == ' ')
		string++;

	while ('0' <= *string && *string <= '9')
		result = result * 10 + (*string++ - '0');

	return (result);
}
/*----------------------*/
/* ends:	read_word	*/
/*--------------------------------------------------------------------------*/
/* RA: pe_next_port needs protect_exec to avoid using xbios Supexec			*/
/* RA: I have defined a 'next_port()' macro at the top of this file for it.	*/
/*--------------------------------------------------------------------------*/
int32	cdecl	pe_next_port(void *dummy)
{	CONNEC  *connect;
	(void)	dummy;
	for (;;)
	{	last_port++;
		if (last_port > 32765 || last_port < (my_conf.generic.flags & 0xfffful))
			last_port = my_conf.generic.flags & 0xfffful;
		for (connect = root_list; connect; connect = connect->next)
			if (connect->local_port == last_port)
				break;
		if (connect)
			continue;
		return ((int32) last_port);
	}
}
/*--------------------------*/
/* ends:	pe_next_port	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_TCP_open (rem_host, rem_port, tos, buff_size)

uint32  rem_host;
uint16  rem_port, tos;
uint16  buff_size;

{	CAB		*cab;
	CONNEC	*connect;
	uint32	lcl_host = 0L, aux_ip, error;
	uint16	buff_used, act_pass, lport, rport, mtu, max_mss = MAX_MSS;
	int16	window, ttl, handle;

	if	(rem_host == 0L && (rem_port == TCP_ACTIVE || rem_port == TCP_PASSIVE))
		rem_port = next_port();

	if	(rem_port != TCP_ACTIVE && rem_port != TCP_PASSIVE)
	{	if	(rem_host)
		{	act_pass = TCP_ACTIVE;
			lport = next_port();
			rport = rem_port;
		}
		else
		{	act_pass = TCP_PASSIVE;
			lport = rem_port;
			rport = 0;
		}
	}
	else
	{	cab = (CAB *) rem_host;
		act_pass = rem_port;
		rem_host = cab->rhost;
		rport =  cab->rport;
		lcl_host = cab->lhost;
		lport = (cab->lport) ? cab->lport : next_port();
	}

	if	(rem_host != 0L)
	{	if	(PRTCL_get_parameters (rem_host, & aux_ip, & ttl, & mtu) != E_NORMAL)
			return (E_UNREACHABLE);
		lcl_host = (lcl_host) ? lcl_host : aux_ip;
		max_mss = mtu - sizeof (IP_HDR) - sizeof (TCP_HDR);
	}
	else
	{	if (act_pass == TCP_ACTIVE)
			return (E_PARAMETER);
	}

	ttl = my_conf.def_ttl;
	buff_used = (buff_size > 0) ? buff_size : my_conf.rcv_window;
	window = (buff_used < my_conf.rcv_window) ? buff_used : my_conf.rcv_window;

	if	((connect = (CONNEC *) KRmalloc (sizeof (CONNEC))) == NULL)
		return (E_NOMEM);

	if	((handle = PRTCL_request (connect, & cn_vectors)) == -1)
	{	KRfree (connect);
		return (E_NOMEM);
	}
/* RA: The ->open.handle element is used for interrupt driven ability to	*/
/* RA: release the TCP handle at final closing (erasing) of connection.		*/
	connect->open.handle = handle;

/* RA: The rest of the ->open.* elements are needed for proper RESET of a	*/
/* RA: connection, which must be different for TCP_ACTIVE vs TCP_PASSIVE	*/
/* RA: They will retain all values even at connection, so that they can		*/
/* RA: be restored at incoming RESET before reaching TESTABLISH, and so		*/
/* RA: that ACTIVE/PASSIVE connections can be recognized at that time.		*/
	connect->open.type = act_pass;
	connect->open.sock.rhost = rem_host;
	connect->open.sock.rport = rport;
	connect->open.sock.lhost = lcl_host;
	connect->open.sock.lport = lport;

	connect->remote_IP_address = rem_host;
	connect->remote_port       = rport;
	connect->local_IP_address  = lcl_host;
	connect->local_port        = lport;
	connect->flags             = 0;
	connect->mss               = (my_conf.mss < max_mss) ? my_conf.mss : max_mss;
	connect->mtu               = mtu;
	connect->tos               = tos;
	connect->ttl               = ttl;
	connect->info              = NULL;
	connect->reason            = 0;
	connect->net_error         = 0;

	connect->send.window		= window;
	connect->send.bufflen		= buff_used;
	connect->ca.cwnd			= MIN_MSS*2;
	connect->ca.sstresh			= MAX_WIND;
	connect->send.total        = 0;
	connect->send.count        = 0;
	connect->send.queue        = NULL;
	connect->send.ACK_time		= TIMER_now()-1500;
	connect->recve.window      = my_conf.rcv_window;
	connect->recve.reseq       = NULL;
	connect->recve.count       = 0;
	connect->recve.queue       = NULL;

	connect->rtrp.mode         = FALSE;
	connect->rtrp.srtt         = my_conf.def_rtt;
	connect->rtrn.mode         = FALSE;
	if	(connect->rtrp.srtt < 100L)
		connect->rtrp.srtt	= 100L;			/* limit SRTT to 100 ms minimum	*/
	else
		if	(connect->rtrp.srtt > 30000L)
			connect->rtrp.srtt = 30000L;	/* limit SRTT to 30 seconds max	*/
	connect->rtrn.timeout      = connect->rtrp.srtt<<1;
	connect->rtrn.start        = TIMER_now();
	connect->rtrn.backoff      = 0;

	connect->sema              = -1;		/* lock connection at first	*/
	connect->pending           = NULL;
	connect->result            = NULL;

	connect->next = root_list;					/* next -> old connections	*/
	root_list     = connect;					/* the new conn is now root	*/

	if	(act_pass == TCP_ACTIVE)				/* if connection is active	*/
	{	send_sync (connect);					/* prep a SYN packet		*/
		connect->state = TSYN_SENT;				/* state transition !!! 	*/
		my_conf.con_out++;
		do_output (connect);
	}
	else										/* if connection is passive	*/
		connect->state = TLISTEN;				/* state transition !!! */

	END_lock(connect);							/* unlock the connection	*/

	if	((error = connect->net_error) == 0)		/* if no network error yet	*/
		return (handle);						/* then return handle OK	*/

	protect_exec( connect, pe_unlink_conn );	/* unlink broken connection	*/
	KRfree (connect);							/* release connection RAM	*/
	PRTCL_release (handle);						/* release the handle too	*/
	return (error);								/* return the error code	*/
}
/*--------------------------*/
/* ends:	my_TCP_open		*/
/*--------------------------------------------------------------------------*/
/* RA: 'my_TCP_close' below has been edited a lot to allow proper closing	*/
/* RA: negotiation to be handled by interrupt driven routines (timer_work),	*/
/* RA: so that negotiation can continue even after user-specified timeout.	*/
/*--------------------------------------------------------------------------*/
int16	cdecl	my_TCP_close (connec, mode, result)

int16	connec, mode, *result;

{	CONNEC  *conn;
	int16   test_f, retval = E_NOROUTINE;
	int32	now = TIMER_now();	/* RA: start time for TCP_close operations	*/
	int16	*result_p;			/* RA: local ptr to avoid trashing stack	*/

	if ((conn = PRTCL_lookup (connec, & cn_vectors)) == NULL)
		return (E_BADHANDLE);

	if (mode >= 0)
	{	if	(conn->result != NULL)
		{	conn->result = NULL;
			return (E_NORMAL);
		}
		result_p = NULL;
		if (mode >= 1000)
			mode = 999;
	}		
	else
	{	result_p = conn->result = result;
	}
	if	(conn->flags & CLOSING)
	{	if	(conn->state != TCLOSED)
			close_self (conn, E_BADCLOSE);
		return (E_BADCLOSE);
	}

	conn->close.start   = now;
	conn->close.timeout = 1000000L;

	switch (conn->state)
	{	case TLISTEN :
		case TSYN_SENT :
			BEG_lock(conn,test_f,1000L);
			close_self (conn, E_NORMAL);
			conn->flags |= CLOSING;
			END_lock(conn);
			retval = E_NORMAL;
			break;
/*----*/
		case TSYN_RECV :
		case TESTABLISH :
		case TCLOSE_WAIT :
			BEG_lock(conn,test_f,1000L);
			conn->flags |= FIN_QUEUED;		/* Note that FIN is in queue	*/
			conn->send.count++;				/* increment send.count for FIN	*/
			conn->state = (conn->state == TCLOSE_WAIT) ? TLAST_ACK : TFIN_WAIT1;
			/* state transition !!! */
			do_output (conn);
			END_lock(conn);

			if ((conn->flags&DEFER) == 0  &&  mode >= 0)
			{	/* We must close connection fully, as client won't read on.	*/
				/* This is what TCP RFCs call 'Half duplex closing'.		*/

				if	(conn->recve.count)			/* any unread pending data?	*/
				{	retval = (mode) ? E_CNTIMEOUT : E_NORMAL;
					send_abort (conn);			/* Send abortive RST packet	*/
					my_conf.generic.stat_dropped++;	/* Note data dropped	*/
					close_self (conn, retval);			/* close connection	*/
					destroy_conn (conn);		/* Release connection RAM	*/
					return (retval);			/* Return errcode directly	*/
				}
				retval = E_NODATA;
				conn->result = & retval;	/* let interrupt produce result	*/

/* The line below tells interrupts to handle closing, but refuse reception.	*/
				conn->flags |= (CLOSING|DISCARD);

				while (retval==E_NODATA && TIMER_elapsed(now)<(1000L * mode))
					safe_appl_yield();
				conn->result = NULL;		/* RA: stop interrupt result	*/
				if	( retval == E_NODATA )
					retval = (mode) ? E_CNTIMEOUT : E_NORMAL;
			}
			else
			{	/* Here we only close outgoing half. Client can still read.	*/
				/* This is what TCP RFCs call 'Full duplex closing'.		*/

				conn->flags |= CLOSING;		/* tell interrupts CLOSING	*/
				if	(conn->flags&DEFER)
					retval = E_LOCKED;
				else
					retval = E_NODATA;
			}
			break;
/*----*/
		case TCLOSED :
/* This case is for when APP calls TCP_close after the connection has	*/
/* already been closed internally due to network errors or RST packets.	*/
			conn->flags &= ~CLOSING;		/* Skip interrupt closure	*/
			retval = (conn->reason == E_NORMAL) ? E_EOF : conn->reason;
			if	(result_p)
				*result_p = retval;		/* Return full duplex errcode	*/
			destroy_conn (conn);		/* Release all connection RAM	*/
			return (retval);			/* Return errcode directly too	*/
/*----*/
		case TFIN_WAIT1 :
		case TFIN_WAIT2 :
		case TCLOSING :
		case TLAST_ACK :
		case TTIME_WAIT :
/* We only reach this case for errors. Normally these states have the	*/
/* CLOSING flag already set, and so will be caught in an earlier test	*/
			close_self (conn, E_BADCLOSE);
			conn->flags |= CLOSING;
			retval = E_BADCLOSE;
			break;
	}	/* ends: switch(conn->state)	*/

/* destroy_conn is used at this level only for errors. Otherwise close	*/
/* completes under interrupt control, so timer_work calls destroy_conn.	*/

	if	(result_p)
		*result_p = retval;
	return (retval);
}
/*--------------------------*/
/* ends:	my_TCP_close	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_TCP_send (connec, buffer, length)

int16  connec, length;
void   *buffer;
{	CONNEC  *conn;
	NDB     *ndb, *walk;
	uint8   *data;
	int16   test_f, error;

	if ((conn = PRTCL_lookup (connec, & cn_vectors)) == NULL)
		return (E_BADHANDLE);

	error = conn->net_error;

	if (error < 0)
	{	conn->net_error = 0;
		return (error);
	}

	switch (categorize (conn))
	{
	case C_FIN :
	case C_CLSD :      return (E_EOF);
	case C_LISTEN :    return (E_LISTEN);
	case C_DEFAULT :   return (E_NODATA);
	}

	if (conn->send.bufflen - conn->send.total < length)
		return (E_OBUFFULL);

	if ((ndb = (NDB *) KRmalloc (sizeof (NDB))) == NULL)
		return (E_NOMEM);

	if ((data = (uint8 *) KRmalloc (length)) == NULL)
	{	KRfree (ndb);
		return (E_NOMEM);
	}

	ndb->ptr  = ndb->ndata = data;
	ndb->len  = length;
	ndb->next = NULL;
	memcpy (data, buffer, length);

	IF_lock(conn,test_f,1000L)
	{	KRfree (data);
		KRfree (ndb);
		return (E_LOCKED);
	}
	if (conn->send.queue)
	{	for (walk = conn->send.queue; walk->next; walk = walk->next)
			;
		walk->next = ndb;
	}
	else
	{	conn->send.queue = ndb;
	}

	conn->send.count += length;
	conn->send.total += length;
	do_output (conn);
	END_lock(conn);
	return (E_NORMAL);
}
/*--------------------------*/
/* ends:	my_TCP_send		*/
/*--------------------------------------------------------------------------*/
int16	cdecl	my_TCP_wait_state (connec, state, timeout)

int16	connec, state, timeout;

{	CONNEC  *conn;
	int16   err;
	uint32  timer, time_out;

	if ((conn = PRTCL_lookup (connec, & cn_vectors)) == NULL)
		return (E_BADHANDLE);

	err = conn->net_error;
	if (err < 0)
	{	conn->net_error = 0;
		return (err);
	}

	if	((time_out = 1000L * timeout) != 0  &&  (conn->flags&DEFER))
		return (E_PARAMETER);

	timer    = TIMER_now();
	while (conn->state != state)
	{	if (TIMER_elapsed (timer) >= time_out)
			return (E_CNTIMEOUT);
		safe_appl_yield();
		err = conn->net_error;		/* RA: network errors must be tested	*/
		if (err < 0)				/* RA: inside the loop as well, since	*/
		{	conn->net_error = 0;	/* RA: ICMP error messages may come in	*/
			return (err);			/* RA: at any time.						*/
		}
	}
	return (E_NORMAL);
}
/*------------------------------*/
/* ends:	my_TCP_wait_state	*/
/*--------------------------------------------------------------------------*/
int16	cdecl	my_TCP_ack_wait (connec, timeout)

int16	connec, timeout;
{	CONNEC  *conn;
	int16   err;
	uint32  timer;

	if ((conn = PRTCL_lookup (connec, & cn_vectors)) == NULL)
		return (E_BADHANDLE);

	err = conn->net_error;
	if (err < 0)
	{	conn->net_error = 0;
		return (err);
	}

	if	(timeout  &&  (conn->flags&DEFER))
		return (E_PARAMETER);

	timer = TIMER_now();
	while (conn->send.total > 0)
	{	if (TIMER_elapsed (timer) >= timeout)	/* RA: needs '>=' not '>'	*/
			return (E_CNTIMEOUT);
		safe_appl_yield();
		err = conn->net_error;		/* RA: network errors must be tested	*/
		if (err < 0)				/* RA: inside the loop as well, since	*/
		{	conn->net_error = 0;	/* RA: ICMP error messages may come in	*/
			return (err);			/* RA: at any time.						*/
		}
	}
	return (E_NORMAL);
}
/*--------------------------*/
/* ends:	my_TCP_ack_wait	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_TCP_info (connec, block)

int16  connec;
TCPIB  *block;
{	CONNEC  *conn;
	uint32	request;
	int16   err;

	if	((conn = PRTCL_lookup (connec, & cn_vectors)) == NULL)
		return (E_BADHANDLE);
	if	(	(int32)block <= 0
		||	(request = block->request) > TCPI_mask
		)
		return (E_PARAMETER);

	if	(request & TCPI_defer)
		conn->flags |= DEFER;
	if	(request & TCPI_state)
		block->state = conn->state;
	if	(request & TCPI_unacked)
		block->unacked = conn->send.count;	/* unacked logical seq_nums */
	if	(request & TCPI_srtt)
		block->srtt = conn->rtrp.srtt;

	err = conn->net_error;
	if (err < 0)
	{	conn->net_error = 0;
		return (err);
	}

	return (TCPI_bits);
}
/*--------------------------*/
/* ends:	my_TCP_info		*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_CNkick (connec)
void  *connec;
{	CONNEC  *conn = connec;
	int16   test_f, error;

	if	((error = poll_receive (connec)) < 0)
		return (error);

	BEG_lock(conn,test_f,1000L);
	conn->rtrn.mode    = FALSE;
	conn->rtrn.backoff = 0;
	conn->rtrn.start   = TIMER_now();
	conn->rtrn.timeout = conn->rtrp.srtt<<1;

	conn->flags |= FORCE;

	do_output (conn);
	END_lock(conn);
	return (E_NORMAL);
}
/*----------------------*/
/* ends:	my_CNkick	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_CNbyte_count (connec)

void  *connec;
{	CONNEC  *conn = connec;
	int16   error;

	if	((error = poll_receive (connec)) < 0)
		return (error);

	switch (categorize (conn))
	{
	case C_END :
	case C_CLSD :
		return (E_EOF);
	case C_LISTEN :
		return (E_LISTEN);
	case C_DEFAULT :
		return (E_NODATA);
	}
	return ((int16) conn->recve.count);
}
/*------------------------------*/
/* ends:	my_CNbyte_count		*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_CNget_char (connec)
void  *connec;
{	CONNEC  *conn = connec;
	int16   test_f, error, length = 1;
	uint8   character;

	if	((error = poll_receive (connec)) < 0)
		return (error);

	switch (categorize (conn))
	{
	case C_END :
	case C_CLSD :		return (E_EOF);
	case C_LISTEN :		return (E_LISTEN);
	case C_DEFAULT :	return (E_NODATA);
	}
	BEG_lock(conn,test_f,1000L);
	receive (conn, & character, & length, TRUE);
	END_lock(conn);
	return ((length) ? (int16) character : E_NODATA);
}
/*--------------------------*/
/* ends:	my_CNget_char	*/
/*--------------------------------------------------------------------------*/
NDB *  cdecl  my_CNget_NDB (connec)
void  *connec;
{	CONNEC  *conn = connec;
	NDB    *ndb;
	int16  test_f, flag = categorize (connec);

	if (poll_receive (connec) < 0)
		return (NULL);

	if (flag != C_READY && flag != C_FIN)
		return (NULL);

	flag = -1;
	IF_lock(conn,test_f,1000L)
		return((NDB *) E_LOCKED);
	receive (conn, (uint8 *) & ndb, & flag, FALSE);
	END_lock(conn);

	if (flag < 0)
		return (NULL);
	else
	{	ndb->next = NULL;
		return (ndb);
	}
}
/*--------------------------*/
/* ends:	my_CNget_NDB	*/
/*--------------------------------------------------------------------------*/
int16  cdecl  my_CNget_block (connec, buffer, length)
void   *connec;
void   *buffer;
int16  length;
{	CONNEC  *conn = connec;
	int16   test_f, error;

	if ((error = poll_receive (connec)) < 0)
		return (error);

	switch (categorize (conn))
	{
	case C_END :
	case C_CLSD :      return (E_EOF);
	case C_LISTEN :    return (E_LISTEN);
	case C_DEFAULT :   return (E_NODATA);
	}

	if (length <= 0)
		return (0);

	if (length > conn->recve.count)
		return (E_NODATA);

	BEG_lock(conn,test_f,1000L);
	receive (conn, buffer, & length, FALSE);
	END_lock(conn);
	return (length);
}
/*--------------------------*/
/* ends:	my_CNget_block	*/
/*--------------------------------------------------------------------------*/
CIB *  cdecl  my_CNgetinfo (void *conn)
{	CIB     *cib;
	char	*test_s;
	static	char	*masque_s = "Masquerade";

	if (((CONNEC *)conn)->info == NULL)
	{	if ((((CONNEC *)conn)->info = (CIB *) KRmalloc (sizeof (CIB))) == NULL)
			return (NULL);
		((CONNEC *)conn)->info->status = 0;
	}
	cib = ((CONNEC *)conn)->info;

	cib->protocol = P_TCP;
	cib->address.lport = ((CONNEC *)conn)->local_port;
	cib->address.rport = ((CONNEC *)conn)->remote_port;
	cib->address.rhost = ((CONNEC *)conn)->remote_IP_address;
	if	((cib->address.lhost = ((CONNEC *)conn)->local_IP_address) == 0L)
	{	test_s=getvstr("FORCED_IP");
		if	(strlen(test_s) > 6)
			cib->address.lhost = get_ip_addr( test_s );
		else
			if	( query_port( masque_s ) )
				cntrl_port( masque_s, (uint32)(&cib->address.lhost), CTL_MASQUE_GET_REALIP );
			else
				if	( cib->address.rhost )
					PRTCL_get_parameters( cib->address.rhost, &cib->address.lhost, NULL, NULL);
				else
					PRTCL_get_parameters( 0x0A00FF49UL, &cib->address.lhost, NULL, NULL);
	}
	return (cib);
}
/*--------------------------*/
/* Ends:	my_CNgetinfo	*/
/*--------------------------------------------------------------------------*/
uint32	get_ip_addr (char *field)
{	uint32  ip_1, ip_2, ip_3, ip_4;
	char    *work = field;

	ip_1 = atoi (work);
	if ((work = strchr (work, '.')) == NULL)
		return (0);
	ip_2 = atoi (++work);
	if ((work = strchr (work, '.')) == NULL)
		return (0);
	ip_3 = atoi (++work);
	if ((work = strchr (work, '.')) == NULL)
		return (0);
	ip_4 = atoi (++work);

	return( (ip_1 << 24) | (ip_2 << 16) | (ip_3 << 8) | ip_4 );
}
/*----------------------*/
/* Ends:	get_ip_addr */
/*--------------------------------------------------------------------------*/
int16  cdecl  my_CNgets (connec, buffer, length, delimiter)

void   *connec;
int16  length;
char   *buffer, delimiter;

{	CONNEC  *conn = connec;
	NDB     *walk;
	int16   test_f, error, count, amount;
	uint8   *search;

	if ((error = poll_receive (connec)) < 0)
		return (error);

	switch (categorize (conn))
	{
	case C_END :
	case C_CLSD :
		return (E_EOF);
	case C_LISTEN :
		return (E_LISTEN);
	case C_DEFAULT :
		return (E_NODATA);
	}

	if (conn->recve.count == 0)
		return (E_NODATA);

	if (length <= 1)
		return (E_BIGBUF);

	BEG_lock(conn,test_f,1000L);
	for (walk = conn->recve.queue, amount = 0; walk != NULL; walk = walk->next)
	{	search = walk->ndata;
		for (count = 0; count < walk->len && amount < length; count++, amount++)
		{	if (*search++ == delimiter)
			{	amount++;
				receive (conn, buffer, & amount, FALSE);
				END_lock(conn);
				buffer[--amount] = '\0';
				return (amount);
			}
		}
		if (amount == length)
		{	END_lock(conn);
			return (E_BIGBUF);
		}
	}
	END_lock(conn);
	return (E_NODATA);
}
/*----------------------*/
/* ends:	my_CNgets	*/
/*--------------------------------------------------------------------------*/
/* End of file:	TCP.C														*/
/*--------------------------------------------------------------------------*/
