// comtool.cpp

#include "stdafx.h"

//
// comtool provides a really simple console based serial
//         port monitor for Windows.
//
//  blame shardy@@differentchairs.com
//
//
// a usecase scenario:
//
//  To relay com2: traffic on BOX1 to remote client BOX2 on the Windows network
//
//  1. run comtool as port monitor on BOX1:
//
//		 COMTOOL A #com2:9600,n,8,1 L\\.\pipe\com2log
//
//  2. then from BOX2, connect to the resulting pipe
//
//		 FINDSTR "^" < \\BOX1\pipe\com2log
//
//

#include <conio.h>
#include <stdlib.h>
#include <string>
#include <time.h>
#include <map>
#include "Tserial_event.h" // 3rd party class from tetraedre.com

using namespace std;

struct ByteBuf
	{
	ByteBuf(int blen, unsigned char*p) : buflen(blen), pbuf(p) {}
	int				buflen;
	unsigned char*	pbuf;
	};

typedef map<char,ByteBuf*> MACROMAP;

MACROMAP MacroMap;

char *commport = "COM1";
int baudrate = 9600;
serial_parity parity = spEVEN;
int databits = 8;
int stopbits = ONESTOPBIT;
int hwHandshake = 0;

int handshake_CTS = 0;
int handshake_DTR = 0;
HANDLE logfile = INVALID_HANDLE_VALUE;
char *logname = 0;

#define FOREVER -1

char hexdigit(int c)
	{
	if (c<10)
		{
		c += '0';
		}
	else
		{
		c = c-10 + 'A';
		}
	return c;
	}

void charout(char c)
{
	if (INVALID_HANDLE_VALUE != logfile)
		{
			DWORD dwWrite;
			if (FALSE == WriteFile(logfile, &c, 1, &dwWrite, NULL))
			{
				// write failed..
				DWORD err = GetLastError();
				if (ERROR_NO_DATA==err)
				{
					// pipe client went away. attempt reconnect & retry
					DisconnectNamedPipe(logfile); // don't bother with flush
					ConnectNamedPipe(logfile,NULL);
					WriteFile(logfile, &c, 1, &dwWrite, NULL);
				}
				else
					fprintf(stderr, "Write error: %ld", err);

			}
			//fprintf(logfile,"%c", c );
		}
}

void printhex(char c)
	{
	char buf[3];
	sprintf(buf, "%c%c", hexdigit((c>>4) & 0xf), hexdigit(c&0xf));
	printf(buf );
	charout(buf[0]);
	charout(buf[1]);
	}

void printchar(char c)
	{
	printf("%c", c );
	charout(c);
	}

typedef void (*type_printfn) (char);
type_printfn printIt  = printhex;

volatile int recvd = 0;

int waitforchar = 0;
int waitlimit = FOREVER;

void OnCharArrival(char c)
	{
	recvd = 1;
	printIt(c);
	}


void OnConnected(Tserial_event *)
	{
	fprintf(stderr,"<connected>\n");
	}


void OnDisconnected(Tserial_event *)
	{
	}


BYTE tailbuf[1000];
LONG bufsize = sizeof tailbuf;

// bufptr == endptr => empty
// bufptr is front. (next char to send)
LONG bufptr = 0;
LONG endptr = 0;

#define isEmpty (bufptr==endptr)

int pushbytes(BYTE* buf, int count)
	{
	int result = 0;
	for (int i=0; i<count; i++)
		{
		int tstEnd=endptr+1;
		if (tstEnd==bufsize)
			{
			tstEnd=0;
			}
		if (tstEnd==bufptr)
			{
			int delta = count-i;
			fprintf(stderr,"<Input buffer full. Dropped %d %s>\n", delta, (delta>1)?"bytes":"byte");
			break; // already full. Don't push.
			}
		tailbuf[endptr] = buf[i];

		::InterlockedExchange(&endptr, tstEnd);
		result++;
		}
	return result;
	}

char popbyte()
	{
	LONG tmpptr = bufptr;
	char c = tailbuf[tmpptr++];
	if (tmpptr >= bufsize)
		{
		tmpptr = 0;
		}
	::InterlockedExchange(&bufptr, tmpptr);
	return c;
	}

void OnCharSent(Tserial_event *com)
	{
	if (!isEmpty)
		{
		com->sendChar( popbyte() );
		}
	}


