//
//	DLL to inteface the BPQ Virtual COM emulator to BPQ32 switch 
//
//	Uses BPQ EXTERNAL interface
//

//	Version 1.0 November 2005
//

//  Version 1.1	October 2006

//		Write diagmnostics to BPQ console window instead of STDOUT

// Version 1.2 February 2008

//		Changes for dynamic unload of bpq32.dll

// Version 1.2.1 May 2008

//		Correct RX length (was 1 byte too long)

// Version 1.3.1 Jan 2009

//		Support Win98 VirtualCOM Driver

#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_DEPRECATE 

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

#define VERSION_MAJOR         1
#define VERSION_MINOR         0

typedef unsigned char byte;

#include "CHeaders.h"
#include "bpqvkiss.h"

#include <stdlib.h>

//#define DYNLOADBPQ		// Dynamically Load BPQ32.dll
//#define EXTDLL			// Use GetMuduleHandle instead of LoadLibrary 
#include "bpq32.h"
 

static int	ASYINIT(int comport, int speed, int bpqport, BOOL Report);
int	kissencode(UCHAR * inbuff, UCHAR * outbuff, int len);
int GetRXMessage(int port, PMESSAGE buff);
void CheckReceivedData(PVCOMINFO  pVCOMInfo);
static int ReadCommBlock(PVCOMINFO  pVCOMInfo, LPSTR lpszBlock, DWORD nMaxLength );
static BOOL WriteCommBlock(int port, UCHAR * lpByte , DWORD dwBytesToWrite);

PVCOMINFO CreateInfo( int port,int speed, int bpqport )	;

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

static BOOL Win98 = FALSE;

struct PORTCONTROL * PORTVEC[33];

static size_t ExtProc(int fn, int port, PMESSAGE buff)
{
	int len,txlen=0;
	char txbuff[1000];

	if (VCOMInfo[port]->ComDev == (HANDLE) -1)
	{
		// Try to reopen every 30 secs

		VCOMInfo[port]->ReopenTimer++;

		if (VCOMInfo[port]->ReopenTimer < 300)
			return 0;

		VCOMInfo[port]->ReopenTimer = 0;
		
		ASYINIT(PORTVEC[port]->IOBASE, 9600, port, FALSE);

		if (VCOMInfo[port]->ComDev == (HANDLE) -1)
			return 0;
	}


	switch (fn)
	{
	case 1:				// poll

		len = GetRXMessage(port,buff);
	
//		if (len > 0)
//		{
//			// Randomly drop packets

//			if ((rand() % 7) > 5)
//			{
//				Debugprintf("VKISS Test Drop packet");
//				return 0;
//			}
//		}
		
		return len;

	case 2:				// send

		txlen = GetLengthfromBuffer((PDATAMESSAGE)buff); 

		txlen = kissencode(&buff->DEST[0], (char *) &txbuff, txlen - MSGHDDRLEN);

		WriteCommBlock(port, txbuff, txlen);
		
		return (0);


	case 3:				// CHECK IF OK TO SEND

		return (0);		// OK
			
		break;

	case 4:				// reinit
		
		CloseHandle(VCOMInfo[port]->ComDev);
		VCOMInfo[port]->ComDev =(HANDLE) -1;
		VCOMInfo[port]->ReopenTimer = 250;

		return (0);

	case 5:				// Close

		CloseHandle(VCOMInfo[port]->ComDev);

		return (0);

	}

	return 0;

}

VOID * VCOMExtInit(struct PORTCONTROL *  PortEntry)
{
	char msg[80];
	
	//
	//	Will be called once for each port to be mapped to a BPQ Virtual COM Port
	//	The VCOM port number is in IOBASE
	//

	sprintf(msg,"VKISS COM%d", PortEntry->IOBASE);
	WritetoConsole(msg);

	PORTVEC[PortEntry->PORTNUMBER] = PortEntry;
	
	CreateInfo(PortEntry->IOBASE, 9600, PortEntry->PORTNUMBER);

	// Open File
	
	ASYINIT(PortEntry->IOBASE, 9600, PortEntry->PORTNUMBER, TRUE);

	WritetoConsole("\n");

	return ExtProc;
}

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;

}

