/*
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
*/

/*

Using code from 6pack Linux Kernel driver with the following licence and credits

 *	6pack driver version 0.4.2, 1999/08/22
 *
 *	This module:
 *		This module 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
 *		2 of the License, or (at your option) any later version.
 *
 * 		This module implements the AX.25 protocol for kernel-based
 *		devices like TTYs. It interfaces between a raw TTY, and the
 *		kernel's AX.25 protocol layers, just like slip.c.
 *		AX.25 needs to be separated from slip.c while slip.c is no
 *		longer a static kernel device since it is a module.
 *
 *	Author: Andreas Könsgen <ajk@ccac.rwth-aachen.de>
 *
 *	Lots of stuff has been taken from mkiss.c, written by
 *	Hans Alblas <hans@esrac.ele.tue.nl>
 *
 *	with the fixes from
 *	
 *	Jonathan (G4KLX)	Fixed to match Linux networking changes - 2.1.15.
 *	Matthias (DG2FEF)       Fixed bug in ax25_close(): dev_lock_wait() was
 *                              called twice, causing a deadlock.
 */


//	6pack needs fast response to received characters, and I want to be able to operate over TCP links as well as serial.
//	So I think the character level stuff may need to run in a separate thread, probably using select.
//
//	I also need to support multiple 6pack ports.

// ?? Do we add this as a backend to KISS driver or a separate Driver. KISS Driver is already quite messy. Not decided yet.

// ?? If using serial/real TNC we need to be able to interleave control and data bytes, but I think with TCP/QtSM it won't be necessary
// ?? Also a don't see any point in running multiple copies of QtSM on one port, but maybe should treat the QtSM channels as
//	multidropped ports for scheduling (?? only if on same radio ??)

//	?? I think it needs to look like a KISS (L2) driver but will need a transmit scheduler level to do DCD/CSMA/PTT processing,
//	ideally with an interlock to other drivers on same port. This needs some thought with QtSM KISS with multiple modems on one channel





#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

#define _CRT_SECURE_NO_DEPRECATE

#include "compatbits.h"
#include <string.h>
#include "cheaders.h"

#include "bpq32.h"

#include "tncinfo.h"

#ifndef WIN32

#define APIENTRY
#define DllExport
#define VOID void

#else
#include <windows.h>
#endif

/****************************************************************************
 *	Defines for the 6pack driver.
 ****************************************************************************/

/*
	provisoric define
*/
#define SIXP_MAJOR	MKISS_MAJOR
/* end of provisoric defines */


#define TRUE	1
#define FALSE	0

#define AX25_MAXDEV	16		/* MAX number of AX25 channels;
					   This can be overridden with
					   insmod -oax25_maxdev=nnn	*/
#define AX_MTU		236	

/* 6pack protocol bytes/masks. */
#define SIXP_INIT_CMD		0xE8
#define SIXP_TNC_FOUND		0xE9
#define SIXP_CMD_MASK		0xC0
#define SIXP_PRIO_CMD_MASK	0x80
#define SIXP_PRIO_DATA_MASK	0x38
#define SIXP_STD_CMD_MASK	0x40
#define SIXP_DCD_MASK		0x08
#define SIXP_RX_DCD_MASK	0x18
#define SIXP_CHN_MASK		0x07
#define SIXP_TX_MASK		0x20
#define SIXP_CON_LED_ON		0x68
#define SIXP_STA_LED_ON		0x70
#define SIXP_LED_OFF		0x60

/* checksum for a valid 6pack encapsulated packet */
#define SIXP_CHKSUM		0xFF

/* priority commands */
#define SIXP_SEOF		0x40	/* TX underrun */
#define SIXP_TX_URUN		0x48	/* TX underrun */
#define SIXP_RX_ORUN		0x50	/* RX overrun */
#define SIXP_RX_BUF_OVL		0x58	/* RX overrun */

struct sixPackTNCInfo
{
	// info for each TNC in chain

	int                magic;

	char * name;

	/* These are pointers to the malloc()ed frame buffers. */
	unsigned char      *rbuff;		/* receiver buffer		*/
	int                rcount;		/* received chars counter       */
	unsigned char      *xbuff;		/* transmitter buffer		*/
	unsigned char      *xhead;		/* pointer to next byte to XMIT */
	int                xleft;		/* bytes left in XMIT queue     */

	/* SLIP interface statistics. */
	unsigned long      rx_packets;		/* inbound frames counter	*/
	unsigned long      tx_packets;		/* outbound frames counter      */
	unsigned long      rx_errors;		/* Parity, etc. errors          */
	unsigned long      tx_errors;		/* Planned stuff                */
	unsigned long      rx_dropped;		/* No memory for skb            */
	unsigned long      tx_dropped;		/* When MTU change              */
	unsigned long      rx_over_errors;	/* Frame bigger then SLIP buf.  */

	/* Detailed SLIP statistics. */
	int                 mtu;		/* Our mtu (to spot changes!)   */
	int                 buffsize;	/* Max buffers sizes            */


	unsigned char       flags;		/* Flag values/ mode etc	*/
#define AXF_INUSE	0				/* Channel in use               */
#define AXF_ESCAPE	1               /* ESC received                 */
#define AXF_ERROR	2               /* Parity, etc. error           */
#define AXF_KEEPTEST	3			/* Keepalive test flag		*/
#define AXF_OUTWAIT	4				/* is outpacket was flag	*/

	int                 mode;


	/* variables for the state machine */

	unsigned char	tnc_ok;
	unsigned char	status;
	unsigned char	status1;
	unsigned char	status2;

	unsigned char	duplex;
	unsigned char	led_state;
	unsigned char	tx_enable;
	
	unsigned char	raw_buf[4];		/* receive buffer */
	unsigned char	cooked_buf[400];	/* receive buffer after 6pack decoding */
	
	unsigned int	rx_count;		/* counter for receive buffer */
	unsigned int	rx_count_cooked;	/* counter for receive buffer after 6pack decoding */

	unsigned char	tx_delay;
	unsigned char	persistance;
	unsigned char	slottime;

};


struct sixPackPortInfo
{
	// Per port (chain of TNC's)

	unsigned int linkOK;			// Set if response is received
	unsigned int reinitTimer;

	struct sixPackTNCInfo * TNCS[8];	// Max TNCs in chain Not sure if real 6pack uses o or 1 for first
};


#define AX25_MAGIC		0x5316
#define SIXP_DRIVER_MAGIC	0x5304

#define SIXP_INIT_RESYNC_TIMEOUT	150	/* in 10 ms */
#define SIXP_RESYNC_TIMEOUT		500	/* in 10 ms */

/* default radio channel access parameters */
#define SIXP_TXDELAY			25	/* in 10 ms */
#define SIXP_PERSIST			50
#define SIXP_SLOTTIME			10	/* in 10 ms */

static int sixpack_encaps(unsigned char *tx_buf, unsigned char *tx_buf_raw, int length, unsigned char tx_delay);
static void sixpack_decaps(struct sixPackPortInfo *, unsigned char);

static void decode_prio_command(unsigned char, struct sixPackTNCInfo *);
static void decode_std_command(unsigned char, struct sixPackTNCInfo *);
static void decode_data(unsigned char, struct sixPackTNCInfo *);
static void resync_tnc(unsigned long);
static void xmit_on_air(struct sixPackTNCInfo *ax);
static void start_tx_timer(struct sixPackTNCInfo *ax);
int Connectto6Pack(int port);

VOID __cdecl Debugprintf(const char * format, ...);

/* Set the "sending" flag.  This must be atomic, hence the ASM. */

static void ax_lock(struct sixPackTNCInfo *ax)
{
//	if (test_and_set_bit(0, (void *)&ax->dev->tbusy))
//		printk(KERN_ERR "6pack: %s: trying to lock already locked device!\n", ax->dev->name);
}