int sendMsg(Tserial_event* com, char *msg, int msglen)
	{
	static int busy = 0;
	if (busy) // serialize...
		{
		return 0;
		}
	busy = 1;
	int result = 1;
	if (msglen>0)
		{
		if (msg)
			{
			if (!isEmpty) // potential flakiness ... !
				{
				result = pushbytes((BYTE*)&msg[0], msglen);
				}
			else
				{
				result = pushbytes((BYTE*)&msg[1], msglen-1);
				if (result || msglen-1==0)
					{
					com->sendChar((char) *msg);
					result = 1;
					}
				}
			}
		}
	busy = 0;
	return result;
	}

int trySendMsg(Tserial_event* com, char *msg, int msglen)
	{
	int result = 0;
	for (int j=0; j<5; j++)
		{
		if (sendMsg(com, msg, msglen))
			{
			result = 1;
			break;
			}
		::Sleep(50);
		}
	return result;
	}

//#define Addr  0
//#define Synch 0xAB


BYTE *cmd = 0;
int  cmdlen = 0;
int  initDelay = 0;
int  intercharDelay = 0;
int  loop = 0;

long wait= FOREVER; // -1 => forever

void parseArgs(int argc, char* argv[]);

void chkIncoming(Tserial_event *com, volatile int*abortflag=0)
	{
	char buf[1];
	for (int i=0; i<10 && (!abortflag || !*abortflag); i++)
		{
		while (_kbhit())
			{
			int c =  _getch();

			MACROMAP::const_iterator It;

			It = MacroMap.find( c );
			char *p;
			unsigned plen = 1;
			if (It==MacroMap.end())
				{
				buf[0] = c;
				p = buf;
				plen = 1;
				}
			else
				{
				p = (char*)It->second->pbuf;
				plen = It->second->buflen;
				}
			trySendMsg(com, p, plen);
			}
		::Sleep(100);
		}
	}

bool isPipe(char *fname)
{
	#define PIPE_PREFIX "\\\\.\\PIPE\\"
	return 0==_strnicmp(fname, PIPE_PREFIX, strlen(PIPE_PREFIX));
}

int main(int argc, char* argv[])
	{
	int result = 0;
	long waited = 0;

	parseArgs(argc, argv);
	Tserial_event *com = new Tserial_event();

	if (com!=0)
		{
		// set callbacks

		com->setManagerOnCharArrival  (OnCharArrival);

		com->setManagerOnConnected    (OnConnected);
		com->setManagerOnDisconnected (OnDisconnected);

		com->setManagerOnCharSent     (OnCharSent);

		result = com->connect(commport, baudrate, parity, databits, stopbits, hwHandshake);
		if (0!=result)
		{
			fprintf(stderr, "ABORT: Error opening port\n");
		}
		else
		{
			if (logname)
			{
				if (isPipe(logname))
				{
					logfile =CreateNamedPipe(logname
									,PIPE_ACCESS_OUTBOUND|FILE_FLAG_WRITE_THROUGH
									,PIPE_TYPE_BYTE|PIPE_WAIT,
									1, //MaxServers,
									512, // outbufsize
									512, // inbufsize
									1000,// 1 sec default timeout
									NULL);
				}
				else
				{
					logfile = CreateFile(logname, FILE_APPEND_DATA,
							FILE_SHARE_READ,	//
							NULL,				// default security
							OPEN_EXISTING,		// append
							FILE_ATTRIBUTE_NORMAL,	// normal file
							NULL);				// no attr template
					if (INVALID_HANDLE_VALUE==logfile &&
							ERROR_FILE_NOT_FOUND == GetLastError())
					{
						logfile = CreateFile(logname, FILE_APPEND_DATA,
								FILE_SHARE_READ,	//
								NULL,				// default security
								CREATE_NEW,			// append
								FILE_ATTRIBUTE_NORMAL,	// normal file
								NULL);				// no attr template
					}
				}

				if (INVALID_HANDLE_VALUE == logfile)
				{
					fprintf(stderr,"error opening logfile:[%s]\n",logname);
					result = -1;
				}
				else
				{
					//block until our client connects
					ConnectNamedPipe(logfile,NULL);

					// log timestamp
					time_t t;
					time(&t);
					tm *ltime = localtime(&t);
					DWORD dwWrite;
					char *sep = "---\n";
					if (FALSE == WriteFile(logfile, "\n",1, &dwWrite, NULL))
					{
						// write failed..
					}
					else if (FALSE == WriteFile(logfile, sep, strlen(sep), &dwWrite, NULL))
					{
						// write failed..
					}
					else if (FALSE == WriteFile(logfile, asctime(ltime), strlen(asctime(ltime)), &dwWrite, NULL))
					{
						// write failed..
					}
					else if (FALSE == WriteFile(logfile, sep, strlen(sep), &dwWrite, NULL))
					{
						// write failed..
					}
				}
			}

			if (0==result)
				{
				do	{
					if (initDelay)
						{
						for (int j=0; j<initDelay; j++)
							{
							chkIncoming(com);
							}
						}
					if (intercharDelay>0)
					{
						for (int i=0; i<cmdlen; i++)
						{
							trySendMsg(com, (char*)&cmd[i], 1);
							::Sleep(intercharDelay);
						}
					}
					else
						trySendMsg(com, (char*)cmd, cmdlen);
					if (loop && wait>=0)
						{
						if (waited>=wait)
							{
							break;
							}
						waited++;
						}
					}
					while (loop);
				while (1)
					{

					if (wait>=0)
						{
						if (waited>=wait)
							{
							break;
							}
						waited++;
						}
					chkIncoming(com);
					}

				while (waitforchar && !recvd)
					{
					if (waitlimit != FOREVER && waited>= waitlimit)
						{
						break;
						}
					chkIncoming(com, &recvd);
					waited++;
					}
				com->disconnect();
				delete com;
				com = 0;
				}
			}
		}
	return result;
	}


