/*
Copyright 2001-2018 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 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 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 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 LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

//
//	C replacement for L4Code.asm
//
#define Kernel

#define _CRT_SECURE_NO_DEPRECATE 


#pragma data_seg("_BPQDATA")

#include "time.h"
#include "stdio.h"
#include <fcntl.h>					 

#include "CHeaders.h"

extern BPQVECSTRUC BPQHOSTVECTOR[];
#define BPQHOSTSTREAMS 64
#define IPHOSTVECTOR BPQHOSTVECTOR[BPQHOSTSTREAMS + 3]

VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session);
VOID SENDL4DISC(TRANSPORTENTRY * Session);
int C_Q_COUNT(VOID * Q);
TRANSPORTENTRY * SetupSessionForL2(struct _LINKTABLE * LINK);
VOID InformPartner(struct _LINKTABLE * LINK);
VOID IFRM150(TRANSPORTENTRY * Session, PDATAMESSAGE Buffer);
VOID SendConNAK(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG);
BOOL FINDCIRCUIT(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY ** REQL4, int * NewIndex);
int GETBUSYBIT(TRANSPORTENTRY * L4);
BOOL cATTACHTOBBS(TRANSPORTENTRY * Session, UINT Mask, int Paclen, int * AnySessions);
VOID SETUPNEWCIRCUIT(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, 
		 TRANSPORTENTRY * L4, char * BPQPARAMS, int ApplMask, int * BPQNODE);
extern char * ALIASPTR;
VOID SendConACK(struct _LINKTABLE * LINK, TRANSPORTENTRY * L4, L3MESSAGEBUFFER * L3MSG, BOOL BPQNODE, UINT Applmask, UCHAR * ApplCall);
VOID L3SWAPADDRESSES(L3MESSAGEBUFFER * L3MSG);
VOID L4TIMEOUT(TRANSPORTENTRY * L4);
struct DEST_LIST * CHECKL3TABLES(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg);
int CHECKIFBUSYL4(TRANSPORTENTRY * L4);
VOID AUTOTIMER();
VOID NRRecordRoute(UCHAR * Buff, int Len);
VOID REFRESHROUTE(TRANSPORTENTRY * Session);
VOID ACKFRAMES(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY * L4, int NR);
VOID SENDL4IACK(TRANSPORTENTRY * Session);
VOID CHECKNEIGHBOUR(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg);
VOID ProcessINP3RIF(struct ROUTE * Route, UCHAR * ptr1, int msglen, int Port);
VOID ProcessRTTMsg(struct ROUTE * Route, struct _L3MESSAGEBUFFER * Buff, int Len, int Port);
VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, UCHAR * ApplCall);
void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode);

extern UINT APPLMASK;

extern BOOL LogL4Connects;
extern BOOL LogAllConnects;

// L4 Flags Values

#define DISCPENDING	8		// SEND DISC WHEN ALL DATA ACK'ED

extern APPLCALLS * APPL;

VOID NETROMMSG(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG)
{
	//	MAKE SURE PID IS 0CF - IN CASE SOMEONE IS SENDING L2 STUFF ON WHAT 
	//	WE THINK IS A _NODE-_NODE LINK

	struct DEST_LIST * DEST;

	int n;

	if (L3MSG->L3PID != 0xCF)
	{
		ReleaseBuffer(L3MSG);
		return;
	}

	if (LINK->NEIGHBOUR == 0)
	{
		// NO ROUTE ASSOCIATED WITH THIS CIRCUIT - SET ONE UP

		CHECKNEIGHBOUR(LINK, L3MSG);

		if (LINK->NEIGHBOUR == 0)
		{
			//	COULDNT SET UP NEIGHBOUR - CAN ONLY THROW IT AWAY

			ReleaseBuffer(L3MSG);
			return;
		}
	}

	// See if a INP3 RIF (first Byte 0xFF)

	if (L3MSG->L3SRCE[0] == 0xff)
	{
		// INP3

		ProcessINP3RIF(LINK->NEIGHBOUR, &L3MSG->L3SRCE[1], L3MSG->LENGTH - 9, L3MSG->Port);
		ReleaseBuffer(L3MSG);
		return;
	}

	APPLMASK = 0;		//	NOT APPLICATION 
	
	if (NODE)				// _NODE SUPPORT INCLUDED?
	{

		if (CompareCalls(L3MSG->L3DEST, MYCALL))
		{
			FRAMEFORUS(LINK, L3MSG, APPLMASK, MYCALL);
			return;
		}
	}
	
	//	CHECK ALL L4 CALLS

	APPLMASK = 1;
	ALIASPTR = &CMDALIAS[0][0];

	n = NumberofAppls;

	APPL = APPLCALLTABLE;

	while (n--)
	{
		if (APPL->APPLCALL[0] > 0x40)		// Valid ax.25 addr
		{
			if (CompareCalls(L3MSG->L3DEST, APPL->APPLCALL))
			{
				FRAMEFORUS(LINK, L3MSG, APPLMASK, APPL->APPLCALL);
				return;
			}
		}
		APPLMASK <<= 1;
		ALIASPTR += ALIASLEN;
		APPL++;
	}

	//	IS IT INP3 (L3RTT)

	if (CompareCalls(L3MSG->L3DEST, L3RTT))
	{
		ProcessRTTMsg(LINK->NEIGHBOUR, L3MSG, L3MSG->LENGTH, L3MSG->Port);
		return;
	}

	L3MSG->L3TTL--;
	
	if (L3MSG->L3TTL == 0)
	{
		ReleaseBuffer(L3MSG);
		return;
	}

	//	If it is a record route frame we should add our call to the list before sending on

	if (L3MSG->L4FLAGS == 0 && L3MSG->L4ID == 1 && L3MSG->L4INDEX == 0)
	{
		//	Add our call on end, and increase count

		int Len = L3MSG->LENGTH;
		int Count;

		UCHAR * ptr = (UCHAR *)L3MSG;

		if (Len < 248)
		{
			ptr += (Len - 1);

			Count = (*(ptr++)) & 0x7F;		// Mask End of Route

			memcpy(ptr, MYCALL, 7);

			ptr += 7;

			Count--;
			*(ptr) = Count;

			if (Count)
				L3MSG->LENGTH += 8;
		}
	}

	if (L3MSG->L3TTL > L3LIVES)
		L3MSG->L3TTL = L3LIVES;		// ENFORCE LIMIT ON ALL FRAMES SENT

	if (FindDestination(L3MSG->L3DEST, &DEST) == 0)
	{
		ReleaseBuffer(L3MSG);			// CANT FIND DESTINATION
		return;
	}

	//	IF MESSAGE ORIGINTED HERE, THERE MUST BE A ROUTING LOOP - 
	//   THERE IS LITTLE POINT SENDING IT OVER THE SAME ROUTE AGAIN,
	//   SO SET ANOTHER ROUTE ACTIVE IF POSSIBLE

	if (CompareCalls(L3MSG->L3SRCE, MYCALL) || CompareCalls(L3MSG->L3SRCE, APPLCALLTABLE->APPLCALL))
	{
		//	MESSAGE HAS COME BACK TO ITS STARTING POINT - ACTIVATE ANOTHER ROUTE,
		// UNLESS THERE IS ONLY ONE, IN WHICH CASE DISCARD IT

		if (DEST->NRROUTE[1].ROUT_NEIGHBOUR == 0)	// No more routes
		{
			ReleaseBuffer(L3MSG);
			return;
		}

		DEST->DEST_ROUTE++;

		if (DEST->DEST_ROUTE == 4)		// TO NEXT 
			DEST->DEST_ROUTE  = 1;		// TRY TO ACTIVATE FIRST
	}

	//	IF CURRENT ROUTE IS BACK THE WAY WE CAME, THEN ACTIVATE
	//ANOTHER (IF POSSIBLE). 

	if (DEST->DEST_ROUTE)
	{
		if (DEST->NRROUTE[DEST->DEST_ROUTE -1].ROUT_NEIGHBOUR == LINK->NEIGHBOUR)
		{
			//	Current ROUTE IS BACK THE WAY WE CAME - ACTIVATE ANOTHER IF POSSIBLE

			DEST->DEST_ROUTE++;
			if (DEST->DEST_ROUTE == 4)
				DEST->DEST_ROUTE =1;
		}
		goto NO_PROBLEM;
	}
	else
	{
		//	DONT HAVE AN ACTIVE ROUTE

		if (DEST->NRROUTE[0].ROUT_NEIGHBOUR == LINK->NEIGHBOUR)
		{
			//	FIRST ROUTE IS BACK THE WAY WE CAME - ACTIVATE ANOTHER IF POSSIBLE

			DEST->DEST_ROUTE = 2;	// WILL BE RESET BY L3 CODE IF THERE IS NOT OTHER ROUTE
		}
	}

NO_PROBLEM:

	CHECKL3TABLES(LINK, L3MSG);

//	EVEN IF WE CANT PUT ORIGINATING NODE INTO OUR TABLES, PASS MSG ON
//	   ANYWAY - THE FINAL TARGET MAY HAVE ANOTHER WAY BACK


	C_Q_ADD(&DEST->DEST_Q, L3MSG);

	L3FRAMES++;
}

VOID SENDL4MESSAGE(TRANSPORTENTRY * L4, struct DATAMESSAGE * Msg)
{
	L3MESSAGEBUFFER * L3MSG;
	struct DEST_LIST * DEST;
	struct DATAMESSAGE * Copy;
	int FRAGFLAG = 0;
	int Len;

	// These make it simpler to understand code

#define NullPKTLen  4 + sizeof(void *)			// 4 is Port, Len, PID
#define MaxL4Len  236 + 4 + sizeof(void *)		// Max NETROM Size


	if (Msg->LENGTH == NullPKTLen)
	{
		//	NO DATA - DISCARD IT

		ReleaseBuffer(Msg);
		return;
	}

	L3MSG = GetBuff();

	if (L3MSG == 0)
	{
		//	DONT THINK WE SHOULD GET HERE, UNLESS _QCOUNT IS CORRUPT,
		//	BUT IF WE DO, SHOULD RETURN MSG TO FREE Q - START TIMER, AND
		//	DROP THROUGH TO RELBUFF

		L4->L4TIMER = L4->SESSIONT1;

		ReleaseBuffer(Msg);
		return;
	}

	L3MSG->L3PID = 0xCF;			// NET MESSAGE

	memcpy(L3MSG->L3SRCE, L4->L4MYCALL, 7);

	DEST = L4->L4TARGET.DEST;
	memcpy(L3MSG->L3DEST, DEST->DEST_CALL, 7);

	L3MSG->L3TTL = L3LIVES;
	
	L3MSG->L4ID = L4->FARID;
	L3MSG->L4INDEX = L4->FARINDEX;

	L3MSG->L4TXNO = L4->TXSEQNO;

	//	SET UP RTT TIMER

	if (L4->RTT_TIMER == 0)
	{
		L4->RTT_SEQ = L4->TXSEQNO;

		L4->RTT_TIMER = GetTickCount();
	}

	L4->TXSEQNO++;
	

	L4->L4LASTACKED	= L3MSG->L4RXNO = L4->RXSEQNO;		// SAVE LAST NUMBER ACKED

	// SEE IF CROSSSESSION IS BUSY

	GETBUSYBIT(L4);							// Sets BUSY in NAKBITS if Busy

	L3MSG->L4FLAGS = L4INFO | L4->NAKBITS;

	L4->L4TIMER = L4->SESSIONT1;			// SET TIMER
	L4->L4ACKREQ = 0;						// CANCEL ACK NEEDED

	Len = Msg->LENGTH;

	if (Len > MaxL4Len)							// 236 DATA + 8 HEADER
	{
		//	MUST FRAGMENT MESSAGE

		L3MSG->L4FLAGS |= L4MORE;
		FRAGFLAG = 1;

		Len = MaxL4Len;
	}
	
	Len += 20;								// L3/4 Header

	L3MSG->LENGTH = Len;

	Len -= (20 + NullPKTLen);				// Actual Data

	memcpy(L3MSG->L4DATA, Msg->L2DATA, Len);

	//	CREATE COPY FOR POSSIBLE RETRY

	Copy = GetBuff();

	if (Copy == 0)
	{
		// SHOULD NEVER HAPPEN
		
		ReleaseBuffer(Msg);
		return;
	}

	memcpy(Copy, L3MSG, L3MSG->LENGTH);
	
	// If we have fragmented, we should adjust length, or retry will send too much
	//	(bug in .asm code)

	if (FRAGFLAG)
		Copy->LENGTH = MaxL4Len;

	C_Q_ADD(&L4->L4HOLD_Q, Copy);

	C_Q_ADD(&DEST->DEST_Q, L3MSG);

	DEST->DEST_COUNT++;				// COUNT THEM

	L4FRAMESTX++;

	if (FRAGFLAG)
	{
		//	MESSAGE WAS TOO BIG - ADJUST IT AND LOOP BACK

		Msg->LENGTH -= 236;

		memmove(Msg->L2DATA, &Msg->L2DATA[236], Msg->LENGTH - NullPKTLen);
	
		SENDL4MESSAGE(L4, Msg);
	}
}


int GETBUSYBIT(TRANSPORTENTRY * L4)
{
	//	SEE IF CROSSSESSION IS BUSY

	L4->NAKBITS &= ~L4BUSY;		// Clear busy
			
	L4->NAKBITS |= CHECKIFBUSYL4(L4);		// RETURNS AL WITH BUSY BIT SET IF CROSSSESSION IS BUSY
	
	return L4->NAKBITS;
}

VOID Q_IP_MSG(PDATAMESSAGE Buffer)
{
	if (IPHOSTVECTOR.HOSTAPPLFLAGS & 0x80)
	{
		//	CHECK WE ARENT USING TOO MANY BUFFERS
		
		if (C_Q_COUNT(&IPHOSTVECTOR.HOSTTRACEQ) > 20)
			ReleaseBuffer(Q_REM((void *)&IPHOSTVECTOR.HOSTTRACEQ));

		C_Q_ADD(&IPHOSTVECTOR.HOSTTRACEQ, Buffer);
		return;
	}

	ReleaseBuffer(Buffer);
}

VOID SENDL4CONNECT(TRANSPORTENTRY * Session)
{
	PL3MESSAGEBUFFER MSG = (PL3MESSAGEBUFFER)GetBuff();
	struct DEST_LIST * DEST = Session->L4TARGET.DEST;

	if (MSG == NULL)
		return;

	if (DEST->DEST_CALL[0] == 0)
	{
		Debugprintf("Trying to send L4CREEQ to NULL Destination");
		ReleaseBuffer(MSG);
		return;
	}

	MSG->L3PID = 0xCF;			// NET MESSAGE
	
	memcpy(MSG->L3SRCE, Session->L4MYCALL, 7);
	memcpy(MSG->L3DEST, DEST->DEST_CALL, 7);

	MSG->L3TTL = L3LIVES;

	MSG->L4INDEX = Session->CIRCUITINDEX;
	MSG->L4ID = Session->CIRCUITID;
	MSG->L4TXNO = 0;
	MSG->L4RXNO = 0;
	MSG->L4FLAGS = L4CREQ;

	MSG->L4DATA[0] = L4DEFAULTWINDOW;	// PROPOSED WINDOW

	memcpy(&MSG->L4DATA[1], Session->L4USER, 7);		// ORIG CALL
	memcpy(&MSG->L4DATA[8], Session->L4MYCALL, 7);
	
	Session->L4TIMER = Session->SESSIONT1;				// START TIMER
	memcpy(&MSG->L4DATA[15], &Session->SESSIONT1, 2);	// AND PUT IN MSG

	MSG->LENGTH = (int)(&MSG->L4DATA[17] - (UCHAR *)MSG);

	if (Session->SPYFLAG)
	{
		MSG->L4DATA[17] = 'Z';							// ADD SPY ON BBS FLAG
		MSG->LENGTH++;
	}
	C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG);
}

void RETURNEDTONODE(TRANSPORTENTRY * Session)
{
	//	SEND RETURNED TO ALIAS:CALL

	struct DATAMESSAGE * Msg = (struct DATAMESSAGE *)GetBuff();
	char Nodename[20];

	if (Msg)
	{
		Msg->PID = 0xf0;

		Nodename[DecodeNodeName(MYCALLWITHALIAS, Nodename)] = 0;		// null terminate

		Msg->LENGTH = (USHORT)sprintf(&Msg->L2DATA[0], "Returned to Node %s\r", Nodename) + 4 + sizeof(void *);
		C_Q_ADD(&Session->L4TX_Q, (UINT *)Msg);
		PostDataAvailable(Session);
	}
}


extern void * BUFFER;

VOID L4BG()
{
	// PROCESS DATA QUEUED ON SESSIONS

	int n = MAXCIRCUITS;
	TRANSPORTENTRY * L4 = L4TABLE;
	int MaxLinks = MAXLINKS;
	UCHAR Outstanding;
	struct DATAMESSAGE * Msg;
	struct PORTCONTROL * PORT;
	struct _LINKTABLE * LINK;
	int Msglen, Paclen;

	while (n--)
	{
		if (L4->L4USER[0] == 0)
		{
			L4++;
			continue;
		}
		while (L4->L4TX_Q)	
		{
			if (L4->L4CIRCUITTYPE & BPQHOST)
				break;							// Leave on TXQ

			//	SEE IF BUSY - NEED DIFFERENT TESTS FOR EACH SESSION TYPE

			if (L4->L4CIRCUITTYPE & SESSION)
			{
				//	L4 SESSION - WILL NEED BUFFERS FOR SAVING COPY,
				//	 AND POSSIBLY FRAGMENTATION

				if (QCOUNT < 15)
					break;

				if (L4->FLAGS & L4BUSY)
				{
					//	CHOKED - MAKE SURE TIMER IS RUNNING

					if (L4->L4TIMER == 0)
						L4->L4TIMER = L4->SESSIONT1;

					break;
				}
				
				//	CHECK WINDOW

				Outstanding = L4->TXSEQNO - L4->L4WS;	// LAST FRAME ACKED - GIVES NUMBER OUTSTANING

				//	MOD 256, SO SHOULD HANDLE WRAP??

				if (Outstanding > L4->L4WINDOW)
					break;

			}
			else if (L4->L4CIRCUITTYPE & L2LINK)
			{
				// L2 LINK 

				LINK = L4->L4TARGET.LINK;

				if (COUNT_AT_L2(LINK) > 8)
					break;
			}

			// Not busy, so continue

			L4->L4KILLTIMER = 0;		//RESET SESSION TIMEOUTS

			if(L4->L4CROSSLINK)
				L4->L4CROSSLINK->L4KILLTIMER = 0;

			Msg = Q_REM((void *)&L4->L4TX_Q);

			if (L4->L4CIRCUITTYPE & PACTOR)
			{
				//	PACTOR-like - queue to Port

				//  Stream Number is in KAMSESSION

				Msg->PORT = L4->KAMSESSION;
				PORT = L4->L4TARGET.PORT;

				C_Q_ADD(&PORT->PORTTX_Q, Msg);

				continue;
			}
			//	non-pactor

			//	IF CROSSLINK, QUEUE TO NEIGHBOUR, ELSE QUEUE ON LINK ENTRY

			if (L4->L4CIRCUITTYPE & SESSION)
			{
				SENDL4MESSAGE(L4, Msg);
				ReleaseBuffer(Msg);
				break;
			}

			LINK = L4->L4TARGET.LINK;

			// If we want to enforce PACLEN this may be a good place to do it

			Msglen = Msg->LENGTH - (MSGHDDRLEN + 1); //Dont include PID
			Paclen = L4->SESSPACLEN;

			if (Paclen == 0)
				Paclen = 256;

			if (Msglen > Paclen)
			{
				// Fragment it. 
				// Is it best to send Paclen packets then short or equal length?
				// I think equal length;

				int Fragments = (Msglen + Paclen - 1) / Paclen;
				int Fraglen = Msglen / Fragments;
						
				if ((Msglen & 1))		// Odd
					Fraglen ++;

				while (Msglen > Fraglen)
				{
					PDATAMESSAGE Fragment = GetBuff();

					if (Fragment == NULL)
						break;				// Cant do much else

					Fragment->PORT = Msg->PORT;
					Fragment->PID = Msg->PID;
					Fragment->LENGTH = Fraglen + (MSGHDDRLEN + 1);

					memcpy(Fragment->L2DATA, Msg->L2DATA, Fraglen);

					C_Q_ADD(&LINK->TX_Q, Fragment);

					memcpy(Msg->L2DATA, &Msg->L2DATA[Fraglen], Msglen - Fraglen);
					Msglen -= Fraglen;
					Msg->LENGTH -= Fraglen;
				}

				// Drop through to send last bit

			}
	
			C_Q_ADD(&LINK->TX_Q, Msg);
		}

		// if nothing on TX Queue If there is stuff on hold queue, timer must be running

//		if (L4->L4TX_Q == 0 && L4->L4HOLD_Q)
		if (L4->L4HOLD_Q)
		{
			if (L4->L4TIMER == 0)
			{
				L4->L4TIMER = L4->SESSIONT1;
			}
		}
		
		// now check for rxed frames

		while(L4->L4RX_Q)
		{
			Msg = Q_REM((void *)&L4->L4RX_Q);

			IFRM150(L4, Msg);

			if (L4->L4USER[0] == 0)		// HAVE JUST CLOSED SESSION!	
				goto NextSess;
		}

		//	IF ACK IS PENDING, AND WE ARE AT RX WINDOW, SEND ACK NOW

		Outstanding = L4->RXSEQNO - L4->L4LASTACKED;
		if (Outstanding >= L4->L4WINDOW)
			SENDL4IACK(L4);
NextSess:
		L4++;
	}
}

VOID CLEARSESSIONENTRY(TRANSPORTENTRY * Session)
{

	//	RETURN ANY QUEUED BUFFERS TO FREE QUEUE

	while (Session->L4TX_Q)
		ReleaseBuffer(Q_REM((void *)&Session->L4TX_Q));

	while (Session->L4RX_Q)
		ReleaseBuffer(Q_REM((void *)&Session->L4RX_Q));

	while (Session->L4HOLD_Q)
		ReleaseBuffer(Q_REM((void *)&Session->L4HOLD_Q));

	if (C_Q_COUNT(&Session->L4RESEQ_Q) > Session->L4WINDOW)
	{
		Debugprintf("Corrupt RESEQ_Q Q Len %d Free Buffs %d", C_Q_COUNT(&Session->L4RESEQ_Q), QCOUNT);
		Session->L4RESEQ_Q = 0;
	}

	while (Session->L4RESEQ_Q)
		ReleaseBuffer(Q_REM((void *)&Session->L4RESEQ_Q));

	if (Session->PARTCMDBUFFER)
		ReleaseBuffer(Session->PARTCMDBUFFER);

	memset(Session, 0, sizeof(TRANSPORTENTRY));
}

VOID CloseSessionPartner(TRANSPORTENTRY * Session)
{
	//	SEND CLOSE TO CROSSLINKED SESSION AND CLEAR LOCAL SESSION

	if (Session == NULL)
		return;

	if (Session->L4CROSSLINK)
		CLOSECURRENTSESSION(Session->L4CROSSLINK);

	CLEARSESSIONENTRY(Session);
}


VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session)
{
	MESSAGE * Buffer;
	struct _LINKTABLE * LINK;

//	SHUT DOWN SESSION AND UNLINK IF CROSSLINKED

	if (Session == NULL)
		return;

	Session->L4CROSSLINK = NULL;

	//	IF STAY FLAG SET, KEEP SESSION, AND SEND MESSAGE

	if (Session->STAYFLAG)
	{
		RETURNEDTONODE(Session);
		Session->STAYFLAG = 0;			// Only do it once
		return;
	}

	if (Session->L4CIRCUITTYPE & BPQHOST)
	{
		//	BPQ HOST MODE SESSION - INDICATE STATUS CHANGE

		PBPQVECSTRUC HOST = Session->L4TARGET.HOST;
		HOST->HOSTSESSION = 0;
		HOST->HOSTFLAGS |= 3;		/// State Change

		PostStateChange(Session);
		CLEARSESSIONENTRY(Session);
		return;
	}

	if (Session->L4CIRCUITTYPE & PACTOR)
	{
		//	PACTOR-type (Session linked to Port)

		struct _EXTPORTDATA * EXTPORT = Session->L4TARGET.EXTPORT;

		// If any data is queued, move it to the port entry, so it can be sent before the disconnect
		
		while (Session->L4TX_Q)
		{
			Buffer = Q_REM((void *)&Session->L4TX_Q);
			EXTPORT->PORTCONTROL.PORTTXROUTINE(EXTPORT, Buffer);
		}

		EXTPORT->ATTACHEDSESSIONS[Session->KAMSESSION] = NULL;

		CLEARSESSIONENTRY(Session);
		return;
	}

	if (Session->L4CIRCUITTYPE & SESSION)
	{
		//	L4 SESSION TO CLOSE

		if (Session->L4HOLD_Q || Session->L4TX_Q)	// WAITING FOR ACK or MORE TO SEND - SEND DISC LATER
		{
			Session->FLAGS |= DISCPENDING;			// SEND DISC WHEN ALL DATA ACKED
			return;
		}

		SENDL4DISC(Session);
		return;
	}

	//	Must be LEVEL 2 SESSION TO CLOSE

	//	COPY ANY PENDING DATA TO L2 TX Q, THEN GET RID OF SESSION

	LINK = Session->L4TARGET.LINK;

	if (LINK == NULL)			// just in case!
	{
		CLEARSESSIONENTRY(Session);
		return;
	}

	while (Session->L4TX_Q)
	{
		Buffer = Q_REM((void *)&Session->L4TX_Q);
		C_Q_ADD(&LINK->TX_Q, Buffer);
	}

	//	NOTHING LEFT AT SESSION LEVEL
	
	LINK->CIRCUITPOINTER = NULL;	// CLEAR REVERSE LINK

	if ((LINK->LINKWS != LINK->LINKNS) || LINK->TX_Q)
	{
		// STILL MORE TO SEND - SEND DISC LATER
		
		LINK->L2FLAGS |= DISCPENDING;	// SEND DISC WHEN ALL DATA ACKED
	}
	else
	{
		//	NOTHING QUEUED - CAN SEND DISC NOW

		LINK->L2STATE = 4;				// DISCONNECTING
		LINK->L2TIMER = 1;				// USE TIMER TO KICK OFF DISC
	}

	CLEARSESSIONENTRY(Session);

}

VOID L4TimerProc()
{
	//	CHECK FOR TIMER EXPIRY

	int n = MAXCIRCUITS;
	TRANSPORTENTRY * L4 = L4TABLE;
	TRANSPORTENTRY * Partner;
	int MaxLinks = MAXLINKS;

	while (n--)
	{
		if (L4->L4USER[0] == 0)
		{
			L4++;
			continue;
		}
		
		//	CHECK FOR L4BUSY SET AND NO LONGER BUSY

		if (L4->NAKBITS & L4BUSY)
		{
			if ((CHECKIFBUSYL4(L4) & L4BUSY) == 0)
			{
				// no longer busy

				L4->NAKBITS &= ~L4BUSY;		// Clear busy
				L4->L4ACKREQ = 1;			// SEND ACK
			}
		}

		if (L4->L4TIMER)					// Timer Running?
		{
			L4->L4TIMER--;
			if (L4->L4TIMER == 0)			// Expired
				L4TIMEOUT(L4);	
		}
	
		if (L4->L4ACKREQ)					// DELAYED ACK Timer Running?
		{
			L4->L4ACKREQ--;
			if (L4->L4ACKREQ == 0)			// Expired
				SENDL4IACK(L4);
		}

		L4->L4KILLTIMER++;

		//	IF BIT 6 OF APPLFLAGS SET, SEND MSG EVERY 11 MINS TO KEEP SESSION OPEN

		if (L4->L4CROSSLINK)			// CONNECTED?
			if (L4->SESS_APPLFLAGS & 0x40)
				if (L4->L4KILLTIMER > 11 * 60)
					AUTOTIMER(L4);

		if (L4->L4LIMIT == 0)
			L4->L4LIMIT = L4LIMIT;
		else
		{
			if (L4->L4KILLTIMER > L4->L4LIMIT)
			{
				L4->L4KILLTIMER = 0;
				
				//	CLOSE THIS SESSION, AND ITS PARTNER (IF ANY)

				L4->STAYFLAG = 0;

				Partner = L4->L4CROSSLINK;
				CLOSECURRENTSESSION(L4);
				
				if (Partner)
				{
					Partner->L4KILLTIMER = 0;		//ITS TIMES IS ALSO ABOUT TO EXPIRE
					CLOSECURRENTSESSION(Partner);	// CLOSE THIS ONE
				}
			}
		}
		L4++;
	}
}

VOID L4TIMEOUT(TRANSPORTENTRY * L4)
{
	//	TIMER EXPIRED

	//	    IF LINK UP REPEAT TEXT
	//	    IF S2, REPEAT CONNECT REQUEST
	//	    IF S4, REPEAT DISCONNECT REQUEST

	struct DATAMESSAGE * Msg;
	struct DATAMESSAGE * Copy;
	struct DEST_LIST * DEST;

	if (L4->L4STATE < 2)
		return;

	if (L4->L4STATE == 2)
	{
		//	RETRY CONNECT

		L4->L4RETRIES++;

		if (L4->L4RETRIES > L4N2)
		{
			//	RETRIED N2 TIMES - FAIL LINK

			L4CONNECTFAILED(L4);		// TELL OTHER PARTNER IT FAILED
			CLEARSESSIONENTRY(L4);
			return;
		}

		Debugprintf("Retrying L4 Connect Request");

		SENDL4CONNECT(L4);				// Resend connect
		return;
	}

	if (L4->L4STATE == 4)
	{
		//	RETRY DISCONNECT

		L4->L4RETRIES++;

		if (L4->L4RETRIES > L4N2)
		{
			//	RETRIED N2 TIMES - FAIL LINK


			CLEARSESSIONENTRY(L4);
			return;
		}

		SENDL4DISC(L4);				// Resend connect
		return;
	}
	//	STATE 5 OR ABOVE - RETRY INFO


	L4->FLAGS &= ~L4BUSY;		// CANCEL CHOKE
	
	L4->L4RETRIES++;

	if (L4->L4RETRIES > L4N2)
	{
		//	RETRIED N2 TIMES - FAIL LINK

		CloseSessionPartner(L4);	// SEND CLOSE TO PARTNER (IF PRESENT)
		return;
	}

	//	RESEND ALL OUTSTANDING FRAMES

	L4->FLAGS &= 0x7F;				// CLEAR CHOKED

	Msg = L4->L4HOLD_Q;

	while (Msg)
	{ 
		Copy = GetBuff();

		if (Copy == 0)
			return;

		memcpy(Copy, Msg, Msg->LENGTH);

		DEST = L4->L4TARGET.DEST;

		C_Q_ADD(&DEST->DEST_Q, Copy);
	
		L4FRAMESRETRIED++;

		Msg = Msg->CHAIN;
	}
}

VOID AUTOTIMER(TRANSPORTENTRY * L4)
{
	//	SEND MESSAGE TO USER TO KEEP CIRCUIT OPEN

	struct DATAMESSAGE * Msg = GetBuff();

	if (Msg == 0)
		return;

	Msg->PID = 0xf0;
	Msg->L2DATA[0] = 0;
	Msg->L2DATA[1] = 0;

	Msg->LENGTH = MSGHDDRLEN + 3;

	C_Q_ADD(&L4->L4TX_Q, Msg);
	
	PostDataAvailable(L4);

	L4->L4KILLTIMER = 0;
	
	if (L4->L4CROSSLINK)
		L4->L4CROSSLINK->L4KILLTIMER = 0;
}

VOID L4CONNECTFAILED(TRANSPORTENTRY * L4)
{
	//	CONNECT HAS TIMED OUT - SEND MESSAGE TO OTHER END

	struct DATAMESSAGE * Msg;
	TRANSPORTENTRY * Partner;
	UCHAR * ptr1;
	char Nodename[20];
	struct DEST_LIST * DEST;

	Partner = L4->L4CROSSLINK;

	if (Partner == 0)
		return;

	Msg = GetBuff();

	if (Msg == 0)
		return;

	Msg->PID = 0xf0;

	ptr1 = SetupNodeHeader(Msg);

	DEST = L4->L4TARGET.DEST;
	Nodename[DecodeNodeName(DEST->DEST_CALL, Nodename)] = 0;		// null terminate

	ptr1 += sprintf(ptr1, "Failure with %s\r", Nodename);

	Msg->LENGTH = (int)(ptr1 - (UCHAR *)Msg);

	C_Q_ADD(&Partner->L4TX_Q, Msg);

	PostDataAvailable(Partner);

	Partner->L4CROSSLINK = 0;		// Back to command lewel
}


VOID ProcessIframe(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer)
{
	//	IF UP/DOWN LINK, AND CIRCUIT ESTABLISHED, ADD LEVEL 3/4 HEADERS
	//	   (FRAGMENTING IF NECESSARY), AND PASS TO TRANSPORT CONTROL
	//	   FOR ESTABLISHED ROUTE

	//	IF INTERNODE MESSAGE, PASS TO ROUTE CONTROL 

	//	IF UP/DOWN, AND NO CIRCUIT, PASS TO COMMAND HANDLER

	TRANSPORTENTRY * Session;

	//	IT IS POSSIBLE TO MULTIPLEX NETROM AND IP STUFF ON THE SAME LINK

	if (Buffer->PID == 0xCC || Buffer->PID == 0xCD)
	{
		Q_IP_MSG(Buffer);
		return;
	}

	if (Buffer->PID ==	0xCF)
	{
		//	INTERNODE frame

		//	IF LINKTYPE IS NOT 3, MUST CHECK IF WE HAVE ACCIDENTALLY  ATTACHED A BBS PORT TO THE NODE

		if (LINK->LINKTYPE != 3)
		{
			if (LINK->CIRCUITPOINTER)
			{
				//	MUST KILL SESSION

				InformPartner(LINK);			// CLOSE IT
				LINK->CIRCUITPOINTER = NULL;	// AND UNHOOK
			}
			LINK->LINKTYPE = 3;					// NOW WE KNOW ITS A CROSSLINK
		}

		NETROMMSG(LINK, (L3MESSAGEBUFFER *)Buffer);
		return;
	}

	if (LINK->LINKTYPE == 3)
	{
		// Weve receved a non- netrom frame on an inernode link

		ReleaseBuffer(Buffer);
		return;
	}

	if (LINK->CIRCUITPOINTER)
	{
		// Pass to Session 
		
		IFRM150(LINK->CIRCUITPOINTER, Buffer);
		return;
	}

	//	UPLINK MESSAGE WITHOUT LEVEL 4 ENTRY - CREATE ONE

	Session = SetupSessionForL2(LINK);

	if (Session == NULL)
		return;

	CommandHandler(Session, Buffer);
	return;
}


VOID IFRM100(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer)
{
	TRANSPORTENTRY * Session;
	
	if (LINK->CIRCUITPOINTER)
	{
		// Pass to Session 
		
		IFRM150(LINK->CIRCUITPOINTER, Buffer);
		return;
	}

	//	UPLINK MESSAGE WITHOUT LEVEL 4 ENTRY - CREATE ONE

	Session = SetupSessionForL2(LINK);

	if (Session == NULL)
		return;

	CommandHandler(Session, Buffer);
	return;
}


VOID IFRM150(TRANSPORTENTRY * Session, PDATAMESSAGE Buffer)
{
	TRANSPORTENTRY * Partner;
	struct _LINKTABLE * LINK;
	
	Session->L4KILLTIMER = 0;			// RESET SESSION TIMEOUT

	if (Session->L4CROSSLINK == NULL)			// CONNECTED?
	{
		//	NO, SO PASS TO COMMAND HANDLER
		
		CommandHandler(Session, Buffer);
		return;
	}

 	Partner = Session->L4CROSSLINK;			// TO SESSION PARTNER

	if (Partner->L4STATE == 5)
	{
		C_Q_ADD(&Partner->L4TX_Q, Buffer);
		PostDataAvailable(Partner);
		return;
	}



	//	MESSAGE RECEIVED BEFORE SESSION IS UP - CANCEL SESSION
	//	  AND PASS MESSAGE TO COMMAND HANDLER

	if (Partner->L4CIRCUITTYPE & L2LINK)		// L2 SESSION?
	{
		//	MUST CANCEL L2 SESSION

		LINK = Partner->L4TARGET.LINK;
		LINK->CIRCUITPOINTER = NULL;	// CLEAR REVERSE LINK

		LINK->L2STATE = 4;				// DISCONNECTING
		LINK->L2TIMER = 1;				// USE TIMER TO KICK OFF DISC

		LINK->L2RETRIES = LINK->LINKPORT->PORTN2 - 2;	//ONLY SEND DISC ONCE
	}

	CLEARSESSIONENTRY(Partner);

	Session->L4CROSSLINK = 0;		// CLEAR CROSS LINK
	CommandHandler(Session, Buffer);
	return;
}


VOID SENDL4DISC(TRANSPORTENTRY * Session)
{
	L3MESSAGEBUFFER * MSG;
	struct DEST_LIST * DEST = Session->L4TARGET.DEST;

	if (Session->L4STATE < 4)
	{
		//	CIRCUIT NOT UP OR CLOSING - PROBABLY NOT YET SET UP - JUST ZAP IT

		CLEARSESSIONENTRY(Session);
		return;
	}
	
	Session->L4TIMER = Session->SESSIONT1;	// START TIMER
	Session->L4STATE = 4;					// SET DISCONNECTING
	Session->L4ACKREQ = 0;					// CANCEL ACK NEEDED

	MSG = GetBuff();

	if (MSG == NULL)
		return;

	MSG->L3PID = 0xCF;			// NET MESSAGE
	
	memcpy(MSG->L3SRCE, Session->L4MYCALL, 7);
	memcpy(MSG->L3DEST, DEST->DEST_CALL, 7);
	MSG->L3TTL = L3LIVES;
	MSG->L4INDEX = Session->FARINDEX;
	MSG->L4ID = Session->FARID;
	MSG->L4TXNO = 0;
	MSG->L4FLAGS = L4DREQ;

	MSG->LENGTH = (int)(&MSG->L4DATA[0] - (UCHAR *)MSG);

	C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG);
}


void WriteL4LogLine(UCHAR * mycall, UCHAR * call, UCHAR * node)
{
	UCHAR FN[MAX_PATH];
	FILE * L4LogHandle;
	time_t T;
	struct tm * tm;

	char Call1[12], Call2[12], Call3[12];

	char LogMsg[256];	
	int MsgLen;
	
	Call1[ConvFromAX25(mycall, Call1)] = 0;
	Call2[ConvFromAX25(call, Call2)] = 0;
	Call3[ConvFromAX25(node, Call3)] = 0;


	T = time(NULL);
	tm = gmtime(&T);	

	sprintf(FN,"%s/L4Log_%02d%02d.txt", BPQDirectory, tm->tm_mon + 1, tm->tm_mday);

	L4LogHandle = fopen(FN, "ab");

	if (L4LogHandle == NULL)
		return;

	MsgLen = sprintf(LogMsg, "%02d:%02d:%02d Call to %s from %s at Node %s\r\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Call1, Call2, Call3);

	fwrite(LogMsg , 1, MsgLen, L4LogHandle);

	fclose(L4LogHandle);
}

VOID CONNECTREQUEST(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, UINT ApplMask, UCHAR * ApplCall)
{
	//	CONNECT REQUEST - SEE IF EXISTING SESSION
	//	IF NOT, GET AND FORMAT SESSION TABLE ENTRY
	//	SEND CONNECT ACK

	//	EDI = _BUFFER, EBX = LINK

	TRANSPORTENTRY * L4;
	int BPQNODE = 0;				// NOT ONE OF MINE
	char BPQPARAMS[10];				// Extended Connect Params from BPQ Node
	int CONERROR;
	int Index;

	memcpy(BPQPARAMS, &L4T1, 2);	// SET DEFAULT T1 IN CASE NOT FROM ANOTHER BPQ NODE

	BPQPARAMS[2] = 0;				// 'SPY' NOT SET
										
	if (FINDCIRCUIT(L3MSG, &L4, &Index))
	{
		// SESSION EXISTS - ASSUME RETRY AND SEND ACK

		SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall);
		return;
	}


	if (L4 == 0)
	{
		SendConNAK(LINK, L3MSG);
		return;
	}

	L4->CIRCUITINDEX = Index;
	
	SETUPNEWCIRCUIT(LINK, L3MSG, L4, BPQPARAMS, ApplMask, &BPQNODE);

	if (L4->L4TARGET.DEST == 0)
	{
		// NODE NOT IN TABLE, AND TABLE FULL - CANCEL IT

		memset(L4, 0, sizeof (TRANSPORTENTRY));
		SendConNAK(LINK, L3MSG);
		return;
	}
	//	IF CONNECT TO APPL, ALLOCATE BBS PORT

	if (ApplMask == 0 || BPQPARAMS[2] == 'Z')		// Z is "Spy" Connect
	{
		SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall);

		return;
	}

	//	IF APPL CONNECT, SEE IF APPL HAS AN ALIAS


	if (ALIASPTR[0] > ' ')
	{
		struct DATAMESSAGE * Msg;

		//	ACCEPT THE CONNECT, THEN INVOKE THE ALIAS

		SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall);

		Msg = GetBuff();

		if (Msg)
		{
			Msg->PID = 0xf0;
			memcpy(Msg->L2DATA, APPL->APPLCMD, 12);
			Msg->L2DATA[12] = 13;
			Msg->LENGTH = MSGHDDRLEN + 12 + 2;		// 2 for PID and CR

			C_Q_ADD(&L4->L4RX_Q, Msg);
			return;
		}
	}

	if (cATTACHTOBBS(L4, ApplMask, PACLEN, &CONERROR))
	{
		SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall);
		return;
	}
	
	//	NO BBS AVAILABLE
	
	CLEARSESSIONENTRY(L4);
	SendConNAK(LINK, L3MSG);
	return;
}

VOID SendConACK(struct _LINKTABLE * LINK, TRANSPORTENTRY * L4, L3MESSAGEBUFFER * L3MSG, BOOL BPQNODE, UINT Applmask, UCHAR * ApplCall)
{
	//	SEND CONNECT ACK	

	L4CONNECTSIN++;
	
	L3MSG->L4TXNO = L4->CIRCUITINDEX;
	L3MSG->L4RXNO = L4->CIRCUITID;

	L3MSG->L4DATA[0] = L4->L4WINDOW;			//WINDOW

	L3MSG->L4FLAGS = L4CACK;

	if (LogL4Connects)
		WriteL4LogLine(ApplCall, L4->L4USER, L3MSG->L3SRCE);

	if (LogAllConnects)
	{
		char From[64];
		char toCall[12], fromCall[12], atCall[12];

		toCall[ConvFromAX25(ApplCall, toCall)] = 0;
		fromCall[ConvFromAX25(L4->L4USER, fromCall)] = 0;
		atCall[ConvFromAX25(L3MSG->L3SRCE, atCall)] = 0;

		sprintf(From, "%s at Node %s", fromCall, atCall);
		WriteConnectLog(From, toCall, "NETROM");
	}


	if (CTEXTLEN && (Applmask == 0) && FULL_CTEXT)	// Any connect, or call to alias
	{
		struct DATAMESSAGE * Msg;
		int Totallen = CTEXTLEN;
		int Paclen= PACLEN;
		UCHAR * ptr = CTEXTMSG;

		if (Paclen == 0)
			Paclen = PACLEN;

		while(Totallen)
		{
			Msg = GetBuff();

			if (Msg == NULL)
				break;				// No Buffers

			Msg->PID = 0xf0;

			if (Paclen > Totallen)
				Paclen = Totallen;
			
			memcpy(Msg->L2DATA, ptr, Paclen);
			Msg->LENGTH = Paclen + MSGHDDRLEN + 1;

			C_Q_ADD(&L4->L4TX_Q, Msg);			// SEND MESSAGE TO CALLER
			PostDataAvailable(L4);
			ptr += Paclen;
			Totallen -= Paclen;
		}
	}

	L3SWAPADDRESSES(L3MSG);
	
	L3MSG->L3TTL = L3LIVES;

	L3MSG->LENGTH = MSGHDDRLEN + 22;		// CTL 20 BYTE Header Window

	if (BPQNODE)
	{
		L3MSG->L4DATA[1] = L3LIVES;		// Our TTL
		L3MSG->LENGTH++;
	}

	C_Q_ADD(&LINK->TX_Q, L3MSG);
}

int FINDCIRCUIT(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY ** REQL4, int * NewIndex)
{
	//	FIND CIRCUIT FOR AN INCOMING MESSAGE

	TRANSPORTENTRY * L4 = L4TABLE;
	TRANSPORTENTRY * FIRSTSPARE = NULL;
	struct DEST_LIST * DEST;

	int Index = 0;
	
	while (Index < MAXCIRCUITS)
	{
		if (L4->L4USER[0] == 0)		// Spare
		{
			if (FIRSTSPARE == NULL)
			{			
				FIRSTSPARE = L4;
				*NewIndex = Index;
			}

			L4++;
			Index++;
			continue;
		}

		DEST = L4->L4TARGET.DEST;

		if (DEST == NULL)
		{
			// L4 entry without a Dest shouldn't happen. (I don't think!)

			char Call1[12], Call2[12];

			Call1[ConvFromAX25(L4->L4USER, Call1)] = 0;
			Call2[ConvFromAX25(L4->L4MYCALL, Call2)] = 0;

			Debugprintf("L4 entry without Target. Type = %02x Calls %s %s",
				L4->L4CIRCUITTYPE, Call1, Call2);

			L4++;
			Index++;
			continue;
		}
		
		if (CompareCalls(L3MSG->L3SRCE, DEST->DEST_CALL))
		{
			if (L4->FARID == L3MSG->L4ID && L4->FARINDEX == L3MSG->L4INDEX)
			{
				// Found it
				
				*REQL4 = L4;
				return TRUE;
			}
		}
		L4++;
		Index++;
	}

	//	ENTRY NOT FOUND - FIRSTSPARE HAS FIRST FREE ENTRY, OR ZERO IF TABLE FULL

	*REQL4 = FIRSTSPARE;
	return FALSE;
}

VOID L3SWAPADDRESSES(L3MESSAGEBUFFER * L3MSG)
{
	//	EXCHANGE ORIGIN AND DEST

	char Temp[7];

	memcpy(Temp, L3MSG->L3SRCE, 7);
	memcpy(L3MSG->L3SRCE, L3MSG->L3DEST, 7);
	memcpy(L3MSG->L3DEST, Temp, 7);

	L3MSG->L3DEST[6] &= 0x1E;		// Mack EOA and CMD
	L3MSG->L3SRCE[6] &= 0x1E;
	L3MSG->L3SRCE[6] |= 1;			// Set Last Call
}

VOID SendConNAK(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG)
{
	L3MSG->L4FLAGS = L4CACK | L4BUSY;			// REJECT
	L3MSG->L4DATA[0] = 0;						// WINDOW

	L3SWAPADDRESSES(L3MSG);	
	L3MSG->L3TTL = L3LIVES;

	C_Q_ADD(&LINK->TX_Q, L3MSG);
}

VOID SETUPNEWCIRCUIT(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, 
					 TRANSPORTENTRY * L4, char * BPQPARAMS, int ApplMask, int * BPQNODE)
{
	struct DEST_LIST * DEST;
	int Maxtries = 2;					// Just in case

	L4->FARINDEX = L3MSG->L4INDEX;
	L4->FARID = L3MSG->L4ID;

	// Index set by caller

	L4->CIRCUITID = NEXTID;

	NEXTID++;
		if (NEXTID == 0)
			NEXTID++;								// kEEP nON-ZERO

	L4->SESSIONT1 = L4T1;
	
	L4->L4WINDOW = (UCHAR)L4DEFAULTWINDOW;

	if (L3MSG->L4DATA[0] > L4DEFAULTWINDOW)
		L4->L4WINDOW = L3MSG->L4DATA[0];
		
	memcpy(L4->L4USER, &L3MSG->L4DATA[1], 7);		// Originator's call from Call Request
	
	if (ApplMask)
	{
		// Should get APPLCALL if set ( maybe ???????????????
	}

//	MOV	ESI,APPLCALLTABLEPTR
//	LEA	ESI,APPLCALL[ESI]

	memcpy(L4->L4MYCALL, MYCALL, 7);

	//	GET BPQ EXTENDED CONNECT PARAMS IF PRESENT

	if (L3MSG->LENGTH  == MSGHDDRLEN + 38 || L3MSG->LENGTH  == MSGHDDRLEN + 39)
	{
		*BPQNODE = 1;
		memcpy(BPQPARAMS, &L3MSG->L4DATA[15],L3MSG->LENGTH - (MSGHDDRLEN + 36));
	}

	L4->L4CIRCUITTYPE = SESSION | UPLINK;	
	L4->L4STATE = 5;
	
TryAgain:

	DEST = CHECKL3TABLES(LINK, L3MSG);

	L4->L4TARGET.DEST = DEST;

	if (DEST == 0)
	{
		int WorstQual = 256;
		struct DEST_LIST * WorstDest = NULL;
		int n = MAXDESTS;

		//	Node not it table and table full

		//	Replace worst quality node with session counts of zero

		//	But could have been excluded, so check

		if (CheckExcludeList(L3MSG->L3SRCE) == 0)
			return;

		DEST = DESTS;

		while (n--)
		{
			if (DEST->DEST_COUNT == 0 && DEST->DEST_RTT == 0)		// Not used and not INP3
			{
				if (DEST->NRROUTE[0].ROUT_QUALITY < WorstQual)
				{
					WorstQual = DEST->NRROUTE[0].ROUT_QUALITY;
					WorstDest = DEST;
				}
			}
			DEST++;
		}
		
		if (WorstDest)
		{
			REMOVENODE(WorstDest);
			if (Maxtries--)
				goto TryAgain;			// We now have a spare (but protect against loop if something amiss)
		}

		// Nothing to delete, so just ignore connect

		return;
	}

	if (*BPQNODE)
	{	
		SHORT T1;
		
		DEST->DEST_STATE |= 0x40;			// SET BPQ _NODE BIT
		memcpy((char *)&T1, BPQPARAMS, 2);

		if (T1 > 300)
			L4->SESSIONT1 = L4T1;
		else
			L4->SESSIONT1 = T1;
	}
	else
		L4->SESSIONT1 = L4T1;			// DEFAULT TIMEOUT
	
	L4->SESSPACLEN = PACLEN;			// DEFAULT
}


int CHECKIFBUSYL4(TRANSPORTENTRY * L4)
{
	//	RETURN TOP BIT OF AL SET IF SESSION PARTNER IS BUSY

	int Count;

	if (L4->L4CROSSLINK)		// CONNECTED?
		Count = CountFramesQueuedOnSession(L4->L4CROSSLINK);
	else
		Count = CountFramesQueuedOnSession(L4);

	if (Count < L4->L4WINDOW)
		return 0;
	else
		return L4BUSY;
}

VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, UCHAR * ApplCall)
{
	//	INTERNODE LINK

	TRANSPORTENTRY * L4;
	struct DEST_LIST * DEST;
	int Opcode;
	char Nodename[20];
	char ReplyText[20];
	struct DATAMESSAGE * Msg;
	TRANSPORTENTRY * Partner;
	UCHAR * ptr1;
	int FramesMissing;
	L3MESSAGEBUFFER * Saved;
	L3MESSAGEBUFFER ** Prev;
	char Call[10];

	L4FRAMESRX++;

	Opcode = L3MSG->L4FLAGS & 15;

	switch (Opcode)
	{
	case 0:

		//	OPCODE 0 is used for a variety of functions, using L4INDEX and L4ID as qualifiers
		//	0c0c is used for IP

		if (L3MSG->L4ID == 0x0C && L3MSG->L4INDEX == 0x0C)
		{
			Q_IP_MSG((PDATAMESSAGE)L3MSG);
			return;
		}

		//	 00 01 Seesm to be Netrom Record Route

		if (L3MSG->L4ID == 1 && L3MSG->L4INDEX == 0)
		{
			NRRecordRoute((UCHAR *)L3MSG, L3MSG->LENGTH);
			return;
		}

		ReleaseBuffer(L3MSG);
		return;

	case L4CREQ:

		CONNECTREQUEST(LINK, L3MSG, ApplMask, ApplCall);
		return;
	}
		
	//	OTHERS NEED A SESSION

	L4 = &L4TABLE[L3MSG->L4INDEX];

	if (L4->CIRCUITID!= L3MSG->L4ID)
	{
		ReleaseBuffer(L3MSG);
		return;
	}

	if ((L4->L4CIRCUITTYPE & SESSION) == 0)
	{
		// Not an L4 Session - must be an old connection

		ReleaseBuffer(L3MSG);
		return;
	}

	//	HAVE FOUND CORRECT SESSION ENTRY

	switch (Opcode)
	{
	case L4CACK:

		//	CONNECT ACK
	
		DEST = L4->L4TARGET.DEST;
	
		//	EXTRACT EXTENDED PARAMS IF PRESENT

		if (L3MSG->LENGTH > MSGHDDRLEN + 22)		// Standard Msg
		{
			DEST->DEST_STATE &= 0x80;
			DEST->DEST_STATE |= (L3MSG->L4DATA[1] - L3MSG->L3TTL) + 0x41; // Hops to dest + x40
		}

		Partner = L4->L4CROSSLINK;

		if (L3MSG->L4FLAGS & L4BUSY)
		{
			// Refused

			CLEARSESSIONENTRY(L4);
			if (Partner)
				Partner->L4CROSSLINK = NULL;	// CLEAR CROSSLINK

			strcpy(ReplyText, "Busy from");
		}
		else
		{
			// Connect OK

			if (L4->L4STATE == 5)
			{
				// MUST BE REPEAT MSG - DISCARD

				ReleaseBuffer(L3MSG);
				return;
			}

			L4->FARINDEX = L3MSG->L4TXNO;
			L4->FARID = L3MSG->L4RXNO;

			L4->L4STATE = 5;			// ACTIVE
			L4->L4TIMER = 0;
			L4->L4RETRIES = 0;

			L4->L4WINDOW = L3MSG->L4DATA[0];

			strcpy(ReplyText, "Connected to");
		}

		if (Partner == 0)
		{
			ReleaseBuffer(L3MSG);
			return;
		}

		Msg = (PDATAMESSAGE)L3MSG;					// reuse input buffer

		Msg->PID = 0xf0;
		ptr1 = SetupNodeHeader(Msg);

		Nodename[DecodeNodeName(DEST->DEST_CALL, Nodename)] = 0;		// null terminate

		ptr1 += sprintf(ptr1, "%s %s\r", ReplyText, Nodename);

		Msg->LENGTH = (int)(ptr1 - (UCHAR *)Msg);

		C_Q_ADD(&Partner->L4TX_Q, Msg);

		PostDataAvailable(Partner);
		return;

	case L4DREQ:

		// DISCONNECT REQUEST

		 L3MSG->L4INDEX = L4->FARINDEX;
		 L3MSG->L4ID = L4->FARID;
		 
		 L3MSG->L4FLAGS = L4DACK;

		 L3SWAPADDRESSES(L3MSG);				// EXCHANGE SOURCE AND DEST
		 L3MSG->L3TTL = L3LIVES;

		 C_Q_ADD(&LINK->TX_Q, L3MSG);

		 CloseSessionPartner(L4);				// SEND CLOSE TO PARTNER (IF PRESENT)
		 return;
	
	case L4DACK:
	
		CLEARSESSIONENTRY(L4);
		ReleaseBuffer(L3MSG);
		return;

	case L4INFO:

		//MAKE SURE SESSION IS UP - FIRST I FRAME COULD ARRIVE BEFORE CONNECT ACK

		if (L4->L4STATE == 2)
		{
			ReleaseBuffer(L3MSG);		// SHOULD SAVE - WILL AVOID NEED TO RETRANSMIT
			return;	
		}

		ACKFRAMES(L3MSG, L4, L3MSG->L4RXNO);

		//	If DISCPENDING or STATE IS 4, THEN SESSION IS CLOSING - IGNORE ANY I FRAMES

		if ((L4->FLAGS & DISCPENDING) || L4->L4STATE == 4)
		{
			ReleaseBuffer(L3MSG);
			return;
		} 

		// CHECK RECEIVED SEQUENCE

		FramesMissing = L3MSG->L4TXNO - L4->RXSEQNO;	// WHAT WE GOT -  WHAT WE WANT

		if (FramesMissing > 128)
			FramesMissing -= 256;

		// if NUMBER OF FRAMES MISSING is  -VE, THEN IN FACT IT	INDICATES A REPEAT

		if (FramesMissing < 0)
		{
			// FRAME IS A REPEAT

			Call[ConvFromAX25(L3MSG->L3SRCE, Call)] = 0;
			Debugprintf("Discarding repeated frame seq %d from %s", L3MSG->L4TXNO, Call);

			L4->L4ACKREQ = 1;
			ReleaseBuffer(L3MSG);
			return;
		}
		
		if (FramesMissing > 0)
		{
			//	EXPECTED FRAME HAS BEEN MISSED - ASK FOR IT AGAIN,
			//	AND KEEP THIS FRAME UNTIL MISSING ONE ARRIVES

			L4->NAKBITS |= L4NAK;			// SET NAK REQUIRED
	
			SENDL4IACK(L4);			// SEND DATA ACK COMMAND TO ACK OUTSTANDING FRAMES
	
			//	SEE IF WE ALREADY HAVE A COPY OF THIS ONE
/*
			Saved = L4->L4RESEQ_Q;

			Call[ConvFromAX25(L3MSG->L3SRCE, Call)] = 0;
			Debugprintf("saving seq %d from %s", L3MSG->L4TXNO, Call);

			while (Saved)
			{
				if (Saved->L4TXNO == L3MSG->L4TXNO)
				{
					//	ALREADY HAVE A COPY - DISCARD IT
			
					Debugprintf("Already have seq %d - discarding", L3MSG->L4TXNO);
					ReleaseBuffer(L3MSG);
					return;
				}

				Saved = Saved->Next;
			}

			C_Q_ADD(&L4->L4RESEQ_Q, L3MSG);		// ADD TO CHAIN
			return;
*/
		}

		// Frame is OK