/* Clear the "sending" flag.  This must be atomic, hence the ASM. */
static void ax_unlock(struct sixPackTNCInfo *ax)
{
//	if (!test_and_clear_bit(0, (void *)&ax->dev->tbusy))
//		printk(KERN_ERR "6pack: %s: trying to unlock already unlocked device!\n", ax->dev->name);
}
/* Send one completely decapsulated AX.25 packet to the AX.25 layer. */
static void ax_bump(struct sixPackTNCInfo *ax)
{
}

VOID SixPackProcessReceivedPacket(struct TNCINFO * TNC)
{
	int InputLen, MsgLen;
	unsigned char * ptr;
	char Buffer[4096];

	if (TNC->InputLen > 8000)	// Shouldnt have packets longer than this
		TNC->InputLen=0;

	InputLen = recv(TNC->TCPSock, &TNC->ARDOPBuffer[TNC->InputLen], 8192 - TNC->InputLen, 0);

	if (InputLen == 0 || InputLen == SOCKET_ERROR)
	{
		// Does this mean closed?

		int err = GetLastError();

		closesocket(TNC->TCPSock);

		TNC->TCPSock = 0;

		TNC->CONNECTED = FALSE;
		TNC->Streams[0].ReportDISC = TRUE;

		sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
		MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);

		return;					
	}

	TNC->InputLen += InputLen;

	// Process Bytes

}



static int sixpack_encaps(unsigned char *tx_buf, unsigned char *tx_buf_raw, int length, unsigned char tx_delay)
{
	int count = 0;
	unsigned char checksum = 0, buf[400];
	int raw_count = 0;

	tx_buf_raw[raw_count++] = SIXP_PRIO_CMD_MASK | SIXP_TX_MASK;
	tx_buf_raw[raw_count++] = SIXP_SEOF;

	buf[0] = tx_delay;
	for(count = 1; count < length; count++)
		buf[count] = tx_buf[count];

	for(count = 0; count < length; count++)
		checksum += buf[count];
	buf[length] = (unsigned char)0xff - checksum;
	
	for(count = 0; count <= length; count++) {
		if((count % 3) == 0) {
			tx_buf_raw[raw_count++] = (buf[count] & 0x3f);
			tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x30);
		}
		else if((count % 3) == 1) {
			tx_buf_raw[raw_count++] |= (buf[count] & 0x0f);
			tx_buf_raw[raw_count] =
				((buf[count] >> 2) & 0x3c);
		} else {
			tx_buf_raw[raw_count++] |= (buf[count] & 0x03);
			tx_buf_raw[raw_count++] =
				(buf[count] >> 2);
		} /* else */
	} /* for */
	if ((length % 3) != 2)
		raw_count++;
	tx_buf_raw[raw_count++] = SIXP_SEOF;
	return(raw_count);
}


static void sixpack_decaps(struct sixPackPortInfo *ax, unsigned char inbyte)
{
	// if (inbyte == SIXP_TNC_FOUND) {
	// This is wrong. It will only work with one TNC in the chain. Should look for E8 in top bits. Bottome 3 bits are number in chain

	if ((inbyte & SIXP_INIT_CMD) == 0)
	{
		int Count = inbyte & SIXP_CHN_MASK;
		struct sixPackTNCInfo * sixTNC;

		int i;

		Debugprintf("6pack: %d TNCs found. ", Count);

		// clear existing TNC strucs and allocate new ones if needed

		for (i = 0; i < Count; i++)
		{
			sixTNC = ax->TNCS[i];

			if (sixTNC)
				memset(sixTNC, 0, sizeof(struct sixPackTNCInfo));
			else
				sixTNC = ax->TNCS[i] = zalloc(sizeof(struct sixPackTNCInfo));

			sixTNC->tnc_ok = 1;
		}

		ax->linkOK = TRUE;

	}
/*
if((inbyte & SIXP_PRIO_CMD_MASK) != 0)
		decode_prio_command(inbyte, ax);
	else if((inbyte & SIXP_STD_CMD_MASK) != 0)
		decode_std_command(inbyte, ax);
	else {
		if ((ax->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)
			decode_data(inbyte, ax);
	} 
*/
}

/* identify and execute a 6pack priority command byte */