void help()
	{
	char *usage = "\nv1.1 usage:\n\ncomtool [L<fname>] [-i] [+j] [k] [*n]"
			"\n\t[A] [R] [!] [#<x>] [@kh1[,..hn]] [$cmd] [~cmd]  [H h1 h2..]\n\n"
		"\tL logs output to <fname> (pipe works here: \\\\.\\pipe\\pipeName)\n\n"
		"\t[Serial Port]\n"
		"\ti idle time before sending any commands\n"
		"\tj maximum time to wait for an incoming character\n"
		"\tk number of seconds to wait before exit\n"
		"\tn number of msec to to delay after each sent character\n"
		"\tA shows received bytes as ascii\n"
		"\tR loop. (send output repeatedly)\n"
		"\t! turns on hardware handshake\n"
		"\t# <x> is commport settings. (E.g., COM1:9600,N,8,1)\n"
		"\t@ defines a key macro for key k, composed of hex bytes h1..hn\n"
		"\t$ defines the sequence of characters to send\n"
		"\t~ defines the sequence of characters to send (\\r replaces embedded '~')\n"
		"\tH defines the sequence of hex bytes to send\n";
	fprintf(stderr,"\ncomtool - serial port access\n%s",usage);
	::exit(1);
	}

char *upcase(char*s)
	{
	for (char*p=s; *p; p++)
		{
		if (islower(*p))
			{
			*p = toupper(*p);
			}
		}
	return s;
	}

int NullChk(char*p, char*msg)
	{
	if (!p)
		{
		fprintf(stderr,"%s\n", msg);
		help();
		}
	return 1;
	}

void commSettings(char *commStr)
	{
	if (strlen(commStr)>1)
		{
		char *token = strtok( commStr, ":" );
		if (NullChk(token,"Unexpected comm cfg") && 0!=strncmp(upcase(token),"COM",3))
			{
			fprintf(stderr,"Bad comm port: %s\n", token);
			help();
			}
		commport = token;
		token = strtok( NULL, "," );
		NullChk(token,"Bad comm cfg");
		baudrate = atol(token);
		if (baudrate<110 || baudrate>900000)
			{
			fprintf(stderr,"Bad baud\n");
			help();
			}
		token = strtok( NULL, "," );
		NullChk(token, "missing parity");
		upcase(token);
		if (0==strcmp(token,"N"))
			{
			parity = spNONE;
			}
		else if (0==strcmp(token,"O"))
			{
			parity = spODD;
			}
		else if (0==strcmp(token,"E"))
			{
			parity = spEVEN;
			}
		else
			{
			fprintf(stderr,"Bad parity\n");
			help();
			}
		token = strtok( NULL, "," );
		NullChk(token, "missing databits");
		databits=atol(token);
		if (databits!=7 && databits!=8)
			{
			fprintf(stderr,"Bad databits\n");
			help();
			}
		token = strtok( NULL, "," );
		NullChk(token, "missing stopbits");
		stopbits=atol(token);
		if (stopbits!=1 && stopbits!=2)
			{
			fprintf(stderr,"Bad stopbits\n");
			help();
			}
		stopbits = (stopbits==1)? ONESTOPBIT : TWOSTOPBITS;
		}
	}


