/*
Copyright 2001-2022 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
*/	

//
//	DLL to inteface AEA/Timewave TNC in Pactor Mode to BPQ32 switch 
//
//	Uses BPQ EXTERNAL interface
//

// Version 1.1.1.2 Sept 2010

// Fix CTEXT
// Turn round link when all acked (not all sent)

// Version 1.1.1.3 Sept 2010

// Turn round link if too long in receive

// Version 1.1.1.4 September 2010

// Fix Freq Display after Node reconfig
// Only use AutoConnect APPL for Pactor Connects

#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE

#include "time.h"

//#include <process.h>
//#include <time.h>

#include "cheaders.h"
#include "tncinfo.h"

#include "bpq32.h"


static char ClassName[]="AEAPACTORSTATUS";

static char WindowTitle[] = "AEA Pactor";
static int RigControlRow = 165;

#define	SOH	0x01	// CONTROL CODES 
#define	ETB	0x17
#define	DLE	0x10

#define MaxStreams 26

#define PTOVER 0x1a					// Pactor Turnround Char

char OverMsg[3] = " \x1a";

static RECT Rect;

static char status[8][8] = {"STANDBY",  "PHASING", "CHGOVER", "IDLE", "TRAFFIC", "ERROR", "RQ", "XXXX"};

struct TNCINFO * CreateTTYInfo(int port, int speed);
BOOL SetupConnection(int);
BOOL CloseConnection(struct TNCINFO * conn);
static BOOL WriteCommBlock(struct TNCINFO * TNC);
BOOL DestroyTTYInfo(int port);
static void CheckRX(struct TNCINFO * TNC);
VOID AEAPoll(int Port);
static VOID ProcessDEDFrame(struct TNCINFO * TNC, UCHAR * rxbuff, int len);
static VOID ProcessTermModeResponse(struct TNCINFO * TNC);
static VOID DoTNCReinit(struct TNCINFO * TNC);
static VOID DoTermModeTimeout(struct TNCINFO * TNC);

VOID ProcessPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len);
VOID ProcessKPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len);
static VOID ProcessAEAPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len);
VOID ProcessKNormCommand(struct TNCINFO * TNC, UCHAR * rxbuffer);
static VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len);

//	Note that AEA host Mode uses SOH/ETB delimiters, with DLE stuffing

static VOID EncodeAndSend(struct TNCINFO * TNC, UCHAR * txbuffer, int Len);
static int	DLEEncode(UCHAR * inbuff, UCHAR * outbuff, size_t len);
static int	DLEDecode(UCHAR * inbuff, UCHAR * outbuff, size_t len);

int ProcessLine(char * buf, int Port)
{
	UCHAR * ptr,* p_cmd;
	char * p_ipad = 0;
	char * p_port = 0;
	unsigned short WINMORport = 0;
	int BPQport;
	int len=510;
	struct TNCINFO * TNC;
	char errbuf[256];

	strcpy(errbuf, buf);

	ptr = strtok(buf, " \t\n\r");

	if(ptr == NULL) return (TRUE);

	if(*ptr =='#') return (TRUE);			// comment

	if(*ptr ==';') return (TRUE);			// comment

	ptr = strtok(NULL, " \t\n\r");

	if (_stricmp(buf, "ADDR") == 0)			// Winmor Using BPQ32 COnfig
	{
		BPQport = Port;
		p_ipad = ptr;
	}
	else
	if (_stricmp(buf, "APPL") == 0)			// Using BPQ32 COnfig
	{
		BPQport = Port;
		p_cmd = ptr;
	}
	else
	if (_stricmp(buf, "PORT") != 0)			// Using Old Config
	{
		// New config without a PORT or APPL  - this is a Config Command

		strcpy(buf, errbuf);
		strcat(buf, "\r");

		BPQport = Port;

		TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO));
		memset(TNC, 0, sizeof(struct TNCINFO));

		TNC->InitScript = malloc(1000);
		TNC->InitScript[0] = 0;

		goto ConfigLine;
	}
	else

	{

		// Old Config from file

	BPQport=0;
	BPQport = atoi(ptr);
	
	p_cmd = strtok(NULL, " \t\n\r");

	if (Port && Port != BPQport)
	{
		// Want a particular port, and this isn't it

		while(TRUE)
		{
			if (GetLine(buf) == 0)
				return TRUE;

			if (memcmp(buf, "****", 4) == 0)
				return TRUE;

		}
	}
	}
	if(BPQport > 0 && BPQport < 33)
	{
		TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO));
		memset(TNC, 0, sizeof(struct TNCINFO));

		TNC->InitScript = malloc(1000);
		TNC->InitScript[0] = 0;

		if (p_cmd != NULL)
		{
			if (p_cmd[0] != ';' && p_cmd[0] != '#')
				TNC->ApplCmd=_strdup(p_cmd);
		}

		// Read Initialisation lines

		while(TRUE)
		{
			if (GetLine(buf) == 0)
				return TRUE;
ConfigLine:
			strcpy(errbuf, buf);

			if (memcmp(buf, "****", 4) == 0)
				return TRUE;

			ptr = strchr(buf, ';');
			if (ptr)
			{
				*ptr++ = 13;
				*ptr = 0;
			}
			
			if (_memicmp(buf, "WL2KREPORT", 10) == 0)
				TNC->WL2K = DecodeWL2KReportLine(buf);
			else
				strcat (TNC->InitScript, buf);
		}
	}

	return (TRUE);
}