void decode_prio_command(unsigned char cmd, struct sixPackTNCInfo *ax)
{
	unsigned char channel;

	channel = cmd & SIXP_CHN_MASK;
	if ((cmd & SIXP_PRIO_DATA_MASK) != 0) {     /* idle ? */

	/* RX and DCD flags can only be set in the same prio command,
	   if the DCD flag has been set without the RX flag in the previous
	   prio command. If DCD has not been set before, something in the
	   transmission has gone wrong. In this case, RX and DCD are
	   cleared in order to prevent the decode_data routine from
	   reading further data that might be corrupt. */

		if (((ax->status & SIXP_DCD_MASK) == 0) &&
			((cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)) {
				if (ax->status != 1)
					Debugprintf("6pack: protocol violation\n");
				else
					ax->status = 0;
				cmd &= !SIXP_RX_DCD_MASK;
		}
		ax->status = cmd & SIXP_PRIO_DATA_MASK;
	} /* if */


        /* if the state byte has been received, the TNC is present,
           so the resync timer can be reset. */

	if (ax->tnc_ok == 1) {
//		del_timer(&(ax->resync_t));
//		ax->resync_t.data = (unsigned long) ax;
//		ax->resync_t.function = resync_tnc;
//		ax->resync_t.expires = jiffies + SIXP_INIT_RESYNC_TIMEOUT;
//		add_timer(&(ax->resync_t));
	}

	ax->status1 = cmd & SIXP_PRIO_DATA_MASK;
}

/* try to resync the TNC. Called by the resync timer defined in
  decode_prio_command */

static void resync_tnc(unsigned long channel)
{
	static char resync_cmd = SIXP_INIT_CMD;
	struct sixPackTNCInfo *ax = (struct sixPackTNCInfo *) channel;

	Debugprintf("6pack: resyncing TNC\n");

	/* clear any data that might have been received */
	
	ax->rx_count = 0;
	ax->rx_count_cooked = 0;

	/* reset state machine */

	ax->status = 1;
	ax->status1 = 1;
	ax->status2 = 0;
	ax->tnc_ok = 0;
	
	/* resync the TNC */

	ax->led_state = SIXP_LED_OFF;
//	ax->tty->driver.write(ax->tty, 0, &(ax->led_state), 1);
//	ax->tty->driver.write(ax->tty, 0, &resync_cmd, 1);


	/* Start resync timer again -- the TNC might be still absent */

//	del_timer(&(ax->resync_t));
//	ax->resync_t.data = (unsigned long) ax;
//	ax->resync_t.function = resync_tnc;
//	ax->resync_t.expires = jiffies + SIXP_RESYNC_TIMEOUT;
//	add_timer(&(ax->resync_t));
}



/* identify and execute a standard 6pack command byte */

void decode_std_command(unsigned char cmd, struct sixPackTNCInfo *ax)
{
	unsigned char checksum = 0, channel;
	unsigned int i;

	channel = cmd & SIXP_CHN_MASK;
	switch(cmd & SIXP_CMD_MASK) {     /* normal command */
		case SIXP_SEOF:
			if ((ax->rx_count == 0) && (ax->rx_count_cooked == 0)) {
				if ((ax->status & SIXP_RX_DCD_MASK) ==
					SIXP_RX_DCD_MASK) {
					ax->led_state = SIXP_CON_LED_ON;
//					ax->tty->driver.write(ax->tty, 0, &(ax->led_state), 1);
				} /* if */
			} else {
				ax->led_state = SIXP_LED_OFF;
//				ax->tty->driver.write(ax->tty, 0, &(ax->led_state), 1);
				/* fill trailing bytes with zeroes */
				if (ax->rx_count == 2) {
					decode_data(0, ax);
					decode_data(0, ax);
					ax->rx_count_cooked -= 2;
				}
				else if (ax->rx_count == 3) {
					decode_data(0, ax);
					ax->rx_count_cooked -= 1;
				}
				for (i=0; i< ax->rx_count_cooked; i++)
					checksum += ax->cooked_buf[i];
				if (checksum != SIXP_CHKSUM) {
					Debugprintf("6pack: bad checksum %2.2x\n", checksum);
				} else {
					ax->rcount = ax->rx_count_cooked-1;
					ax_bump(ax);
				} /* else */
				ax->rx_count_cooked = 0;
			} /* else */
			break;
		case SIXP_TX_URUN:
			Debugprintf("6pack: TX underrun\n");
			break;
		case SIXP_RX_ORUN:
			Debugprintf("6pack: RX overrun\n");
			break;
		case SIXP_RX_BUF_OVL:
			Debugprintf("6pack: RX buffer overflow\n");
	} /* switch */
} /* function */

/* decode 4 sixpack-encoded bytes into 3 data bytes */

void decode_data(unsigned char inbyte, struct sixPackTNCInfo *ax)
{

	unsigned char *buf;

	if (ax->rx_count != 3)
		ax->raw_buf[ax->rx_count++] = inbyte;
	else {
		buf = ax->raw_buf;
		ax->cooked_buf[ax->rx_count_cooked++] =
			buf[0] | ((buf[1] << 2) & 0xc0);
		ax->cooked_buf[ax->rx_count_cooked++] =
			(buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0);
		ax->cooked_buf[ax->rx_count_cooked++] =
			(buf[2] & 0x03) | (inbyte << 2);
		ax->rx_count = 0;
	}
}

// This takes a packet from the KISS driver and converts to 6pack format

static void ax_encaps(struct sixPackTNCInfo *ax, unsigned char *icp, int len)
{
	unsigned char *p;
	int count;
	struct sixPackTNCInfo *tmp_ax;

	if (len > ax->mtu) {		/* Sigh, shouldn't occur BUT ... */
		len = ax->mtu;
		Debugprintf("6pack: %s: truncating oversized transmit packet!\n", ax->name);
		ax->tx_dropped++;
		ax_unlock(ax);
		return;
	}

	p = icp;

	if (p[0] > 5)
	{
		Debugprintf("%s: invalid KISS command -- dropped\n", ax->name);
		ax_unlock(ax);
		return;
	}

	if ((p[0] != 0) && (len > 2))
	{
		Debugprintf("%s: KISS command packet too long -- dropped\n", ax->name);
		ax_unlock(ax);
		return;
	}

	if ((p[0] == 0) && (len < 15))
	{
		Debugprintf("%s: bad AX.25 packet to transmit -- dropped\n", ax->name);
		ax->tx_dropped++;
		ax_unlock(ax);
		return;
	}

	switch(p[0])
	{
		case 1:	tmp_ax->tx_delay = p[1];	break;
		case 2:	tmp_ax->persistance = p[1];	break;
		case 3: tmp_ax->slottime = p[1];	break;
		case 4: Debugprintf("6pack: got KISS command 4 -- ignored\n");
			break;
		case 5: tmp_ax->duplex = p[1];		break;
	}

	// How do we handle ACKMODE ??

	if (p[0] != 0) {
		ax_unlock(ax);
		return;
	}
	
	count = sixpack_encaps(p, (unsigned char *) tmp_ax->xbuff, len, tmp_ax->tx_delay);
	tmp_ax->xleft = count;
	tmp_ax->status2 = count;
	tmp_ax->xhead = tmp_ax->xbuff;
	
	/* in case of DAMA or fullduplex operation, we don't take care
	   about the state of the DCD or of any timers, as the determination
	   of the correct time to send is the job of the AX.25 layer. We send
	   immediately after data has arrived. */

	if (tmp_ax->duplex == 1) { /* DAMA / fullduplex */
		xmit_on_air(tmp_ax);
	} else { /* CSMA */
		start_tx_timer(tmp_ax);
	} /* if ax->duplex */
} /* ax_encaps */

// This starts sending a packet to a TNC. TNC will immediately send (after TXD)

/* the next two functions perform the persistence/slottime algorithm for CSMA access.
   If the persistence check was successful, write the data to the serial driver.
   Note that in case of DAMA operation, the data is not sent here. */

/* set timer to time out slottime */
static void start_tx_timer(struct sixPackTNCInfo *ax)
{
//	del_timer(&(ax->tx_t));
//	ax->tx_t.data = (unsigned long) ax;
//	ax->tx_t.function = tx_schedule;
//	ax->tx_t.expires = jiffies + (((ax->slottime)+1)*HZ)/100;
//	add_timer(&(ax->tx_t));
}

/* compute random number and check if it is ok to send */
static void tx_schedule(unsigned long channel)
{
	struct sixPackTNCInfo *ax = (struct sixPackTNCInfo *) channel;
	static unsigned char random;
	
	random = random * 17 + 41;

	if (((ax->status1 & SIXP_DCD_MASK) == 0) && (random < ax->persistance)) 
		xmit_on_air(ax);
	else
		start_tx_timer(ax);
}


static void xmit_on_air(struct sixPackTNCInfo *ax)
{
	int actual;

//	ax->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
	ax->led_state = SIXP_STA_LED_ON;
//	ax->tty->driver.write(ax->tty, 0, &(ax->led_state), 1);
	ax->tx_enable = 1;
//	actual = ax->tty->driver.write(ax->tty, 0, ax->xbuff, ax->status2);
	ax->tx_packets++;
//	ax->dev->trans_start = jiffies;
	ax->xleft -= actual;
	ax->xhead = ax->xbuff + actual;
	ax->led_state = SIXP_LED_OFF;
//	ax->tty->driver.write(ax->tty, 0, &(ax->led_state), 1);
} /* xmit_on_air */


// BPQ Driver Module Interface


extern int (WINAPI FAR *GetModuleFileNameExPtr)();
extern int (WINAPI FAR *EnumProcessesPtr)();

static int Socket_Data(int sock, int error, int eventcode);

VOID MoveWindows(struct TNCINFO * TNC);
static VOID SendToTNC(struct TNCINFO * TNC, int Stream, UCHAR * Encoded, int EncLen);
int ReadCOMBlockEx(HANDLE fd, char * Block, int MaxLength, BOOL * Error);
BOOL SerialWriteCommBlock(struct TNCINFO * TNC);
void SerialCheckRX(struct TNCINFO * TNC);
int SerialSendData(struct TNCINFO * TNC, UCHAR * data, int txlen);
int SerialSendCommand(struct TNCINFO * TNC, UCHAR * data);
int DoScanLine(struct TNCINFO * TNC, char * Buff, int Len);
VOID SendInitScript(struct TNCINFO * TNC);
int SIXPACKGetLine(char * buf);
int ProcessEscape(UCHAR * TXMsg);
VOID SIXPACKProcessReceivedPacket(struct TNCINFO * TNC);
static int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len);
int ConnecttoKISS(int port);
TRANSPORTENTRY * SetupNewSession(TRANSPORTENTRY * Session, char * Bufferptr);
BOOL DecodeCallString(char * Calls, BOOL * Stay, BOOL * Spy, UCHAR * AXCalls);
BOOL FindLink(UCHAR * LinkCall, UCHAR * OurCall, int Port, struct _LINKTABLE ** REQLINK);
VOID RESET2(struct _LINKTABLE * LINK);
VOID L2SENDXID(struct _LINKTABLE * LINK);
VOID SENDSABM(struct _LINKTABLE * LINK);

static char ClassName[]="KISSSTATUS";
static char WindowTitle[] = "SIXPACK";
static int RigControlRow = 165;

#ifndef LINBPQ
#include <commctrl.h>
#endif

extern int SemHeldByAPI;

static RECT Rect;

static int ProcessLine(char * buf, int Port);