unsigned countCh(int c, char* s)
	{
	int result = 0;
	for (unsigned i=0; s && i<strlen(s); i++)
		{
		if (c==s[i])
			{
			result++;
			}
		}
	return result;
	}

void appendMacro(char c, char *buf) // 'c', "f,cc,3"
	{
	unsigned bufsize = countCh(',',buf)+1;
	char *p = (char*)malloc(bufsize);
	for (unsigned i=0; i<bufsize; i++)
		{
		char *endptr;
		if (!isxdigit(*buf))
			{
			fprintf(stderr, "bad macro (@) definition\n");
			help();
			}
		long x = strtol(buf,&endptr,16);
		p[i] = (char)x;
		buf = endptr;
		if (*buf==',')
			{
			buf++;
			}
		}
	MacroMap[c] = new ByteBuf(bufsize, (unsigned char*)p);
	}

#define COMMENTCHAR ';'

void parseArgs(int argc, char* argv[])
	{
	for (int i=1; i<argc; i++)
		{
		if (isdigit(argv[i][0]))
			{
			wait = atol(argv[i]);
			}
		else if ( argv[i][0] == '*' )
			{
			intercharDelay = atol(&argv[i][1]);
			}
		else if ( argv[i][0] == '-' )
			{
			initDelay = atol(&argv[i][1]);
			}
		else if (*argv[i] == '+') //
			{
			waitforchar = 1;
			if (isdigit(argv[i][1]))
				{
				waitlimit = atol(&argv[i][1]);
				}
			}
		else if ( 0==stricmp(argv[i], "R") )
			{
			loop = 1;
			}
		else if ( 0==stricmp(argv[i], "A") )
			{
			printIt = printchar;
			}
		else if ( 0==strcmp(argv[i], "!") )
			{
			hwHandshake = 1;
			}
		else if (*argv[i] == '#') // port settings. e.g., "9600:N,8,1"
			{
			commSettings(&argv[i][1]);
			}
		else if (*argv[i] == '@') //
			{
			if (strlen(argv[i])<3)
				{
				fprintf(stderr, "<bad macro def>\n");
				help();
				}
			appendMacro(argv[i][1], &argv[i][2]);
			}
		else if (0==stricmp(argv[i], "H"))
			{
			// set cmdlen
			cmdlen = argc - i - 1;
			// allocate cmdbuf
			cmd = (BYTE*)malloc(cmdlen);
			//read each byte
			for (int j=i+1; j<argc; j++)
				{
				if (*argv[j]==COMMENTCHAR)
					{
					i = argc;
					break;
					}
				int nxtByt;
				sscanf( argv[j], "%x", &nxtByt );
				cmd[j-(i+1)] = nxtByt;
				}
			break;
			}
		else if ( '$'==*argv[i] )
			{
				if (strlen(argv[i]) > 1 )
				{
					#define CRPROXY '~'
					cmdlen = strlen(argv[i]) - 1;
					cmd = (BYTE*)malloc(cmdlen);
					memcpy(cmd, &argv[i][1], cmdlen);
				}
			}
		else if ( '~'==*argv[i] )
			{
				if (strlen(argv[i]) > 1 )
				{
					#define CRPROXY '~'
					cmdlen = strlen(argv[i]) - 1;
					cmd = (BYTE*)malloc(cmdlen);
					memcpy(cmd, &argv[i][1], cmdlen);
					//printf(" ---> cmd:[%*s]\n", cmdlen, cmd);
					for (int i=0; i<cmdlen; i++)
					{
						if (CRPROXY==cmd[i])
							cmd[i] = '\r';
					}
				}
			}
		else if ( 'L'==*argv[i] )
			{
				if (strlen(argv[i]) > 1 )
				{
					logname = &argv[i][1];
				}
			}
		else if ( COMMENTCHAR == *argv[i] )
			{
			i = argc;
			break;
			}
		else
			{
			help();
			}
		}
	}