static size_t ExtProc(int fn, int port, PDATAMESSAGE buff) 
{
	int txlen = 0;
	PMSGWITHLEN buffptr;
	struct TNCINFO * TNC = TNCInfo[port];
	struct STREAMINFO * STREAM;
	int Stream;

	if (TNC == NULL || TNC->hDevice == 0)
		return 0;							// Port not open

	switch (fn)
	{
	case 1:				// poll

		while (TNC->PortRecord->UI_Q)			// Release anything accidentally put on UI_Q
		{
			buffptr = Q_REM(&TNC->PortRecord->UI_Q);
			ReleaseBuffer(buffptr);
		}

		for (Stream = 0; Stream <= MaxStreams; Stream++)
		{
			if (TNC->Streams[Stream].ReportDISC)
			{
				TNC->Streams[Stream].ReportDISC = FALSE;
				buff->PORT = Stream;

				return -1;
			}
		}

		CheckRX(TNC);
		AEAPoll(port);

		for (Stream = 0; Stream <= MaxStreams; Stream++)
		{
			if (TNC->Streams[Stream].PACTORtoBPQ_Q !=0)
			{
				size_t datalen;
			
				buffptr=Q_REM(&TNC->Streams[Stream].PACTORtoBPQ_Q);

				datalen = buffptr->Len;

				buff->PORT = Stream;						// Compatibility with Kam Driver
				buff->PID = 0xf0;
				memcpy(&buff->L2DATA, &buffptr->Data[0], datalen);		// Data goes to + 7, but we have an extra byte
				datalen += sizeof(void *) + 4;

				PutLengthinBuffer(buff, (int)datalen);

		
				ReleaseBuffer(buffptr);
	
				return (1);
			}
		}
			
		return 0;

	case 2:				// send

		buffptr = GetBuff();

		if (buffptr == 0) return (0);			// No buffers, so ignore

		// Find TNC Record

		Stream = buff->PORT;
		
		if (!TNC->TNCOK)
		{
			// Send Error Response

			buffptr->Len = sprintf(buffptr->Data, "No Connection to PACTOR TNC\r");

			C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
			
			return 0;
		}

		txlen = GetLengthfromBuffer((PDATAMESSAGE)buff) - 8;

		buffptr->Len = txlen;
		memcpy(buffptr->Data, buff->L2DATA, txlen);
		
		C_Q_ADD(&TNC->Streams[Stream].BPQtoPACTOR_Q, buffptr);

		if(TNC->Streams[Stream].Connected)
		{
			TNC->Streams[Stream].FramesQueued++;
		}
		return (0);


	case 3:				// CHECK IF OK TO SEND. Also used to check if TNC is responding
		
		Stream = (int)(size_t)buff;
		STREAM = &TNC->Streams[Stream];

		if (STREAM->FramesQueued  > 4)
			return 1 | TNC->HostMode << 8 | STREAM->Disconnecting << 15;	// Busy
	
		return TNC->HostMode << 8 | STREAM->Disconnecting << 15;		// OK, but lock attach if disconnecting;		// OK

	case 4:				// reinit

		return (0);

	case 5:				// Close

		EncodeAndSend(TNC, "OHON", 4);		// HOST N
		Sleep(50);

		SaveWindowPos(port);

		CloseCOMPort(TNC->hDevice);

		return (0);

	case 6:				// Scan Control

		return 0;		// None Yet

	}
	return 0;

}