int	ASYINIT(int comport, int speed, int bpqport, BOOL Report)
{
   char szPort[ 30 ];
   char buf[256];
   int n, Err;

#pragma warning( push )
#pragma warning( disable : 4996 )

#ifndef _winver

#define _winver 0x0600

#endif


   if (HIBYTE(_winver) < 5)
		Win98 = TRUE;

#pragma warning( pop ) 

   if (Win98)
	{
		VCOMInfo[bpqport]->ComDev = CreateFile( "\\\\.\\BPQVCOMM.VXD", GENERIC_READ | GENERIC_WRITE,
                  0,                    // exclusive access
	               NULL,                 // no security attrs
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL, 
                  NULL );
	}
   else{
		sprintf( szPort, "\\\\.\\pipe\\BPQCOM%d", comport ) ;

		VCOMInfo[bpqport]->ComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE,
                  0,                    // exclusive access
                  NULL,                 // no security attrs
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL, 
                  NULL );

			//Handle = CreateFile(Value, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);


		Err = GetLastError();

		if (VCOMInfo[bpqport]->ComDev != (HANDLE) -1)
		{
			VCOMInfo[bpqport]->NewVCOM = TRUE;
			Err = GetFileType(VCOMInfo[bpqport]->ComDev);
		}
		else
		{
			// Try old style 	

			sprintf( szPort, "\\\\.\\BPQ%d", comport ) ;

			VCOMInfo[bpqport]->ComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE,
                  0,                    // exclusive access
                  NULL,                 // no security attrs
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL, 
                  NULL );
		}
   }  
	if (VCOMInfo[bpqport]->ComDev == (HANDLE) -1 && Report)
	{
		n=sprintf(buf,"Virtual COM Port %d could not be opened ",comport);
		WritetoConsole(buf);

		return (FALSE) ;
	}

	return (TRUE) ;
}

static int GetRXMessage(int port, PMESSAGE buff)
{
	int len;
	PVCOMINFO pVCOMInfo ;

	if (NULL == (pVCOMInfo = VCOMInfo[port]))
		return 0;

	if (!pVCOMInfo->MSGREADY)
		CheckReceivedData(pVCOMInfo);		// Look for data in RXBUFFER and COM port

	if (pVCOMInfo->MSGREADY)
	{
		len = (int)(pVCOMInfo->RXMPTR- &pVCOMInfo->RXMSG[1]);		// Don't need KISS Control Byte
		
  		if (pVCOMInfo->RXMSG[0] != 0 && pVCOMInfo->RXMSG[0] != 12)
		{
			pVCOMInfo->MSGREADY=FALSE;
			pVCOMInfo->RXMPTR=(UCHAR *)&pVCOMInfo->RXMSG;
			return 0;						// Not KISS Data
		}

		//
		//	Remove KISS control byte
		//

		if (pVCOMInfo->RXMSG[0] == 12)
		{
			//	AckMode Frame. Return the next 2 bytes, but don't pass them to Host

			UCHAR AckResp[8];

			AckResp[0] = FEND;
			memcpy(&AckResp[1], &pVCOMInfo->RXMSG[0], 3);	//Copy Opcode and Ack Bytes
			AckResp[4] = FEND;
			WriteCommBlock(port, AckResp, 5);

			len -= 2;
			memcpy(&buff->DEST[0], &pVCOMInfo->RXMSG[3], len);
//			Debugprintf("VKISS Ackmode Frame");
		}
		else
	
			memcpy(&buff->DEST[0], &pVCOMInfo->RXMSG[1], len);

		len += MSGHDDRLEN;

		PutLengthinBuffer((PDATAMESSAGE)buff, len);
		
		//
		//	reset pointers
		//

		pVCOMInfo->MSGREADY=FALSE;
		pVCOMInfo->RXMPTR=(UCHAR *)&pVCOMInfo->RXMSG;

		return len;
	}
	else

	return 0;					// nothing doing
}

static void CheckReceivedData(PVCOMINFO pVCOMInfo)
{
 	UCHAR c;

	if (pVCOMInfo->RXBCOUNT == 0)
	{	
		//
		//	Check com buffer
		//
	
		pVCOMInfo->RXBCOUNT = ReadCommBlock(pVCOMInfo, (LPSTR) &pVCOMInfo->RXBUFFER, MAXBLOCK-1 );
		pVCOMInfo->RXBPTR=(UCHAR *)&pVCOMInfo->RXBUFFER; 
	}

	if (pVCOMInfo->RXBCOUNT == 0)
		return;

	while (pVCOMInfo->RXBCOUNT != 0)
	{
		pVCOMInfo->RXBCOUNT--;

		c = *(pVCOMInfo->RXBPTR++);

		if (pVCOMInfo->ESCFLAG)
		{
			//
			//	FESC received - next should be TFESC or TFEND

			pVCOMInfo->ESCFLAG = FALSE;

			if (c == TFESC)
				c=FESC;
	
			if (c == TFEND)
				c=FEND;

		}
		else
		{
			switch (c)
			{
			case FEND:		
	
				//
				//	Either start of message or message complete
				//
				
				if (pVCOMInfo->RXMPTR == (UCHAR *)&pVCOMInfo->RXMSG)
					continue;

				pVCOMInfo->MSGREADY=TRUE;
				return;

			case FESC:
		
				pVCOMInfo->ESCFLAG = TRUE;
				continue;

			}
		}
		
		//
		//	Ok, a normal char
		//

		*(pVCOMInfo->RXMPTR++) = c;

	}

	if (pVCOMInfo->RXMPTR - (UCHAR *)&pVCOMInfo->RXMSG > 500)
		pVCOMInfo->RXMPTR=(UCHAR *)&pVCOMInfo->RXMSG;
	
 	return;
}