L4INFO_OK:

		if (L3MSG == 0)
		{
			Debugprintf("Trying to Process NULL L3 Message");
			return;
		}

		L4->NAKBITS &= ~L4NAK;				// CLEAR MESSAGE LOST STATE

		L4->RXSEQNO++;
	
		//	REMOVE HEADERS, AND QUEUE INFO 

		L3MSG->LENGTH -= 20;				// L3/L4 Header

		if (L3MSG->LENGTH < (4 + sizeof(void *)))	// No PID
		{					
			ReleaseBuffer(L3MSG);
			return;
		}

		L3MSG->L3PID = 0xF0;				// Normal Data PID

		memmove(L3MSG->L3SRCE, L3MSG->L4DATA, L3MSG->LENGTH - (4 + sizeof(void *)));

		REFRESHROUTE(L4);

		L4->L4ACKREQ = L4DELAY;				// SEND INFO ACK AFTER L4DELAY (UNLESS I FRAME SENT) 

		IFRM150(L4, (PDATAMESSAGE)L3MSG);	// CHECK IF SETTING UP AND PASS ON

		// See if anything on reseq Q to process

		if (L4->L4RESEQ_Q == 0)
			return;

		Prev = &L4->L4RESEQ_Q;
		Saved = L4->L4RESEQ_Q;

		while (Saved)
		{
			if (Saved->L4TXNO == L4->RXSEQNO)		// The one we want
			{
				// REMOVE IT FROM QUEUE,AND PROCESS IT

				*Prev = Saved->Next;		// CHAIN  NEXT IN CHAIN TO PREVIOUS

				OLDFRAMES++;			// COUNT FOR STATS
	
				L3MSG = Saved;
				Debugprintf("Processing Saved Message %d Address %x", L4->RXSEQNO, L3MSG);
				goto L4INFO_OK;
			}

			Debugprintf("Message %d %x still on Reseq Queue", Saved->L4TXNO, Saved);

			Prev = &Saved;
			Saved = Saved->Next;
		}

		return;

	case L4IACK:

		ACKFRAMES(L3MSG, L4, L3MSG->L4RXNO);
		REFRESHROUTE(L4);

		// Drop Through
	}

	// Unrecognised - Ignore

	ReleaseBuffer(L3MSG);
	return;
}