VOID * AEAExtInit(EXTPORTDATA *  PortEntry)
{
	char msg[500];
	struct TNCINFO * TNC;
	int port;
	char * TempScript;
	char * ptr;

	//
	//	Will be called once for each Pactor Port
	//	The COM port number is in IOBASE
	//

	sprintf(msg,"AEA Pactor %s", PortEntry->PORTCONTROL.SerialPortName);
	WritetoConsole(msg);

	port=PortEntry->PORTCONTROL.PORTNUMBER;

	ReadConfigFile(port, ProcessLine);

	TNC = TNCInfo[port];

	if (TNC == NULL)
	{
		// Not defined in Config file

		sprintf(msg," ** Error - no info in BPQ32.cfg for this port\n");
		WritetoConsole(msg);

		return ExtProc;
	}

	TNC->Port = port;

	TNC->PortRecord = PortEntry;
	TNC->PortRecord->PORTCONTROL.HWType = TNC->Hardware = H_AEA;

	TNC->TEXTMODE = FALSE;

	PortEntry->MAXHOSTMODESESSIONS = 11;		// Default

	TNC->InitScript = _strupr(TNC->InitScript);

	if (PortEntry->PORTCONTROL.PORTCALL[0] == 0)
	{
		memcpy(TNC->NodeCall, MYNODECALL, 10);
	}
	else
	{
		ConvFromAX25(&PortEntry->PORTCONTROL.PORTCALL[0], TNC->NodeCall);
	}

	if (PortEntry->PORTCONTROL.PORTINTERLOCK && TNC->RXRadio == 0 && TNC->TXRadio == 0)
		TNC->RXRadio = TNC->TXRadio = PortEntry->PORTCONTROL.PORTINTERLOCK;

	PortEntry->PORTCONTROL.PROTOCOL = 10;
	PortEntry->PORTCONTROL.PORTQUALITY = 0;
	PortEntry->SCANCAPABILITIES = NONE;		// No Scan Interlock 

	if (PortEntry->PORTCONTROL.PORTPACLEN == 0)
		PortEntry->PORTCONTROL.PORTPACLEN = 100;

	ptr=strchr(TNC->NodeCall, ' ');
	if (ptr) *(ptr) = 0;					// Null Terminate

	// Set Essential Params and MYCALL

	TempScript = malloc(4000);

	strcpy(TempScript, "RESTART\r");
	strcat(TempScript, "EXPERT ON\r");
	strcat(TempScript, "PTHUFF 0\r");
	strcat(TempScript, "PT200 ON\r");
	strcat(TempScript, "WIDESHFT OFF\r");
	strcat(TempScript, "ARQT 30\r");

	strcat(TempScript, TNC->InitScript);

	free(TNC->InitScript);
	TNC->InitScript = TempScript;

	// Others go on end so they can't be overriden

	strcat(TNC->InitScript, "XMITOK ON\r");
	strcat(TNC->InitScript, "XFLOW OFF\r");
	strcat(TNC->InitScript, "RXREV OFF\r");
	strcat(TNC->InitScript, "FLOW OFF\r");
	strcat(TNC->InitScript, "AWLEN 8\r");
	strcat(TNC->InitScript, "AUTOBAUD OFF\r");
	strcat(TNC->InitScript, "8BITCONV ON\r");
	strcat(TNC->InitScript, "ALFPAC OFF\r");
	strcat(TNC->InitScript, "ALFDISP OFF\r");
	strcat(TNC->InitScript, "ACRRTTY 0\r");
	strcat(TNC->InitScript, "HPOLL ON\r");
	strcat(TNC->InitScript, "EAS ON\r\r");
	strcat(TNC->InitScript, "CONMODE TRANS\r");
	strcat(TNC->InitScript, "PTOVER $1A\r\r");

	// Set the ax.25 MYCALL

	sprintf(msg, "MYCALL %s\r", TNC->NodeCall);
	strcat(TNC->InitScript, msg);

#ifndef LINBPQ

	CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 0, 0, ForcedClose);

	CreateWindowEx(0, "STATIC", "Comms State", WS_CHILD | WS_VISIBLE, 10,6,120,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_COMMSSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,6,386,20, TNC->hDlg, NULL, hInstance, NULL);
	
	CreateWindowEx(0, "STATIC", "TNC State", WS_CHILD | WS_VISIBLE, 10,28,106,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TNCSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,28,520,20, TNC->hDlg, NULL, hInstance, NULL);

	CreateWindowEx(0, "STATIC", "Mode", WS_CHILD | WS_VISIBLE, 10,50,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_MODE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,50,200,20, TNC->hDlg, NULL, hInstance, NULL);
 
	CreateWindowEx(0, "STATIC", "Status", WS_CHILD | WS_VISIBLE, 10,72,110,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_STATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,72,144,20, TNC->hDlg, NULL, hInstance, NULL);

 	CreateWindowEx(0, "STATIC", "TX/RX State", WS_CHILD | WS_VISIBLE,10,94,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TXRX = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,94,374,20 , TNC->hDlg, NULL, hInstance, NULL);
 
	CreateWindowEx(0, "STATIC", "Buffers", WS_CHILD | WS_VISIBLE,10,116,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_BUFFERS = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,116,374,20 , TNC->hDlg, NULL, hInstance, NULL);
	
	CreateWindowEx(0, "STATIC", "Traffic", WS_CHILD | WS_VISIBLE,10,138,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TRAFFIC = CreateWindowEx(0, "STATIC", "RX 0 TX 0", WS_CHILD | WS_VISIBLE,116,138,374,20 , TNC->hDlg, NULL, hInstance, NULL);

	TNC->ClientHeight = 213;
	TNC->ClientWidth = 500;

	TNC->hMenu = CreateMenu();
	TNC->hWndMenu = CreatePopupMenu();
	
	MoveWindows(TNC);
#endif

	OpenCOMMPort(TNC, PortEntry->PORTCONTROL.SerialPortName, PortEntry->PORTCONTROL.BAUDRATE, FALSE);

	WritetoConsole("\n");

	return ExtProc;
}

static void CheckRX(struct TNCINFO * TNC)
{
	int Length, Len;

	// only try to read number of bytes in queue 

	if (TNC->RXLen == 500)
		TNC->RXLen = 0;

	Len = ReadCOMBlock(TNC->hDevice, &TNC->RXBuffer[TNC->RXLen], 500 - TNC->RXLen);

	if (Len == 0)
		return;
	
	TNC->RXLen += Len;

	Length = TNC->RXLen;

	// AEA uses SOH ETX Framing

	if (TNC->RXBuffer[0] != SOH)
	{
		// Char Mode Frame I think we need to see cmd: on end

		// If we think we are in host mode, then to could be noise - just discard.

		if (TNC->HostMode)
		{
			if (TNC->ReinitState == 2)
			{
				// Just entered host mode, so could be text command on front

				unsigned char * ptr = strchr(TNC->RXBuffer, SOH);
				
				if (ptr)
				{
					int textlen = ptr - TNC->RXBuffer;

					TNC->RXLen -= textlen;
					Length = TNC->RXLen;

					Debugprintf("%s", TNC->RXBuffer);
					Debugprintf("AEA Entered Host Mode");
					TNC->ReinitState =0; 

					memmove(TNC->RXBuffer, ptr, Length);
					goto hostFrame;
				}
			}

			Debugprintf("AEA Bad Host Frame");
			TNC->RXLen = 0;		// Ready for next frame
			return;
		}

		TNC->RXBuffer[TNC->RXLen] = 0;

//		if (TNC->RXBuffer[TNC->RXLen-2] != ':')
		if (strstr(TNC->RXBuffer, "cmd:") == 0)
		{
			if (strlen(TNC->RXBuffer) < TNC->RXLen)
				TNC->RXLen = 0;							// NULL in text mode message

			return;				// Wait for rest of frame
		}

		// Complete Char Mode Frame

		TNC->RXLen = 0;		// Ready for next frame
					
		ProcessTermModeResponse(TNC);
		return;
	}

	// Receiving a Host Mode frame

	if (TNC->HostMode == 0)	// If we are in Term Mode, discard it. Probably in recovery
	{
		TNC->RXLen = 0;		// Ready for next frame
		return;
	}

hostFrame:

	if (Length < 3)				// Minimum Frame Sise
		return;

	if (TNC->RXBuffer[Length-1] != ETB)
		return;					// Wait till we have a full frame

	if (TNC->RXBuffer[Length-2] == DLE && TNC->RXBuffer[Length-3] != DLE)
		return;					// ??? DLE ETB isn't end of frame, but DLE DLE ETB is

	ProcessHostFrame(TNC, TNC->RXBuffer, Length);	// Could have multiple packets in buffer

	TNC->RXLen = 0;		// Ready for next frame

		
	return;

}

static VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len)
{
	UCHAR * FendPtr;
	size_t NewLen;

	//	Split into Packets. By far the most likely is a single packet, so treat as special case
	//  Beware of DLE ETB and DLE DLE ETB!

	FendPtr = memchr(&rxbuffer[1], ETB, Len-1);

FENDLoop:

	if (*(FendPtr - 1) == DLE && *(FendPtr - 2) != DLE)
	{
		FendPtr++;
		FendPtr = memchr(FendPtr, ETB, Len-1);
		goto FENDLoop;
	}
	
	if (FendPtr == &rxbuffer[Len-1])
	{
		ProcessAEAPacket(TNC, &rxbuffer[1], Len - 2);
		return;
	}
		
	// Process the first Packet in the buffer

	NewLen =  FendPtr - rxbuffer -1;

	ProcessAEAPacket(TNC, &rxbuffer[1], NewLen);
	
	// Loop Back

	ProcessHostFrame(TNC, FendPtr+1, Len - NewLen - 2);
	return;

}

static BOOL WriteCommBlock(struct TNCINFO * TNC)
{
	WriteCOMBlock(TNC->hDevice, TNC->TXBuffer, TNC->TXLen);
	TNC->Timeout = 50;
	return TRUE;
}

VOID AEAPoll(int Port)
{
	struct TNCINFO * TNC = TNCInfo[Port];
	struct STREAMINFO * STREAM;
	UCHAR * Poll = TNC->TXBuffer;
	char Status[80];
	int Stream;

	if (TNC->Timeout)
	{
		TNC->Timeout--;
		
		if (TNC->Timeout)			// Still waiting
			return;

		// Timed Out

		if (TNC->HostMode == 0)
		{
			DoTermModeTimeout(TNC);
			return;
		}

		// Timed out in host mode - Clear any connection and reinit the TNC

		TNC->TNCOK = FALSE;
		TNC->HostMode = 0;
		TNC->ReinitState = 0;
				
		sprintf(Status,"%s Open but TNC not responding", TNC->PortRecord->PORTCONTROL.SerialPortName);
		SetWindowText(TNC->xIDC_COMMSSTATE, Status);

		for (Stream = 0; Stream <= MaxStreams; Stream++)
		{
			if (TNC->PortRecord->ATTACHEDSESSIONS[Stream])		// Connected
			{
				TNC->Streams[Stream].Connected = FALSE;		// Back to Command Mode
				TNC->Streams[Stream].ReportDISC = TRUE;		// Tell Nod
			}
		}
	}

	// if we have just restarted or TNC appears to be in terminal mode, run Initialisation Sequence

	if (!TNC->HostMode)
	{
		DoTNCReinit(TNC);
		return;
	}	

	if (TNC->CommandBusy)
		goto Poll;

	// We don't check for a new attach unless Timeout and CommandBusy are both zero, as we need to send a command.

	// If Pactor Session has just been attached, set Pactor Call to the connecting user's callsign


	if (TNC->PortRecord->ATTACHEDSESSIONS[0] && TNC->Streams[0].Attached == 0)
	{
		// New Attach

		int calllen;
		UCHAR TXMsg[1000];
		int datalen;
		char Msg[80];

		TNC->Streams[0].Attached = TRUE;
		TNC->Streams[0].TimeInRX = 0;

		calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4USER, TNC->Streams[0].MyCall);
		TNC->Streams[0].MyCall[calllen] = 0;
		
		datalen = sprintf(TXMsg, "OMf%s", TNC->Streams[0].MyCall);
		EncodeAndSend(TNC, TXMsg, datalen);
		TNC->InternalCmd = 'M';
		TNC->CommandBusy = TRUE;

		sprintf(Status, "In Use by %s", TNC->Streams[0].MyCall);
		SetWindowText(TNC->xIDC_TNCSTATE, Status);

		// Stop Scanning

		sprintf(Msg, "%d SCANSTOP", TNC->Port);
		
		Rig_Command( (TRANSPORTENTRY *) -1, Msg);

		// Shouldn't we also take out of standby mode?? PN is Pactor Listen, for monitoring

		return;

	}
	
	//If sending internal command list, send next element

	if (TNC->CmdSet)
	{
		char * start, * end;
		size_t len;

		start = TNC->CmdSet;
		
		if (*(start) == 0)			// End of Script
		{
			free(TNC->CmdSave);
			TNC->CmdSet = NULL;
		}
		else
		{
			end = strchr(start, 13);
			len = ++end - start -1;	// exclude cr
			TNC->CmdSet = end;
		
			EncodeAndSend(TNC, start, (int)len);
			TNC->InternalCmd = 'X';
			TNC->CommandBusy = TRUE;

			return;
		}
	}

	for (Stream = 0; Stream <= MaxStreams; Stream++)
	{
		STREAM = &TNC->Streams[Stream];

		if (STREAM->Attached)
			CheckForDetach(TNC, Stream, STREAM, TidyClose, ForcedClose, CloseComplete);
	}

	if (TNC->NeedPACTOR)
	{
		TNC->NeedPACTOR--;

		if (TNC->NeedPACTOR == 0)
		{
			EncodeAndSend(TNC, "OPA", 3);	// ??Return to packet mode??
			TNC->CommandBusy = TRUE;

			TNC->CmdSet = TNC->CmdSave = malloc(100);

			sprintf(TNC->CmdSet, "OMf%s\rOPt\rOCETRANS\r", TNC->NodeCall);  // Queue Back to Pactor Standby
			TNC->InternalCmd = 'T';
			TNC->TEXTMODE = FALSE;
			TNC->IntCmdDelay--;

			// Restart Scanning

			sprintf(Status, "%d SCANSTART 15", TNC->Port);
		
			Rig_Command( (TRANSPORTENTRY *) -1, Status);

			return;
		}
	}

	for (Stream = 0; Stream <= MaxStreams; Stream++)
	{
		if (TNC->TNCOK && TNC->Streams[Stream].BPQtoPACTOR_Q && TNC->DataBusy == FALSE)
		{
			int datalen;
			UCHAR TXMsg[1000];
			PMSGWITHLEN buffptr;
			UCHAR * MsgPtr;
			char Status[80];
			
			if (TNC->Streams[Stream].Connected)
			{
				if (Stream == 0)
				{
					// Limit amount in TX

					if (TNC->Streams[0].bytesTXed - TNC->Streams[0].BytesAcked > 200)
						continue;

					// If in IRS state for too long, force turnround

					if (TNC->TXRXState == 'R')
					{
						if (TNC->Streams[0].TimeInRX++ > 15)
						{
							EncodeAndSend(TNC, "OAG", 3);
							TNC->InternalCmd = 'A';
							TNC->CommandBusy = TRUE;
						}
						else
							goto GetStatus;
					}
					TNC->Streams[0].TimeInRX = 0;
				}

				buffptr=Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q);
				TNC->Streams[Stream].FramesQueued--;
				datalen=buffptr->Len;
				MsgPtr = buffptr->Data;

				if (TNC->SwallowSignon)
				{
					TNC->SwallowSignon = FALSE;	
					if (strstr(MsgPtr, "Connected"))	// Discard *** connected
					{
						ReleaseBuffer(buffptr);
						return;
					}
				}

				// If in CONV, and data looks binary, switch to TRAN

				TNC->NeedTurnRound = TRUE;		// Sending data, so need turnround at end

				if (TNC->TEXTMODE)
				{
					int i;
					UCHAR j;

					for (i = 0; i < datalen; i++)
					{
						j = MsgPtr[i];

						if (j > 127 ||  j == 26 || j < 10)
						{
							TNC->TEXTMODE = FALSE;
							EncodeAndSend(TNC, "OCETRANS", 8);
							Debugprintf("Switching to TRANS");
							TNC->CommandBusy = TRUE;
							TNC->InternalCmd = 'A';	
							break;
						}
					}
				}

				sprintf(TXMsg, "%c", Stream + ' ');
					
				memcpy(&TXMsg[1], buffptr->Data, datalen);
				
				EncodeAndSend(TNC, TXMsg, datalen + 1);
				ReleaseBuffer(buffptr);
				TNC->Streams[Stream].bytesTXed += datalen; 
				Debugprintf("Stream %d Sending %d, BytesTXED now %d", Stream, datalen, TNC->Streams[Stream].bytesTXed);
				TNC->Timeout = 0;
				TNC->DataBusy = TRUE;

				if (Stream == 0)
					ShowTraffic(TNC);

				if (STREAM->Disconnecting && TNC->Streams[Stream].BPQtoPACTOR_Q == 0)
					TidyClose(TNC, 0);

				return;
			}
			else
			{
				buffptr=Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q);
				datalen=buffptr->Len;
				MsgPtr = buffptr->Data;

				// Command. Do some sanity checking and look for things to process locally

				datalen--;				// Exclude CR
				MsgPtr[datalen] = 0;	// Null Terminate
				_strupr(MsgPtr);

				if ((Stream == 0) && memcmp(MsgPtr, "RADIO ", 6) == 0)
				{
					sprintf(&MsgPtr[40], "%d %s", TNC->Port, &MsgPtr[6]);
					if (Rig_Command(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK, &MsgPtr[40]))
					{
						ReleaseBuffer(buffptr);
					}
					else
					{
						buffptr->Len = sprintf(buffptr->Data, "%s", &MsgPtr[40]);
						C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
					}
					return;
				}

				if (memcmp(MsgPtr, "MODE CONV", 9) == 0)
				{
					TNC->TEXTMODE = TRUE;
					buffptr->Len = sprintf((UCHAR *)buffptr->Data,"AEA} Ok\r");
					C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);

					EncodeAndSend(TNC, "OCECONV", 7);
					TNC->CommandBusy = TRUE;

					return;
				}

				if (memcmp(MsgPtr, "MODE TRANS", 9) == 0)
				{
					TNC->TEXTMODE = FALSE;
					buffptr->Len = sprintf(buffptr->Data,"AEA} Ok\r");
					C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);

					EncodeAndSend(TNC, "OCETRANS", 8);
					TNC->CommandBusy = TRUE;

					return;
				}

				if (MsgPtr[0] == 'C' && MsgPtr[1] == ' ' && datalen > 2)	// Connect
				{
					memcpy(TNC->Streams[Stream].RemoteCall, &MsgPtr[2], 9);
					TNC->Streams[Stream].Connecting = TRUE;

					// If Stream 0, Convert C CALL to PACTOR CALL

					if (Stream == 0)
					{
						datalen = sprintf(TXMsg, "%cPG%s", Stream + '@', TNC->Streams[0].RemoteCall);
						sprintf(Status, "%s Connecting to %s",
							TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall);
						SetWindowText(TNC->xIDC_TNCSTATE, Status);
					}
					else
						datalen = sprintf(TXMsg, "%cCO %s", Stream + '@', TNC->Streams[Stream].RemoteCall);

					EncodeAndSend(TNC, TXMsg, datalen);
					TNC->InternalCmd = 'C';			// So we dont send the reply to the user.
					ReleaseBuffer(buffptr);
					TNC->Streams[Stream].Connecting = TRUE;

					return;
				}

				if (memcmp(MsgPtr, "DISCONNECT", datalen) == 0)	// Disconnect
				{
					if (Stream == 0)
					{
						EncodeAndSend(TNC, "ODI", 3);			// ??Return to packet mode??
						TNC->NeedPACTOR = 50;
						TNC->CommandBusy = TRUE;
					}
					else
					{
						sprintf(TXMsg, "%cDI", Stream + '@');
						EncodeAndSend(TNC, TXMsg, 3);
						TNC->CmdStream = Stream;
					}

					TNC->Streams[Stream].Connecting = FALSE;
					TNC->Streams[Stream].ReportDISC = TRUE;
					ReleaseBuffer(buffptr);

					return;
				}
	
				// Other Command ??

				if (Stream > 0)
					datalen = sprintf(TXMsg, "C20%s", MsgPtr);
				else
					datalen = sprintf(TXMsg, "O%s", MsgPtr);

				EncodeAndSend(TNC, TXMsg, datalen);
				ReleaseBuffer(buffptr);
				TNC->InternalCmd = 0;
				TNC->CommandBusy = TRUE;
				TNC->CmdStream = Stream;
			}
		}
	}