VOID WritetoTrace(struct TNCINFO * TNC, char * Msg, int Len);

#define	FEND	0xC0	// KISS CONTROL CODES 
#define	FESC	0xDB
#define	TFEND	0xDC
#define	TFESC	0xDD

static 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

	if (_stricmp(buf, "ADDR"))
		return FALSE;						// Must start with ADDR

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

	BPQport = Port;
	p_ipad = ptr;

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

	TNC->DefaultMode = TNC->WL2KMode = 0;	// Packet 1200

	TNC->InitScript = malloc(1000);
	TNC->InitScript[0] = 0;
	
	if (p_ipad == NULL)
		p_ipad = strtok(NULL, " \t\n\r");

	if (p_ipad == NULL) return (FALSE);
	
	p_port = strtok(NULL, " \t\n\r");
			
	if (p_port == NULL) return (FALSE);

	WINMORport = atoi(p_port);

	TNC->destaddr.sin_family = AF_INET;
	TNC->destaddr.sin_port = htons(WINMORport);

	TNC->HostName = malloc(strlen(p_ipad)+1);

	if (TNC->HostName == NULL) return TRUE;

	strcpy(TNC->HostName,p_ipad);

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

	if (ptr)
	{
		if (_stricmp(ptr, "PTT") == 0)
		{
			ptr = strtok(NULL, " \t\n\r");

			if (ptr)
			{
				DecodePTTString(TNC, ptr);
				ptr = strtok(NULL, " \t\n\r");
			}
		}
	}
		
	if (ptr)
	{
		if (_memicmp(ptr, "PATH", 4) == 0)
		{
			p_cmd = strtok(NULL, "\n\r");
			if (p_cmd) TNC->ProgramPath = _strdup(p_cmd);
		}
	}

	// Read Initialisation lines

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

		strcpy(errbuf, buf);

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

		ptr = strchr(buf, ';');
		if (ptr)
		{
			*ptr++ = 13;
			*ptr = 0;
		}

		if (_memicmp(buf, "UPDATEMAP", 9) == 0)
			TNC->PktUpdateMap = TRUE;
		else if (standardParams(TNC, buf) == FALSE)
			strcat(TNC->InitScript, buf);	
	}
	return (TRUE);	
}

char * Config;
static char * ptr1, * ptr2;

int SIXPACKGetLine(char * buf)
{
loop:

	if (ptr2 == NULL)
		return 0;

	memcpy(buf, ptr1, ptr2 - ptr1 + 2);
	buf[ptr2 - ptr1 + 2] = 0;
	ptr1 = ptr2 + 2;
	ptr2 = strchr(ptr1, 13);

	if (buf[0] < 0x20) goto loop;
	if (buf[0] == '#') goto loop;
	if (buf[0] == ';') goto loop;

	if (buf[strlen(buf)-1] < 0x20) buf[strlen(buf)-1] = 0;
	if (buf[strlen(buf)-1] < 0x20) buf[strlen(buf)-1] = 0;
	buf[strlen(buf)] = 13;

	return 1;
}

VOID SuspendOtherPorts(struct TNCINFO * ThisTNC);
VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC);
VOID WritetoTrace(struct TNCINFO * TNC, char * Msg, int Len);

static time_t ltime;

static VOID SendToTNC(struct TNCINFO * TNC, int Stream, UCHAR * Encoded, int EncLen)
{
	if (TNC->hDevice)
	{
		// Serial mode. Queue to Hostmode driver
		
		PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();

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

		buffptr->Len = EncLen;
		memcpy(&buffptr->Data[0], Encoded, EncLen);
		
		C_Q_ADD(&TNC->Streams[Stream].BPQtoPACTOR_Q, buffptr);
		TNC->Streams[Stream].FramesQueued++;

		return;
	}
}


static SOCKADDR_IN sinx; 
static SOCKADDR_IN rxaddr;

static int addrlen=sizeof(sinx);