VOID ACKFRAMES(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY * L4, int NR)
{
	//	SEE HOW MANY FRAMES ARE ACKED - IF NEGATIVE, THAN THIS MUST BE A
	//	DELAYED REPEAT OF AN ACK ALREADY PROCESSED

	int Count = NR - L4->L4WS;
	L3MESSAGEBUFFER * Saved;
	struct DEST_LIST * DEST;
	struct DATAMESSAGE * Msg;
	struct DATAMESSAGE * Copy;
	int RTT;


	if (Count < -128)
		Count += 256;

	if (Count < 0)
	{
		//	THIS MAY BE A DELAYED REPEAT OF AN ACK ALREADY PROCESSED

		return;				// IGNORE COMPLETELY
	}

	while (Count > 0)
	{
		// new ACK

		//	FRAME L4WS HAS BEED ACKED - IT SHOULD BE FIRST ON HOLD QUEUE

		Saved = Q_REM((void *)&L4->L4HOLD_Q);

		if (Saved)
			ReleaseBuffer(Saved);

		//	CHECK RTT SEQUENCE

		if (L4->L4WS == L4->RTT_SEQ)
		{
			if (L4->RTT_TIMER)
			{
				//	FRAME BEING TIMED HAS BEEN ACKED - UPDATE DEST RTT TIMER
				
				DEST = L4->L4TARGET.DEST;
				
				RTT = GetTickCount() - L4->RTT_TIMER;

				if (DEST->DEST_RTT == 0)
					DEST->DEST_RTT = RTT;
				else
					DEST->DEST_RTT = ((DEST->DEST_RTT * 9) + RTT) /10;	// 90% Old + New
			}
		}

		L4->L4WS++;
		Count--;
	}

	L4->L4TIMER = 0;
	L4->L4RETRIES = 0;

	if (NR != L4->TXSEQNO)
	{
		// Not all Acked

		L4->L4TIMER = L4->SESSIONT1;	// RESTART TIMER
	}
	else
	{
		if ((L4->FLAGS & DISCPENDING) && L4->L4TX_Q == 0)
		{
			// All Acked and DISC Pending, so send it
		
			SENDL4DISC(L4);
			return;
		}
	}

	//	SEE IF CHOKE SET

	L4->FLAGS &= ~L4BUSY;
		
	if (L3MSG->L4FLAGS & L4BUSY)
	{
		L4->FLAGS |= L3MSG->L4FLAGS & L4BUSY;		// Get Busy flag from message

		if ((L3MSG->L4FLAGS & L4NAK) == 0)
			return;						// Dont send while biust unless NAC received
	}

	if (L3MSG->L4FLAGS & L4NAK)
	{
		//	RETRANSMIT REQUESTED MESSAGE - WILL BE FIRST ON HOLD QUEUE

		Msg = L4->L4HOLD_Q;
		
		if (Msg == 0)
			return;
 
		Copy = GetBuff();

		if (Copy == 0)
			return;

		memcpy(Copy, Msg, Msg->LENGTH);

		DEST = L4->L4TARGET.DEST;

		C_Q_ADD(&DEST->DEST_Q, Copy);
	}
}