GetStatus:

	// Need to poll data and control channel (for responses to commands)

	// Also check status if we have data buffered (for flow control)

	if (TNC->TNCOK)
	{
		if (TNC->IntCmdDelay <= 0)
		{
			EncodeAndSend(TNC, "OOP", 3);
			TNC->InternalCmd = 'S';
			TNC->CommandBusy = TRUE;
			TNC->IntCmdDelay = 9;	// Every second
			return;
		}
		else
			TNC->IntCmdDelay--;
	}

Poll:
	// Nothing doing - send Poll (but not too often)

	TNC->PollDelay++;

	if (TNC->PollDelay < 3)
		return;

	TNC->PollDelay = 0;

	EncodeAndSend(TNC, "OGG", 3);			// Poll

	return;
}

static VOID DoTNCReinit(struct TNCINFO * TNC)
{
	UCHAR * Poll = TNC->TXBuffer;

	if (TNC->ReinitState == 1)		// Forcing back to Term
		TNC->ReinitState = 0;		// Got Response, so must be back in term mode

	if (TNC->ReinitState == 0)
	{
		// Just Starting - Send a CR to see if in Terminal or Host Mode

		char Status[80];
		
		sprintf(Status,"%s Initialising TNC", TNC->PortRecord->PORTCONTROL.SerialPortName);
		SetWindowText(TNC->xIDC_COMMSSTATE, Status);

		Poll[0] = 13;
		TNC->TXLen = 1;
		TNC->RXLen = 0;

		WriteCommBlock(TNC);

		return;
	}

	if (TNC->ReinitState == 2)		// In Term State, Sending Initialisation Commands
	{
		char * start, * end;
		size_t len;

		start = TNC->InitPtr;
		
		if (*(start) == 0)			// End of Script
		{
			// Put into Host Mode


			Poll[0] = 0x11;		// XON
			Poll[1] = 0x18;		// CAN
			Poll[2] = 0x03;		// COM

			memcpy(&Poll[3], "HOST Y\r", 7);

			TNC->TXLen = 10;
			WriteCommBlock(TNC);
			TNC->Timeout = 0;
			TNC->CommandBusy = FALSE;
			TNC->DataBusy = FALSE;

			TNC->HostMode = TRUE;		// Should now be in Host Mode
			TNC->NeedPACTOR = 50;		// Need to Send PACTOR command after 5 secs

			return;
		}
		
		end = strchr(start, 13);
		len = ++end - start;
		TNC->InitPtr = end;
		memcpy(Poll, start, len);

		TNC->TXLen = (int)len;
		WriteCommBlock(TNC);


		return;

	}
}