static size_t ExtProc(int fn, int port, PDATAMESSAGE buff)
{
	int datalen;
	PMSGWITHLEN buffptr;
	char txbuff[500];
	unsigned int bytes,txlen = 0;
	UCHAR * TXMsg;

	size_t Param;
	int Stream = 0;
	HKEY hKey=0;
	struct TNCINFO * TNC = TNCInfo[port];
	struct STREAMINFO * STREAM = &TNC->Streams[0];
	struct ScanEntry * Scan;

	if (TNC == NULL)
		return 0;							// Port not defined

	if (TNC->CONNECTED == 0 && TNC->CONNECTING == 0)
	{
		// Try to reopen every 30 secs

		if (fn > 3  && fn < 7)
			goto ok;

		TNC->ReopenTimer++;

		if (TNC->ReopenTimer < 150)
			return 0;

		TNC->ReopenTimer = 0;

		Connectto6Pack(TNC->Port);
		
		return 0;

		SendInitScript(TNC);

	}
ok:

	switch (fn)
	{
		case 7:			

		// 100 mS Timer. May now be needed, as Poll can be called more frequently in some circumstances

		SerialCheckRX(TNC);
		return 0;

		case 1:				// poll

			STREAM = &TNC->Streams[0];

			if (STREAM->NeedDisc)
			{
				STREAM->NeedDisc--;

				if (STREAM->NeedDisc == 0)
				{
					// Send the DISCONNECT

					SerialSendCommand(TNC, "DISCONNECT\r");
				}
			}

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

				int calllen;
				char Msg[80];

				STREAM->Attached = TRUE;

				calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[Stream]->L4USER, TNC->Streams[Stream].MyCall);
				TNC->Streams[Stream].MyCall[calllen] = 0;


				// Stop other ports in same group

				SuspendOtherPorts(TNC);

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

				// Stop Scanning

				sprintf(Msg, "%d SCANSTOP", TNC->Port);

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

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

			// See if any frames for this port

			STREAM = &TNC->Streams[0];

			if (STREAM->BPQtoPACTOR_Q)
			{
				PMSGWITHLEN buffptr = (PMSGWITHLEN)Q_REM(&STREAM->BPQtoPACTOR_Q);
				UCHAR * data = &buffptr->Data[0];
				STREAM->FramesQueued--;
				txlen = (int)buffptr->Len;
				STREAM->bytesTXed += txlen;

				bytes=SerialSendData(TNC, data, txlen);
				WritetoTrace(TNC, data, txlen);
			}

			if (STREAM->PACTORtoBPQ_Q != 0)
			{
				buffptr = (PMSGWITHLEN)Q_REM(&STREAM->PACTORtoBPQ_Q);

				datalen = (int)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, datalen);

				ReleaseBuffer(buffptr);

				return (1);
			}

			if (STREAM->ReportDISC)		// May need a delay so treat as a counter
			{
				STREAM->ReportDISC--;

				if (STREAM->ReportDISC == 0)
				{
					buff->PORT = Stream;
					return -1;
				}
			}

			return (0);

	case 2:				// send

		Stream = 0;

		if (!TNC->CONNECTED)
			return 0;		// Don't try if not connected

		STREAM = &TNC->Streams[0];
		
		if (TNC->SwallowSignon)
		{
			TNC->SwallowSignon = FALSE;		// Discard *** connected
			return 0;
		}

		// We may get KISS packets (UI or session related) or text commands such as RADIO, CONNECT

		txlen = GetLengthfromBuffer(buff) - (MSGHDDRLEN);

		TXMsg = &buff->L2DATA[0];

		if (buff->PID != 240)			// ax.25 address
		{
			txlen = KissEncode(&buff->PID, txbuff, txlen);
			txlen = send(TNC->TCPSock, txbuff, txlen, 0);
			return 1;
		}

		TXMsg[txlen - 1] = 0;
		strcpy(txbuff, TXMsg);

		if (STREAM->Attached == 0)
			return 0;

		if (STREAM->Connected)
		{
			PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();

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

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

			// connected data

			return 1;
		}

		// Process as Text Command

		if (_memicmp(txbuff, "D\r", 2) == 0 || _memicmp(txbuff, "BYE\r", 4) == 0)
		{
			STREAM->ReportDISC = TRUE;		// Tell Node
			return 0;
		}
	
		if (_memicmp(txbuff, "RADIO ", 6) == 0)
		{
			sprintf(&buff->L2DATA[0], "%d %s", TNC->Port, &txbuff[6]);

			if (Rig_Command(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK, &buff->L2DATA[0]))
			{
			}
			else
			{
				PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();

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

				buffptr->Len  = sprintf((UCHAR *)&buffptr->Data[0], "%s", &buff->L2DATA[0]);
				C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
			}
			return 1;
		}


		if (_memicmp(txbuff, "OVERRIDEBUSY", 12) == 0)
		{
			PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();

			TNC->OverrideBusy = TRUE;

			if (buffptr)
			{
				buffptr->Len = sprintf((UCHAR *)&buffptr->Data[0], "SIXPACK} OK\r");
				C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
			}

			return 0;

		}

		if (_memicmp(&buff->L2DATA[0], "SessionTimeLimit", 16) == 0)
		{
			if (buff->L2DATA[16] != 13)
			{					
				PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();

				TNC->SessionTimeLimit = atoi(&buff->L2DATA[16]) * 60;

				if (buffptr)
				{
					buffptr->Len = sprintf((UCHAR *)&buffptr->Data[0], "SIXPACK} OK\r");
					C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
				}
				return 0;
			}
		}

		if (toupper(buff->L2DATA[0]) == 'C' && buff->L2DATA[1] == ' ' && txlen > 2)	// Connect
		{
			// Connect Command. Pass to L2 code to start session

			char * ptr = strchr(&buff->L2DATA[2], 13);
			TRANSPORTENTRY * NewSess = L4TABLE;
			struct _LINKTABLE * LINK;
			TRANSPORTENTRY * Session = TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK;
			struct PORTCONTROL * PORT = &TNC->PortRecord->PORTCONTROL;

			UCHAR axcalls[64];
			UCHAR ourcall[7];					// Call we are using (may have SSID bits inverted
			int Stay = 0, Spy = 0, CQFLAG = 0, n;

			if (ptr)
				*ptr = 0;

			_strupr(&buff->L2DATA[2]);

			if (DecodeCallString(&buff->L2DATA[2], &Stay, &Spy, &axcalls[0]) == 0)
			{
				PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();
				if (buffptr)
				{
					buffptr->Len = sprintf((UCHAR *)&buffptr->Data[0], "SIXPACK} Invalid Call\r");
					C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
				}
				return 0;
			}

			// Code copied from cmdc00

			// Get Session Entry for Downlink

			NewSess = SetupNewSession(Session, NULL);
	
			if (NewSess == NULL)
				return 0;

			NewSess->L4CIRCUITTYPE = L2LINK + DOWNLINK;

			//	FORMAT LINK TABLE ENTRY FOR THIS CONNECTION

			memcpy(Session->L4USER, NewSess->L4USER, 7);
			memcpy(ourcall, NewSess->L4USER, 7);

			// SSID SWAP TEST - LEAVE ALONE FOR HOST or Pactor like (unless UZ7HO)

			if ((Session->L4CIRCUITTYPE & BPQHOST))// host
				goto noFlip3;

			if ((Session->L4CIRCUITTYPE & PACTOR))
			{
				// incoming is Pactorlike - see if UZ7HO

				if (memcmp(Session->L4TARGET.EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0)
					goto noFlip3;

				if (Session->L4TARGET.EXTPORT->MAXHOSTMODESESSIONS < 2)	// Not multisession	
					goto noFlip3;

				ourcall[6] ^= 0x1e;		// UZ7HO Uplink - flip
			}
			else

				// Must be L2 uplink - flip

				ourcall[6] ^= 0x1e;									// Flip SSID

noFlip3:

			//	SET UP NEW SESSION (OR RESET EXISTING ONE)

			FindLink(axcalls, ourcall, TNC->Port, &LINK);

			if (LINK == NULL)
			{			
				PMSGWITHLEN buffptr = (PMSGWITHLEN)GetBuff();
				if (buffptr)
				{
					buffptr->Len = sprintf((UCHAR *)&buffptr->Data[0], "SIXPACK} Sorry - System Tables Full\r");
					C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
				}
				return 0;
			}

			memcpy(LINK->LINKCALL, axcalls, 7);
			memcpy(LINK->OURCALL, ourcall, 7);

			LINK->LINKPORT = PORT;

			LINK->L2TIME = PORT->PORTT1;

			// Copy Digis

			n = 7;
			ptr = &LINK->DIGIS[0];

			while (axcalls[n])
			{
				memcpy(ptr, &axcalls[n], 7);
				n += 7;
				ptr += 7;

				LINK->L2TIME += 2 * PORT->PORTT1;	// ADJUST TIMER VALUE FOR 1 DIGI
			}

			LINK->LINKTYPE = 2;						// DOWNLINK
			LINK->LINKWINDOW = PORT->PORTWINDOW;

			RESET2(LINK);						// RESET ALL FLAGS

//			if (CMD->String[0] == 'N' && SUPPORT2point2)
//				LINK->L2STATE = 1;					// New (2.2) send XID
//			else
				LINK->L2STATE = 2;					// Send SABM

			LINK->CIRCUITPOINTER = NewSess;

			NewSess->L4TARGET.LINK = LINK;

			if (PORT->PORTPACLEN)
				NewSess->SESSPACLEN = Session->SESSPACLEN = PORT->PORTPACLEN;

			STREAM->Connecting = TRUE;

			if (CQFLAG == 0)			// if a CQ CALL  DONT SEND SABM
			{
				if (LINK->L2STATE == 1)
					L2SENDXID(LINK);
				else	
					SENDSABM(LINK);
			}
			return 0;
		}	

		return 0;

	case 3:	
		
		// CHECK IF OK TO SEND (And check TNC Status)

		return ((TNC->CONNECTED) << 8 | TNC->Streams[0].Disconnecting << 15);		// OK
		
	case 4:				// reinit7

		return 0;

	case 5:				// Close

		return 0;

	case 6:				// Scan Stop Interface

		Param = (size_t)buff;
	
		if (Param == 2)		// Check  Permission (Shouldn't happen)
		{
			Debugprintf("Scan Check Permission called on SIXPACK");
			return 1;		// OK to change
		}

		if (Param == 1)		// Request Permission
		{
			if (!TNC->CONNECTED)
				return 0;					// No connection so no interlock

			if (TNC->ConnectPending == 0 && TNC->PTTState == 0)
			{
				SerialSendCommand(TNC, "CONOK OFF");
				TNC->GavePermission = TRUE;
				return 0;	// OK to Change
			}

			if (TNC->ConnectPending)
				TNC->ConnectPending--;		// Time out if set too long

			return TRUE;
		}

		if (Param == 3)		// Release  Permission
		{
			if (TNC->GavePermission)
			{
				TNC->GavePermission = FALSE;
				if (TNC->ARDOPCurrentMode[0] != 'S')	// Skip
					SerialSendCommand(TNC, "CONOK ON");
			}
			return 0;
		}

		// Param is Address of a struct ScanEntry

		Scan = (struct ScanEntry *)buff;
		return 0;
	}
	return 0;
}

VOID SIXPACKReleaseTNC(struct TNCINFO * TNC)
{
	// Set mycall back to Node or Port Call, and Start Scanner

	UCHAR TXMsg[64];

	//	Start Scanner
				
	sprintf(TXMsg, "%d SCANSTART 15", TNC->Port);
	Rig_Command( (TRANSPORTENTRY *) -1, TXMsg);

	strcpy(TNC->WEB_TNCSTATE, "Free");
	MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);

	ReleaseOtherPorts(TNC);
}