VOID SENDL4IACK(TRANSPORTENTRY * Session)
{
	//	SEND INFO ACK

	PL3MESSAGEBUFFER MSG = (PL3MESSAGEBUFFER)GetBuff();
	struct DEST_LIST * DEST = Session->L4TARGET.DEST;

	if (MSG == NULL)
		return;

	MSG->L3PID = 0xCF;			// NET MESSAGE

	memcpy(MSG->L3SRCE, Session->L4MYCALL, 7);
	memcpy(MSG->L3DEST, DEST->DEST_CALL, 7);

	MSG->L3TTL = L3LIVES;

	MSG->L4INDEX = Session->FARINDEX;
	MSG->L4ID = Session->FARID;

	MSG->L4TXNO = 0;

	
	MSG->L4RXNO = Session->RXSEQNO;
	Session->L4LASTACKED = Session->RXSEQNO;	// SAVE LAST NUMBER ACKED

	MSG->L4FLAGS = L4IACK | GETBUSYBIT(Session) | Session->NAKBITS;

	MSG->LENGTH = MSGHDDRLEN + 22;

	C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG);
}




/*
	PUBLIC	KILLSESSION
KILLSESSION:

	pushad
	push	ebx
	CALL	_CLEARSESSIONENTRY
	pop	ebx
	popad

	JMP	L4CONN90		; REJECT

	PUBLIC	CONNECTACK
CONNECTACK:
;
;	EXTRACT EXTENDED PARAMS IF PRESENT
;

	CMP	BYTE PTR MSGLENGTH[EDI],L4DATA+1
	JE SHORT NOTBPQ

	MOV	AL,L4DATA+1[EDI]
	SUB	AL,L3MONR[EDI]
	ADD	AL,41H			; HOPS TO DEST + 40H

	MOV	ESI,L4TARGET[EBX]
	AND	DEST_STATE[ESI],80H
	OR	DEST_STATE[ESI],AL	; SAVE

	PUBLIC	NOTBPQ
NOTBPQ:
;
;	SEE IF SUCCESS OR FAIL
;
	PUSH	EDI

	MOV	ESI,L4TARGET[EBX]		; ADDR OF LINK/DEST ENTRY
	LEA	ESI,DEST_CALL[ESI]

	CALL	DECODENODENAME		; CONVERT TO ALIAS:CALL

	MOV	EDI,OFFSET32 CONACKCALL
	MOV	ECX,17
	REP MOVSB


	POP	EDI

	TEST	L4FLAGS[EDI],L4BUSY
	JNZ SHORT L4CONNFAILED

	CMP	L4STATE[EBX],5
	JE SHORT CONNACK05		; MUST BE REPEAT MSG - DISCARD

	MOV	AX,WORD PTR L4TXNO[EDI]	; HIS INDEX
	MOV	WORD PTR FARINDEX[EBX],AX

	MOV	L4STATE[EBX],5		; ACTIVE
	MOV	L4TIMER[EBX],0		; CANCEL TIMER
	MOV	L4RETRIES[EBX],0		; CLEAR RETRY COUNT
 
	MOV	AL,L4DATA[EDI]		; WINDOW
	MOV	L4WINDOW[EBX],AL		; SET WINDOW

	MOV	EDX,L4CROSSLINK[EBX]	; POINT TO PARTNER
;
	MOV	ESI,OFFSET32 CONNECTEDMSG
	MOV	ECX,LCONNECTEDMSG

	JMP SHORT L4CONNCOMM

	PUBLIC	L4CONNFAILED
L4CONNFAILED:
;
	MOV	EDX,L4CROSSLINK[EBX]	; SAVE PARTNER
	pushad
	push	ebx
	CALL	_CLEARSESSIONENTRY
	pop	ebx
	popad

	PUSH	EBX

	MOV	EBX,EDX
	MOV	L4CROSSLINK[EBX],0	; CLEAR CROSSLINK
	POP	EBX

	MOV	ESI,OFFSET32 BUSYMSG	; ?? BUSY
	MOV	ECX,LBUSYMSG

	PUBLIC	L4CONNCOMM
L4CONNCOMM:

	OR	EDX,EDX
	JNZ SHORT L4CONNOK10
;
;	CROSSLINK HAS GONE?? - JUST CHUCK MESSAGE
;
	PUBLIC	CONNACK05
CONNACK05:

	JMP	L4DISCARD

	PUBLIC	L4CONNOK10
L4CONNOK10:

	PUSH	EBX
	PUSH	ESI
	PUSH	ECX

	MOV	EDI,_BUFFER

	ADD	EDI,7
	MOV	AL,0F0H
	STOSB				; PID

	CALL	_SETUPNODEHEADER		; PUT IN _NODE ID


	POP	ECX
	POP	ESI
	REP MOVSB

	MOV	ESI,OFFSET32 CONACKCALL
	MOV	ECX,17			; MAX LENGTH ALIAS:CALL
	REP MOVSB

	MOV	AL,0DH
	STOSB

	MOV	ECX,EDI
	MOV	EDI,_BUFFER
	SUB	ECX,EDI

	MOV	MSGLENGTH[EDI],CX

	MOV	EBX,EDX			; CALLER'S SESSION

	LEA	ESI,L4TX_Q[EBX]
	CALL	_Q_ADD			; SEND MESSAGE TO CALLER

	CALL	_POSTDATAAVAIL
	
	POP	EBX			; ORIGINAL CIRCUIT TABLE
	RET


	PUBLIC	SENDCONNECTREPLY
SENDCONNECTREPLY:
;
;	LINK SETUP COMPLETE - EBX = LINK, EDI = _BUFFER
;
	CMP	LINKTYPE[EBX],3
	JNE SHORT CONNECTED00
;
;	_NODE - _NODE SESSION SET UP - DONT NEED TO DO ANYTHING (I THINK!)
;
	CALL	RELBUFF
	RET

;
;	UP/DOWN LINK
;
	PUBLIC	CONNECTED00
CONNECTED00:
	CMP	CIRCUITPOINTER[EBX],0	
	JNE SHORT CONNECTED01

	CALL	RELBUFF			; UP/DOWN WITH NO SESSION - NOONE TO TELL
	RET				; NO CROSS LINK
	PUBLIC	CONNECTED01
CONNECTED01:
	MOV	_BUFFER,EDI
	PUSH	EBX
	PUSH	ESI
	PUSH	ECX

	ADD	EDI,7
	MOV	AL,0F0H
	STOSB				; PID

	CALL	_SETUPNODEHEADER		; PUT IN _NODE ID

	LEA	ESI,LINKCALL[EBX]

	PUSH	EDI
	CALL	CONVFROMAX25		; ADDR OF CALLED STATION
	POP	EDI

	MOV	EBX,CIRCUITPOINTER[EBX]

	MOV	L4STATE[EBX],5		; SET LINK UP

	MOV	EBX,L4CROSSLINK[EBX]	; TO INCOMING LINK
	cmp	ebx,0
	jne	xxx
;
;	NO LINK ??? 
;
	MOV		EDI,_BUFFER
	CALL	RELBUFF	
		
	POP	ECX
	POP	ESI
	POP	EBX
	
	RET

	PUBLIC	xxx
xxx:
			
	POP	ECX
	POP	ESI
	REP MOVSB

	MOV	ESI,OFFSET32 _NORMCALL
	MOVZX	ECX,_NORMLEN
	REP MOVSB

	MOV	AL,0DH
	STOSB

	MOV	ECX,EDI
	MOV	EDI,_BUFFER
	SUB	ECX,EDI

	MOV	MSGLENGTH[EDI],CX

	LEA	ESI,L4TX_Q[EBX]
	CALL	_Q_ADD			; SEND MESSAGE TO CALLER

	CALL	_POSTDATAAVAIL

	POP	EBX
	RET
*/