static VOID DoTermModeTimeout(struct TNCINFO * TNC)
{
	UCHAR * Poll = TNC->TXBuffer;

	if (TNC->ReinitState == 0)
	{
		//Checking if in Terminal Mode - Try to set back to Term Mode

		TNC->ReinitState = 1;

		EncodeAndSend(TNC, "OHON", 4);		// HOST N
		TNC->Timeout = 20;
		return;
	}
	if (TNC->ReinitState == 1)
	{
		// Forcing back to Term Mode

		TNC->ReinitState = 0;
		DoTNCReinit(TNC);				// See if worked
		return;
	}
}

static VOID ProcessTermModeResponse(struct TNCINFO * TNC)
{
	UCHAR * Poll = TNC->TXBuffer;

	Debugprintf("AEA Initstate %d Response %s", TNC->ReinitState, TNC->RXBuffer);

	if (TNC->ReinitState == 0 || TNC->ReinitState == 1) 
	{
		// Testing if in Term Mode. It is, so can now send Init Commands

		TNC->InitPtr = TNC->InitScript;
		TNC->ReinitState = 2;
		DoTNCReinit(TNC);		// Send First Command
		return;
	}
	if (TNC->ReinitState == 2)
	{
		// Sending Init Commands

		DoTNCReinit(TNC);		// Send Next Command
		return;
	}
}