VOID SIXPACKSuspendPort(struct TNCINFO * TNC, struct TNCINFO * ThisTNC)
{
}

VOID SIXPACKReleasePort(struct TNCINFO * TNC)
{
}

static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL)
{
	int Len = sprintf(Buff, "<html><meta http-equiv=expires content=0><meta http-equiv=refresh content=15>"
		"<script type=\"text/javascript\">\r\n"
		"function ScrollOutput()\r\n"
		"{var textarea = document.getElementById('textarea');"
		"textarea.scrollTop = textarea.scrollHeight;}</script>"
		"</head><title>VARA Status</title></head><body id=Text onload=\"ScrollOutput()\">"
		"<h2><form method=post target=\"POPUPW\" onsubmit=\"POPUPW = window.open('about:blank','POPUPW',"
		"'width=440,height=150');\" action=ARDOPAbort?%d>SIXPACK Status"
		"<input name=Save value=\"Abort Session\" type=submit style=\"position: absolute; right: 20;\"></form></h2>",
		TNC->Port);


	Len += sprintf(&Buff[Len], "<table style=\"text-align: left; width: 500px; font-family: monospace; align=center \" border=1 cellpadding=2 cellspacing=2>");

	Len += sprintf(&Buff[Len], "<tr><td width=110px>Comms State</td><td>%s</td></tr>", TNC->WEB_COMMSSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>TNC State</td><td>%s</td></tr>", TNC->WEB_TNCSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Mode</td><td>%s</td></tr>", TNC->WEB_MODE);
	Len += sprintf(&Buff[Len], "<tr><td>Channel State</td><td>%s</td></tr>", TNC->WEB_CHANSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Proto State</td><td>%s</td></tr>", TNC->WEB_PROTOSTATE);
	Len += sprintf(&Buff[Len], "<tr><td>Traffic</td><td>%s</td></tr>", TNC->WEB_TRAFFIC);
//	Len += sprintf(&Buff[Len], "<tr><td>TNC Restarts</td><td></td></tr>", TNC->WEB_RESTARTS);
	Len += sprintf(&Buff[Len], "</table>");

	Len += sprintf(&Buff[Len], "<textarea rows=10 style=\"width:500px; height:250px;\" id=textarea >%s</textarea>", TNC->WebBuffer);
	Len = DoScanLine(TNC, Buff, Len);

	return Len;
}



VOID * SIXPACKExtInit(EXTPORTDATA * PortEntry)
{
	int port;
	char Msg[255];
	char * ptr;
	struct TNCINFO * TNC;
	char * TempScript;
	struct PORTCONTROL * PORT = &PortEntry->PORTCONTROL;
	
	port = PORT->PORTNUMBER;

	if (TNCInfo[port])					// If restarting, free old config
		free(TNCInfo[port]);

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

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

	if (PortConfig[port])			// May not have config
		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->sixPack = zalloc(sizeof(struct sixPackTNCInfo));

	TNC->Port = port;
	TNC->PortRecord = PortEntry;
	TNC->PortRecord->PORTCONTROL.HWType = TNC->Hardware = H_SIXPACK;

	TNC->ARDOPBuffer = malloc(8192);


	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->PORTCONTROL.USERS = 1;			// Max 1 Session

	TNC->PacketChannels = 0;

	PortEntry->MAXHOSTMODESESSIONS = 1;

	PortEntry->SCANCAPABILITIES = SIMPLE;			// Scan Control - pending connect only
	PortEntry->PERMITGATEWAY = TRUE;				// Can change ax.25 call on each stream

	PortEntry->PORTCONTROL.UICAPABLE = TRUE;

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

	TNC->SuspendPortProc = SIXPACKSuspendPort;
	TNC->ReleasePortProc = SIXPACKReleasePort;

//	PortEntry->PORTCONTROL.PORTSTARTCODE = KISSStartPort;
//	PortEntry->PORTCONTROL.PORTSTOPCODE = KISSStopPort;

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

	// Set Essential Params and MYCALL

	// Put overridable ones on front, essential ones on end

	TempScript = zalloc(1000);

	// cant think of any yet

	if (TNC->InitScript)
	{
		strcat(TempScript, TNC->InitScript);
		free(TNC->InitScript);
	}

	TNC->InitScript = TempScript;

	if (TNC->WL2K == NULL)
		if (PortEntry->PORTCONTROL.WL2KInfo.RMSCall[0])			// Alrerady decoded
			TNC->WL2K = &PortEntry->PORTCONTROL.WL2KInfo;

	PortEntry->PORTCONTROL.TNC = TNC;

	TNC->WebWindowProc = WebProc;
	TNC->WebWinX = 520;
	TNC->WebWinY = 500;
	TNC->WebBuffer = zalloc(5000);

	TNC->WEB_COMMSSTATE = zalloc(100);
	TNC->WEB_TNCSTATE = zalloc(100);
	TNC->WEB_CHANSTATE = zalloc(100);
	TNC->WEB_BUFFERS = zalloc(100);
	TNC->WEB_PROTOSTATE = zalloc(100);
	TNC->WEB_RESTARTTIME = zalloc(100);
	TNC->WEB_RESTARTS = zalloc(100);

	TNC->WEB_MODE = zalloc(20);
	TNC->WEB_TRAFFIC = zalloc(100);


#ifndef LINBPQ

	CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 700, 450, 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", "Channel State", WS_CHILD | WS_VISIBLE, 10,72,110,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_CHANSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,72,144,20, TNC->hDlg, NULL, hInstance, NULL);
 
 	CreateWindowEx(0, "STATIC", "Proto State", WS_CHILD | WS_VISIBLE,10,94,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_PROTOSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,94,374,20 , TNC->hDlg, NULL, hInstance, NULL);
 
	CreateWindowEx(0, "STATIC", "Traffic", WS_CHILD | WS_VISIBLE,10,116,80,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_TRAFFIC = CreateWindowEx(0, "STATIC", "0 0 0 0", WS_CHILD | WS_VISIBLE,116,116,374,20 , TNC->hDlg, NULL, hInstance, NULL);

	CreateWindowEx(0, "STATIC", "TNC Restarts", WS_CHILD | WS_VISIBLE,10,138,100,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_RESTARTS = CreateWindowEx(0, "STATIC", "0", WS_CHILD | WS_VISIBLE,116,138,40,20 , TNC->hDlg, NULL, hInstance, NULL);
	CreateWindowEx(0, "STATIC", "Last Restart", WS_CHILD | WS_VISIBLE,140,138,100,20, TNC->hDlg, NULL, hInstance, NULL);
	TNC->xIDC_RESTARTTIME = CreateWindowEx(0, "STATIC", "Never", WS_CHILD | WS_VISIBLE,250,138,200,20, TNC->hDlg, NULL, hInstance, NULL);

	TNC->hMonitor= CreateWindowEx(0, "LISTBOX", "", WS_CHILD |  WS_VISIBLE  | LBS_NOINTEGRALHEIGHT | 
            LBS_DISABLENOSCROLL | WS_HSCROLL | WS_VSCROLL,
			0,170,250,300, TNC->hDlg, NULL, hInstance, NULL);

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

	TNC->hMenu = CreatePopupMenu();

	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_KILL, "Kill TNC");
	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_RESTART, "Kill and Restart TNC");
	AppendMenu(TNC->hMenu, MF_STRING, WINMOR_RESTARTAFTERFAILURE, "Restart TNC after failed Connection");	
	CheckMenuItem(TNC->hMenu, WINMOR_RESTARTAFTERFAILURE, (TNC->RestartAfterFailure) ? MF_CHECKED : MF_UNCHECKED);
	AppendMenu(TNC->hMenu, MF_STRING, ARDOP_ABORT, "Abort Current Session");

	MoveWindows(TNC);
#endif
	Consoleprintf("SIXPACK Host %s %d", TNC->HostName, htons(TNC->destaddr.sin_port));

	Connectto6Pack(port);

	time(&TNC->lasttime);			// Get initial time value

	return ExtProc;
}


VOID TidyClose(struct TNCINFO * TNC, int Stream)
{
	// If all acked, send disc
	
	if (TNC->Streams[Stream].BytesOutstanding == 0)
		SerialSendCommand(TNC, "DISCONNECT\r");
}

VOID ForcedClose(struct TNCINFO * TNC, int Stream)
{
	SerialSendCommand(TNC, "DISCONNECT\r");
}

VOID CloseComplete(struct TNCINFO * TNC, int Stream)
{
	if (Stream == 0)
	{
		SIXPACKReleaseTNC(TNC);
	}
}


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

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

		if (c == FESC)
		{
			c=inbuff[++i];
			{
				if (c == TFESC)
					c=FESC;
				else
				if (c == TFEND)
					c=FEND;
			}
		}

		outbuff[txptr++]=c;
	}

	return txptr;

}


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

	outbuff[0]=FEND;
	outbuff[1]=0;
	txptr=2;

	for (i=0;i<len;i++)
	{
		c=inbuff[i];
		
		switch (c)
		{
		case FEND:
			outbuff[txptr++]=FESC;
			outbuff[txptr++]=TFEND;
			break;

		case FESC:

			outbuff[txptr++]=FESC;
			outbuff[txptr++]=TFESC;
			break;

		default:

			outbuff[txptr++]=c;
		}
	}

	outbuff[txptr++]=FEND;

	return txptr;

}