static PVCOMINFO CreateInfo( int port,int speed, int bpqport )
{
   PVCOMINFO pVCOMInfo ;

   if (NULL == (pVCOMInfo =
                   (PVCOMINFO) LocalAlloc( LPTR, sizeof( VCOMINFO ) )))
      return ( (PVCOMINFO) -1 ) ;

 	pVCOMInfo->RXBCOUNT=0;
	pVCOMInfo->MSGREADY=FALSE;
	pVCOMInfo->RXBPTR=(UCHAR *)&pVCOMInfo->RXBUFFER; 
	pVCOMInfo->RXMPTR=(UCHAR *)&pVCOMInfo->RXMSG;
   
	pVCOMInfo->ComDev = 0 ;
	pVCOMInfo->Connected = FALSE ;
	pVCOMInfo->Port = port;

	VCOMInfo[bpqport]=pVCOMInfo;
	
	return (pVCOMInfo);
}

static BOOL NEAR DestroyTTYInfo( int port )
{
   PVCOMINFO pVCOMInfo ;

   if (NULL == (pVCOMInfo = VCOMInfo[port]))
      return ( FALSE ) ;

   LocalFree( pVCOMInfo ) ;

   VCOMInfo[port] = 0;

   return ( TRUE ) ;

} 

static int ReadCommBlock(PVCOMINFO  pVCOMInfo, LPSTR lpszBlock, DWORD nMaxLength)
{
	DWORD dwLength = 0;
	DWORD Available = 0;

	if (Win98)
		DeviceIoControl(pVCOMInfo->ComDev, (pVCOMInfo->Port << 16) | W98_SERIAL_GETDATA,
					NULL,0,lpszBlock,nMaxLength, &dwLength,NULL);

	else if (pVCOMInfo->NewVCOM)
	{
		int ret = PeekNamedPipe(pVCOMInfo->ComDev, NULL, 0, NULL, &Available, NULL);

		if (ret == 0)
		{
			ret = GetLastError();

			if (ret == ERROR_BROKEN_PIPE)
			{
				CloseHandle(pVCOMInfo->ComDev);
				pVCOMInfo->ComDev = INVALID_HANDLE_VALUE;
				return 0;
			}
		}

		if (Available > nMaxLength)
			Available = nMaxLength;
		
		if (Available)
		{
			UCHAR * ptr1 = lpszBlock;
			UCHAR * ptr2 = lpszBlock;
			UCHAR c;
			int Length;
			
			ReadFile(pVCOMInfo->ComDev, lpszBlock, Available, &dwLength, NULL);

			// Have to look foro FF escape chars

			Length = dwLength;

			while (Length != 0)
			{
				c = *(ptr1++);
				Length--;

				if (c == 0xff)
				{
					c = c = *(ptr1++);
					Length--;
					
					if (c == 0xff)			// ff ff means ff
					{
						dwLength--;
					}
					else
					{
						// This is connection statua from other end

						dwLength -= 2;
						pVCOMInfo->NewVCOMConnected = c;
						continue;
					}
				}
				*(ptr2++) = c;
			}
		}
	}

	else
		DeviceIoControl(
			pVCOMInfo->ComDev,IOCTL_SERIAL_GETDATA,NULL,0,lpszBlock,nMaxLength, &dwLength,NULL);

   return (dwLength);

}

static BOOL WriteCommBlock(int port, UCHAR * Message, DWORD MsgLen)
{
	ULONG bytesReturned;

//	if ((rand() % 100) > 80)
//		return 0;

	if (Win98)
		return DeviceIoControl(
			VCOMInfo[port]->ComDev,(VCOMInfo[port]->Port << 16) | W98_SERIAL_SETDATA,Message,MsgLen,NULL,0, &bytesReturned,NULL);

	else if (VCOMInfo[port]->NewVCOM)
	{
		// Have to escape all oxff chars, as these are used to get status info 

		UCHAR NewMessage[1000];
		UCHAR * ptr1 = Message;
		UCHAR * ptr2 = NewMessage;
		UCHAR c;

		int Length = MsgLen;

		while (Length != 0)
		{
			c = *(ptr1++);
			*(ptr2++) = c;

			if (c == 0xff)
			{
				*(ptr2++) = c;
				MsgLen++;
			}
			Length--;
		}

		return WriteFile(VCOMInfo[port]->ComDev, NewMessage, MsgLen, &bytesReturned, NULL);
	}
	else
		return DeviceIoControl(
			VCOMInfo[port]->ComDev,IOCTL_SERIAL_SETDATA,Message,MsgLen,NULL,0, &bytesReturned,NULL);

}