static VOID ProcessAEAPacket(struct TNCINFO * TNC, UCHAR * Msg, size_t Len)
{
	PMSGWITHLEN buffptr;
	char * Buffer = &Msg[1];			// Data portion of frame
	char * Call;
	char Status[80];
	int Stream = 0;
	int Opcode;
	struct STREAMINFO * STREAM;

	// Any valid frame is an ACK

	TNC->TNCOK = TRUE;
	TNC->Timeout = 0;

	Len = DLEDecode(Msg, Msg, Len);		// Remove KISS transparency

	Stream = Msg[0] & 15;
	Opcode = Msg[0] >> 4;

	STREAM = &TNC->Streams[Stream];

	if (Msg[0] == 'O' && Msg[1] == 'G' && Msg[2] == 'G')
	{
		if (Msg[3] == 0)
		{
			// OK Response

			sprintf(Status,"%s TNC link OK", TNC->PortRecord->PORTCONTROL.SerialPortName);
			SetWindowText(TNC->xIDC_COMMSSTATE, Status);

			return;
		}
	}

	if (Msg[0] == '/')			// 2F
	{
		char * Eptr;
		
		// Echoed Data

		Len--;

		Eptr = memchr(Buffer, 0x1c, Len);

		if (Eptr) 
			Debugprintf("Echoed 1c followed by %x", *(++Eptr));

		TNC->Streams[0].BytesAcked += (int)Len;

		Debugprintf("Ack for %d, BytesAcked now %d", Len, TNC->Streams[0].BytesAcked);
		ShowTraffic(TNC);

		// If nothing more to send, turn round link
						
		if ((TNC->Streams[0].BPQtoPACTOR_Q == 0) && TNC->NeedTurnRound &&
			(TNC->Streams[0].BytesAcked >= TNC->Streams[0].bytesTXed))		// Nothing following and all acked
			{
				Debugprintf("AEA Sent = Acked - sending Turnround");
						
				if (TNC->TEXTMODE == 0)
				{
					// In Trans - switch back

					TNC->TEXTMODE = TRUE;
					EncodeAndSend(TNC, "OCECONV", 7);
					Debugprintf("Switching to CONV");
					TNC->CommandBusy = TRUE;
					TNC->InternalCmd = 'A';	
					TNC->NeedTRANS = TRUE;
				}
					
				TNC->NeedTurnRound = FALSE;
				EncodeAndSend(TNC, OverMsg, 2);
				TNC->Timeout = 0;						//  No response to data
		}
		return;
	}

	if (Opcode == 3)
	{
		// Received Data

		// Pass to Appl

		buffptr = GetBuff();
		if (buffptr == NULL) return;	// No buffers, so ignore

		Len--;							// Remove Header

		buffptr->Len = Len;				// Length
		TNC->Streams[Stream].bytesRXed += (int)Len;
		memcpy(&buffptr->Data[0], Buffer, Len);
		C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);

		if (Stream == 0)
			ShowTraffic(TNC);

		return;
	}


	if (Opcode == 4)
	{
		// Link Status or Command Response

		TNC->CommandBusy = FALSE;

		if (TNC->InternalCmd)
		{
			// Process it

			if (TNC->InternalCmd == 'S')		// Status
			{
//				if (Msg[3] == 'P' && Msg[4] == 'G')
				{
					SetWindowText(TNC->xIDC_STATE, status[Msg[5] - 0x30]);
					
					TNC->TXRXState = Msg[6];

					if (Msg[6] == 'S')
						SetWindowText(TNC->xIDC_TXRX, "Sender");
					else
						SetWindowText(TNC->xIDC_TXRX, "Receiver");

					Msg[12] = 0;
					SetWindowText(TNC->xIDC_MODE, Msg);

					// Testing.. I think ZF returns buffers

					EncodeAndSend(TNC, "OZF", 3);
					TNC->InternalCmd = 'Z';
					TNC->CommandBusy = TRUE;
				}
			
				return;
			}

			if (TNC->InternalCmd == 'Z')		// Buffers?
			{
				Msg[Len] = 0;
				SetWindowText(TNC->xIDC_BUFFERS, &Msg[3]);
				return;
			}

			return;
		}

		// Reply to Manual command - Pass to Appl

		Stream = TNC->CmdStream;
		STREAM = &TNC->Streams[Stream];


		buffptr = GetBuff();

		if (buffptr == NULL) return;			// No buffers, so ignore

		Buffer[Len - 1] = 13;
		Buffer[Len] = 0;

		buffptr->Len = sprintf(&buffptr->Data[0],"AEA} %s", Buffer);

		C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);

		return;

	}

	if (Opcode == 5)
	{
		// Link Messages (Connect, etc)

		if (Stream == 15)
		{
			// SOH $5F X X $00 ETB data acknowledgement 

			if (Msg[1] == 'X' && Msg[2] == 'X' && Msg[3] == 0)
			{
				TNC->DataBusy = FALSE;

				if (TNC->NeedTRANS)		// Sent CTRL/Z in conv mode - switch back to trans
				{
					TNC->NeedTRANS = FALSE;
					TNC->TEXTMODE = FALSE;
					EncodeAndSend(TNC, "OCETRANS", 8);
					Debugprintf("Switching to TRANS");
					TNC->CommandBusy = TRUE;
					TNC->InternalCmd = 'A';	
				}
				else
					EncodeAndSend(TNC, "OGG", 3);			// Send another Poll
			}
			return;
		}

		if (strstr(Buffer, "DISCONNECTED") || strstr(Buffer, "Timeout"))
		{
			if ((TNC->Streams[Stream].Connecting | TNC->Streams[Stream].Connected) == 0)
			{
				// Not connected or Connecting. Probably response to going into Pactor Listen Mode

				return;
			}
	
			if (STREAM->Connecting && STREAM->Disconnecting == FALSE)
			{
				// Connect Failed
			
				buffptr = GetBuff();
				if (buffptr == 0) return;			// No buffers, so ignore

				buffptr->Len = sprintf(&buffptr->Data[0], "*** Failure with %s\r", TNC->Streams[Stream].RemoteCall);

				C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
	
				TNC->Streams[Stream].Connecting = FALSE;
				TNC->Streams[Stream].Connected = FALSE;				// In case!
				TNC->Streams[Stream].FramesQueued = 0;

				return;
			}

			// Connected, or Disconnecting - Release Session

			TNC->Streams[Stream].Connecting = FALSE;
			TNC->Streams[Stream].Connected = FALSE;		// Back to Command Mode
			TNC->Streams[Stream].FramesQueued = 0;

			if (STREAM->Disconnecting == FALSE)
				STREAM->ReportDISC = TRUE;		// Tell Node

			STREAM->Disconnecting = FALSE;

			// Need to reset Pactor Call in case it was changed

			TNC->NeedPACTOR = 20;

			if (Stream == 0)
			{
				// Claar any data from buffers 

				EncodeAndSend(TNC, "OTC", 3);			// Clear buffers
				TNC->NeedPACTOR = 20;
				TNC->InternalCmd = 'T';
				TNC->CommandBusy = TRUE;
			}

			return;
		}

		Call = strstr(Buffer, "NNECTED to");

		if (Call)
		{	
			Call+=11;					// To Callsign
			
			if (Stream == 0)
			{
				Buffer[Len-2] = 0;
			}

			TNC->Streams[Stream].bytesRXed = TNC->Streams[Stream].bytesTXed = TNC->Streams[Stream].BytesAcked = 0;
			TNC->Streams[Stream].ConnectTime = time(NULL); 

			if (Stream == 0)
			{
				// Stop Scanner

				char Msg[80];
				
				sprintf(Msg, "%d SCANSTOP", TNC->Port);

				Rig_Command( (TRANSPORTENTRY *) -1, Msg);

				ShowTraffic(TNC);

			}

			if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0)
			{
				// Incoming Connect

				ProcessIncommingConnect(TNC, Call, Stream, TRUE);

				if (Stream == 0)
				{
					struct WL2KInfo * WL2K = TNC->WL2K;
					char FreqAppl[10] = "";				// Frequecy-specific application
	
					if (TNC->RIG && TNC->RIG != &TNC->DummyRig)
					{
						// If Scan Entry has a Appl, save it

						if (TNC->RIG->FreqPtr && TNC->RIG->FreqPtr[0]->APPL[0])
							strcpy(FreqAppl, &TNC->RIG->FreqPtr[0]->APPL[0]);
						}

					// We are going to Send something, so turn link round
				
					EncodeAndSend(TNC, "OAG", 3);
					TNC->InternalCmd = 'A';
					TNC->CommandBusy = TRUE;
				
					sprintf(Status, "%s Connected to %s Inbound", TNC->Streams[0].RemoteCall, TNC->NodeCall);
					SetWindowText(TNC->xIDC_TNCSTATE, Status);

					// If an autoconnect APPL is defined, send it

					if (FreqAppl[0])				// Frequency specific APPL overrides TNC APPL
					{
						buffptr = GetBuff();
						if (buffptr == 0) return;	// No buffers, so ignore

						buffptr->Len = sprintf(&buffptr->Data[0], "%s\r", FreqAppl);
						C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
						TNC->SwallowSignon = TRUE;
						return;
					}

					if (TNC->ApplCmd)
					{
						buffptr = GetBuff();
						if (buffptr == 0) return;			// No buffers, so ignore

						buffptr->Len = sprintf(&buffptr->Data[0], "%s\r", TNC->ApplCmd);
						C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
												
						TNC->SwallowSignon = TRUE;

						return;
					}
				}

				if (FULL_CTEXT && HFCTEXTLEN == 0)
				{
					char CTBuff[300];
					int Len = CTEXTLEN, CTPaclen = 50;
					int Next = 0;

					CTBuff[0] = Stream + ' ';

					while (Len > CTPaclen)		// CTEXT Paclen
					{
						memcpy(&CTBuff[1], &CTEXTMSG[Next], CTPaclen);
						EncodeAndSend(TNC, CTBuff, CTPaclen + 1);
						Next += CTPaclen;
						Len -= CTPaclen;
					}

					memcpy(&CTBuff[1], &CTEXTMSG[Next], Len);
					EncodeAndSend(TNC, CTBuff, Len + 1);
				}
				return;

			}
			else
			{
				// Connect Complete
			
				buffptr = GetBuff();
				if (buffptr == 0) return;			// No buffers, so ignore

				buffptr->Len  = sprintf(&buffptr->Data[0], "*** Connected to %s\r", Call);;

				C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
	
				TNC->Streams[Stream].Connecting = FALSE;
				TNC->Streams[Stream].Connected = TRUE;			// Subsequent data to data channel
	
				if (Stream == 0)
				{
					sprintf(Status, "%s Connected to %s Outbound", TNC->NodeCall, TNC->Streams[0].RemoteCall);
					SetWindowText(TNC->xIDC_TNCSTATE, Status);
					UpdateMH(TNC, Call, '+', 'O');
				}

				return;
			}
		}
	}	
}