VOID SIXPACKProcessReceivedPacket(struct TNCINFO * TNC)
{
	int InputLen, MsgLen;
	unsigned char * ptr;
	char Buffer[4096];

	if (TNC->InputLen > 8000)	// Shouldnt have packets longer than this
		TNC->InputLen=0;

	InputLen = recv(TNC->TCPSock, &TNC->ARDOPBuffer[TNC->InputLen], 8192 - TNC->InputLen, 0);

	if (InputLen == 0 || InputLen == SOCKET_ERROR)
	{
		// Does this mean closed?

		int err = GetLastError();

		closesocket(TNC->TCPSock);

		TNC->TCPSock = 0;

		TNC->CONNECTED = FALSE;
		TNC->Streams[0].ReportDISC = TRUE;

		sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
		MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);

		return;					
	}

	TNC->InputLen += InputLen;

	// Extract and decode KISS frames

	ptr = memchr(TNC->ARDOPBuffer + 1, FEND, TNC->InputLen - 1);		// Ignore leading FEND

	while (ptr)	//  FEND in buffer
	{
		ptr++;

		MsgLen = ptr - TNC->ARDOPBuffer;

		if (MsgLen > 360)
		{
			TNC->InputLen = 0;
			return;
		}

		TNC->InputLen -= MsgLen;

		if (MsgLen > 1)
		{
			PMESSAGE Buff = GetBuff();

			MsgLen = KissDecode(TNC->ARDOPBuffer, Buffer, MsgLen);

			// we dont need the FENDS or control byte

			MsgLen -= 3;
				
			if (Buff)
			{
				memcpy(&Buff->DEST, &Buffer[2], MsgLen);
				MsgLen += (3 + sizeof(void *));

				PutLengthinBuffer((PDATAMESSAGE)Buff, MsgLen);		// Needed for arm5 portability

				C_Q_ADD(&TNC->PortRecord->PORTCONTROL.PORTRX_Q, (UINT *)Buff);
			}
		}

		if (TNC->InputLen == 0)
			return;

		memmove(TNC->ARDOPBuffer, ptr, TNC->InputLen);
		ptr = memchr(TNC->ARDOPBuffer + 1, FEND, TNC->InputLen - 1);		// Ignore leading FEND
	}
		
}



/*

// we dont need the control byte

len --;

if (Buffer)
{
memcpy(&Buffer->DEST, &Port->RXMSG[1], len);
len += (3 + sizeof(void *));

PutLengthinBuffer((PDATAMESSAGE)Buffer, len);		// Needed for arm5 portability

C_Q_ADD(TNC->PortRecord->PORTCONTROL.PORTRX_Q, (UINT *)Buffer);
}

*/

//	TNC->InputLen -= MsgLen;
//	goto loop;




void AttachSIXPACK(struct PORTCONTROL * PORT, MESSAGE * Buffer)
{
	// SABM on HFKISS port. L2 code will accepr call and connect to appl if necessary, but
	// need to attach the port

	char Call[16] = "";
	char OrigCall[16] = "";
	struct TNCINFO * TNC = PORT->TNC;
	struct WL2KInfo * WL2K = TNC->WL2K;

	if (TNC->PortRecord->ATTACHEDSESSIONS[0] == 0)
	{
		TRANSPORTENTRY * SESS;
		struct TNCINFO * TNC = PORT->TNC;

		// Incoming Connect

		Call[ConvFromAX25(Buffer->DEST, Call)] = 0;
		OrigCall[ConvFromAX25(Buffer->ORIGIN, OrigCall)] = 0;

		// Stop other ports in same group

		SuspendOtherPorts(TNC);

		TNC->SessionTimeLimit = TNC->DefaultSessionTimeLimit;		// Reset Limit

		ProcessIncommingConnectEx(TNC, Call, 0, FALSE, FALSE);

		SESS = TNC->PortRecord->ATTACHEDSESSIONS[0];

		SESS->Mode = TNC->WL2KMode;

		TNC->ConnectPending = FALSE;

		if (TNC->RIG && TNC->RIG != &TNC->DummyRig && strcmp(TNC->RIG->RigName, "PTT"))
		{
			sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound Freq %s", OrigCall, Call, TNC->RIG->Valchar);
			SESS->Frequency = (int)(atof(TNC->RIG->Valchar) * 1000000.0) + 1500;		// Convert to Centre Freq
			if (SESS->Frequency == 1500)
			{
				// try to get from WL2K record

				if (WL2K)
				{
					SESS->Frequency = WL2K->Freq;
				}
			}
			SESS->Mode = TNC->WL2KMode;
		}
		else
		{
			sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Inbound", OrigCall, Call);
			if (WL2K)
			{
				SESS->Frequency = WL2K->Freq;
				SESS->Mode = WL2K->mode;
			}
		}

		if (WL2K)
			strcpy(SESS->RMSCall, WL2K->RMSCall);

		MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);
	}
}

void DetachSIXPACK(struct PORTCONTROL * PORT)
{
	// L2 Link Closed. Detach.

	struct TNCINFO * TNC = PORT->TNC;
	struct STREAMINFO * STREAM = &TNC->Streams[0];

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

	STREAM->Connecting = FALSE;
	STREAM->Connected = FALSE;

}

void SIXPACKConnected(struct PORTCONTROL * PORT, struct _LINKTABLE * LINK)
{
	// UA received when connecting

	struct TNCINFO * TNC = PORT->TNC;
	struct STREAMINFO * STREAM = &TNC->Streams[0];
	struct WL2KInfo * WL2K = TNC->WL2K;

	TRANSPORTENTRY * SESS;
	char Call[16] = "";
	char OrigCall[16] = "";


	if (STREAM->Connecting)
	{
		STREAM->Connecting = FALSE;
		STREAM->Connected = TRUE;

		Call[ConvFromAX25(LINK->LINKCALL, Call)] = 0;
		OrigCall[ConvFromAX25(LINK->OURCALL, OrigCall)] = 0;

		TNC->SessionTimeLimit = TNC->DefaultSessionTimeLimit;		// Reset Limit

		SESS = TNC->PortRecord->ATTACHEDSESSIONS[0];
		SESS->Mode = TNC->WL2KMode;

		if (TNC->RIG && TNC->RIG != &TNC->DummyRig && strcmp(TNC->RIG->RigName, "PTT"))
		{
			sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound Freq %s", OrigCall, Call, TNC->RIG->Valchar);
			SESS->Frequency = (int)(atof(TNC->RIG->Valchar) * 1000000.0) + 1500;		// Convert to Centre Freq
			
			if (SESS->Frequency == 1500)
			{
				// try to get from WL2K record

				if (WL2K)
				{
					SESS->Frequency = WL2K->Freq;
				}
			}
			SESS->Mode = TNC->WL2KMode;
		}
		else
		{
			sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound", OrigCall, Call);

			if (WL2K)
			{
				SESS->Frequency = WL2K->Freq;
				SESS->Mode = WL2K->mode;
			}
		}

		if (WL2K)
			strcpy(SESS->RMSCall, WL2K->RMSCall);

		MySetWindowText(TNC->xIDC_TNCSTATE, TNC->WEB_TNCSTATE);
	}
}