int	DLEDecode(UCHAR * inbuff, UCHAR * outbuff, size_t len)
{
	unsigned int i, txptr = 0;
	UCHAR c;

	for (i=0; i<len; i++)
	{
		c = inbuff[i];

		if (c == DLE)
			c = inbuff[++i];

		outbuff[txptr++] = c;
	}

	return txptr;

}

static VOID EncodeAndSend(struct TNCINFO * TNC, UCHAR * txbuffer, int Len)
{
	// Send A Packet With DLE Encoding Encoding

	TNC->TXLen = DLEEncode(txbuffer, TNC->TXBuffer, Len);

	WriteCommBlock(TNC);
}

static int DLEEncode(UCHAR * inbuff, UCHAR * outbuff, size_t len)
{
	unsigned int i, txptr = 0;
	UCHAR c;

	outbuff[0] = SOH;
	txptr=1;

	for (i=0; i<len; i++)
	{
		c = inbuff[i];
		
		switch (c)
		{
		case DLE:
		case SOH:
		case ETB:

			outbuff[txptr++] = DLE;
//			Debugprintf("Escaping %x", c);
		}
		
		outbuff[txptr++] = c;
	}

	outbuff[txptr++] = ETB;

	return txptr;

}


VOID TidyClose(struct TNCINFO * TNC, int Stream)
{
	if (Stream == 0)					// Pactor Stream
	{
		EncodeAndSend(TNC, "ODI", 3);	// Disconnect

		TNC->InternalCmd = 'P';
		TNC->CommandBusy = TRUE;
	}
	else
	{
		UCHAR TXMsg[10];

		sprintf(TXMsg, "%cDI", Stream + '@');
		EncodeAndSend(TNC, TXMsg, 4);		// Send twice - must force a disconnect
	}
}

VOID ForcedClose(struct TNCINFO * TNC, int Stream)
{
	TidyClose(TNC, Stream);			// Send DI again - can't do much else
}

VOID CloseComplete(struct TNCINFO * TNC, int Stream)
{
	TNC->NeedPACTOR = 50;	
}