VOID x6PackThead(void * portptr);

int Connectto6Pack(int port)
{
	_beginthread(x6PackThead, 0, (void *)(size_t)port);

	return 0;
}

VOID x6PackThead(void * portptr)
{
	// 6pack over TNC thread

	// Opens socket and looks for data 
	
	int port = (int)(size_t)portptr;
	char Msg[255];
	int err, i, ret;
	u_long param=1;
	BOOL bcopt=TRUE;
	struct hostent * HostEnt;
	struct TNCINFO * TNC = TNCInfo[port];
	fd_set readfs;
	fd_set errorfs;
	struct timeval timeout;
	char * ptr1;
	char * ptr2;
	UINT * buffptr;
	int reinitTimer = 0;
	int linkactive = 0;

	struct sixPackPortInfo * sixPack = TNC->sixPack;

	if (sixPack == NULL || TNC->HostName == NULL)
		return;

	TNC->BusyFlags = 0;

	TNC->CONNECTING = TRUE;

	Sleep(3000);		// Allow init to complete 

#ifdef WIN32
	if (strcmp(TNC->HostName, "127.0.0.1") == 0)
	{
		// can only check if running on local host
		
		TNC->PID = GetListeningPortsPID(TNC->destaddr.sin_port);
		if (TNC->PID == 0)
		{
			TNC->CONNECTING = FALSE;
			return;						// Not listening so no point trying to connect
		}

		// Get the File Name in case we want to restart it.

		if (TNC->ProgramPath == NULL)
		{
			if (GetModuleFileNameExPtr)
			{
				HANDLE hProc;
				char ExeName[256] = "";

				hProc =  OpenProcess(PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, TNC->PID);
	
				if (hProc)
				{
					GetModuleFileNameExPtr(hProc, 0,  ExeName, 255);
					CloseHandle(hProc);

					TNC->ProgramPath = _strdup(ExeName);
				}
			}
		}
	}
#endif

//	// If we started the TNC make sure it is still running.

//	if (!IsProcess(TNC->PID))
//	{
//		RestartTNC(TNC);
//		Sleep(3000);
//	}


	TNC->destaddr.sin_addr.s_addr = inet_addr(TNC->HostName);

	if (TNC->destaddr.sin_addr.s_addr == INADDR_NONE)
	{
		//	Resolve name to address

		HostEnt = gethostbyname (TNC->HostName);
		 
		 if (!HostEnt)
		 {
		 	TNC->CONNECTING = FALSE;
			return;			// Resolve failed
		 }
		 memcpy(&TNC->destaddr.sin_addr.s_addr,HostEnt->h_addr,4);
		 memcpy(&TNC->Datadestaddr.sin_addr.s_addr,HostEnt->h_addr,4);
	}

//	closesocket(TNC->TCPSock);

	TNC->TCPSock=socket(AF_INET,SOCK_STREAM,0);

	if (TNC->TCPSock == INVALID_SOCKET)
	{
		i=sprintf(Msg, "Socket Failed for 6Pack socket - error code = %d\r\n", WSAGetLastError());
		WritetoConsole(Msg);

	 	TNC->CONNECTING = FALSE;
  	 	return; 
	}

	setsockopt(TNC->TCPSock, SOL_SOCKET, SO_REUSEADDR, (const char FAR *)&bcopt, 4);

	sinx.sin_family = AF_INET;
	sinx.sin_addr.s_addr = INADDR_ANY;
	sinx.sin_port = 0;

	if (connect(TNC->TCPSock,(LPSOCKADDR) &TNC->destaddr,sizeof(TNC->destaddr)) == 0)
	{
		//
		//	Connected successful
		//
	}
	else
	{
		if (TNC->Alerted == FALSE)
		{
			err=WSAGetLastError();
   			i=sprintf(Msg, "Connect Failed for 6Pack socket - error code = %d\r\n", err);
			WritetoConsole(Msg);
			sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC failed");
			MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);

			TNC->Alerted = TRUE;
		}
		
		closesocket(TNC->TCPSock);
		TNC->TCPSock = 0;
	 	TNC->CONNECTING = FALSE;
		return;
	}

	Sleep(1000);

	TNC->LastFreq = 0;			//	so V4 display will be updated

 	TNC->CONNECTING = FALSE;
	TNC->CONNECTED = TRUE;
	TNC->BusyFlags = 0;
	TNC->InputLen = 0;

	TNC->Alerted = TRUE;

	sprintf(TNC->WEB_COMMSSTATE, "Connected to TNC");		
	MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);

	FreeSemaphore(&Semaphore);

	sprintf(Msg, "Connected to TNC Port %d\r\n", TNC->Port);
	WritetoConsole(Msg);

	while (TNC->CONNECTED)
	{
		FD_ZERO(&readfs);	
		FD_ZERO(&errorfs);

		FD_SET(TNC->TCPSock,&readfs);
		FD_SET(TNC->TCPSock,&errorfs);

		timeout.tv_sec = 1;	
		timeout.tv_usec = 0;				// We use this to run timeouts

		ret = select((int)TNC->TCPSock + 1, &readfs, NULL, &errorfs, &timeout);
		
		if (ret == SOCKET_ERROR)
		{
			Debugprintf("6Pack Select failed %d ", WSAGetLastError());
			goto Lost;
		}
		if (ret > 0)
		{
			//	See what happened

			if (FD_ISSET(TNC->TCPSock, &readfs))
			{
				GetSemaphore(&Semaphore, 52);
				SixPackProcessReceivedPacket(TNC);
				FreeSemaphore(&Semaphore);
			}
								
			if (FD_ISSET(TNC->TCPSock, &errorfs))
			{
Lost:	
				sprintf(Msg, "6Pack Connection lost for Port %d\r\n", TNC->Port);
				WritetoConsole(Msg);

				sprintf(TNC->WEB_COMMSSTATE, "Connection to TNC lost");
				MySetWindowText(TNC->xIDC_COMMSSTATE, TNC->WEB_COMMSSTATE);

				TNC->CONNECTED = FALSE;
				TNC->Alerted = FALSE;

				if (TNC->PTTMode)
					Rig_PTT(TNC, FALSE);			// Make sure PTT is down

				if (TNC->Streams[0].Attached)
					TNC->Streams[0].ReportDISC = TRUE;

				TNC->TCPSock = 0;
				return;
			}
		}
		else
		{
			// Timeout. See if we need to reinit tnc chain

			if (sixPack->linkOK == 0)
			{
				sixPack->reinitTimer++;
				if (sixPack->reinitTimer > 30)
				{
					// send SIXP_INIT_CMD which should rescan the tnc chain

					unsigned char Msg = SIXP_INIT_CMD;
					int txlen = send(TNC->TCPSock, &Msg, 1, 0);
					sixPack->reinitTimer = 0;
				}
			}
		}

	}
	sprintf(Msg, "6Pack Thread Terminated Port %d\r\n", TNC->Port);
	WritetoConsole(Msg);
}






