/** @name Police Dog Sniff

	Serial port sniffer/forwarder
	My first try with overlapped IO and my does this
	have some decent bugs.  It mostly works, but there
	are some problems with a read after data ready still
	going into overlap mode.

	@author Michael Hope, michaelh@earthling.net
	@version 0.1
*/
/* Use doc++ to extract the comments from this file */
/*@{*/
#include <windows.h>
#include <assert.h>
#include <stdio.h>

static HANDLE hCom[3];
static OVERLAPPED oCom[3];
static OVERLAPPED oComWrite[3];
static FILE *logFP;


/** Maximum number of bytes to read in a loop */
#define BUFFER_LEN		512

/** Length of a dump line in bytes */
#define LINE_LEN		16

/** Sets the default baud rate.
	Used in OpenComm
*/
#define BAUD_RATE		115200

/** Default timeout on reads in milliseconds
	Used in OpenComm
*/
#define DEFAULT_TIMEOUT	50

/** Open and configure a serial port
	Opens the device, then configures the baud rate, bits, etc.  Sets the
	event mask to EV_RXCHAR only, and sets the timeouts.  Change BAUD_RATE
	to set the default baud rate

	@param szPortName	The port to open (COM1, COM3...)
	@return The handle of the open, configured device or INVALID_FILE_HANDLE
	on error
*/
HANDLE OpenComm(char *szPortName)
{
  HANDLE hRet;
  DCB dcb;
  COMMTIMEOUTS timeout;

  hRet = CreateFile(szPortName, 
				  GENERIC_READ | GENERIC_WRITE,
				  0,
				  NULL,
				  OPEN_EXISTING,
				  FILE_FLAG_OVERLAPPED,
				  NULL);
  
  if (hRet == INVALID_HANDLE_VALUE) {
	printf("OpenComm:  Cant open %s\n", szPortName);
	return hRet;
  }

  ZeroMemory(&dcb, sizeof(DCB));
  dcb.BaudRate = BAUD_RATE;
  dcb.ByteSize = 8;
  dcb.Parity = NOPARITY;
  dcb.StopBits = ONESTOPBIT;
  dcb.fBinary = 1;

  if (!SetCommState(hRet, &dcb)) {
	printf("OpenComm: Cant set state for %s\n", szPortName);
	CloseHandle(hRet);
	return INVALID_HANDLE_VALUE;
  }

  if (!SetCommMask(hRet, EV_RXCHAR)) {
	printf("OpenComm: Cant set event mask for %s\n", szPortName);
	CloseHandle(hRet);
	return INVALID_HANDLE_VALUE;
  }

  ZeroMemory(&timeout, sizeof(COMMTIMEOUTS));
  timeout.ReadIntervalTimeout = MAXDWORD;
  timeout.ReadTotalTimeoutMultiplier = MAXDWORD;
  timeout.ReadTotalTimeoutConstant = DEFAULT_TIMEOUT;
  timeout.WriteTotalTimeoutMultiplier = 1000*100/BAUD_RATE;
  timeout.WriteTotalTimeoutConstant = 1000;
  
  if (!SetCommTimeouts(hRet, &timeout)) {
	printf("OpenComm: Cant set timeouts for %s\n", szPortName);
	CloseHandle(hRet);
	return INVALID_HANDLE_VALUE;
  }

  EscapeCommFunction(hRet, SETDTR);
  EscapeCommFunction(hRet, CLRRTS);

  return hRet;
}

/** Print a system error message
	@param	szSource	Source function that it occured in
	@param	dwError		Error number
*/
void DumpError(char *szSource, DWORD dwError)
{
  LPVOID lpMsgBuf;
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
				NULL,
				dwError,
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
				(LPTSTR) &lpMsgBuf,
				0,
				NULL);
  printf("%s: %s\n", szSource, lpMsgBuf);
  LocalFree(lpMsgBuf);
}

/** Handles input from one of the ports
	Note:  assumes that data is present
	If bSource is zero, then a timeout has occured.  No read is attempted,
	but the current buffers are flushed.
	Note that the global handles and overlaps are accessed.

	@param bSource	Port number (0, 1, 2)
*/
void HandleInput(BYTE bSource)
{
  DWORD dwRead, dwError, dwWrote;
  int i;
  BYTE pBuffer[BUFFER_LEN];
  static pLineBuffer[LINE_LEN];
  static BYTE bLinePos = 0;
  static BYTE bLastSource = 0;

  if (bSource != bLastSource) {
	fflush(logFP);
	if (bSource == 0) {
	  fprintf(logFP, " (timeout)");
	  printf(" (timeout)");
	}
	/* Flush the buffer */
	if (bLastSource != 0) {
	  i = (16 - bLinePos)*3;
	  if (bSource != 0) {
		i+=10;
	  }
	  while (i--) {
		fprintf(logFP, " ");
		printf(" ");
	  }
	  for (i=0; i<bLinePos; i++) {
		fprintf(logFP, "%c", isprint(pLineBuffer[i])?pLineBuffer[i]:'.');
		printf("%c", isprint(pLineBuffer[i])?pLineBuffer[i]:'.');
	  }
	}
	if (bSource != 0) {
	  fprintf(logFP, "\n%u:", bSource);
	  printf("\n%u:", bSource);
	}
	bLastSource = bSource;
	bLinePos = 0;
  }

  if (bSource != 0) {
	dwRead = 0;
	if (ReadFile(hCom[bSource], pBuffer, BUFFER_LEN, &dwRead, &oCom[bSource])) {
	}
	else {
	  dwRead = 0;
	  dwError = GetLastError();
	  /* Should be there... wait for completion */
	  if (GetOverlappedResult(hCom[bSource], &oCom[bSource], &dwRead, TRUE)) {
	  }
	  else {
		dwRead = 0;
		DumpError("HandleInput: ReadFile", GetLastError());
	  }
	}
	if (dwRead != 0) {
	  /* Succeded */
	  WriteFile(hCom[3-bSource], pBuffer, dwRead, &dwWrote, &oComWrite[3-bSource]);
	  //WriteFile(hCom[bSource], pBuffer, dwRead, &dwWrote, &oComWrite[bSource]);
	  for (i=0; i<dwRead; i++) {
		if (bLinePos == LINE_LEN) {
		  fprintf(logFP, "          ");
		  printf("          ");
		  for (bLinePos=0; bLinePos < LINE_LEN; bLinePos++) {
			fprintf(logFP, "%c", isprint(pLineBuffer[bLinePos])?pLineBuffer[bLinePos]:'.');
			printf("%c", isprint(pLineBuffer[bLinePos])?pLineBuffer[bLinePos]:'.');
		  }
		  fprintf(logFP, "\n%u:", bSource);
		  printf("\n%u:", bSource);
		  bLinePos = 0;
		}
		fprintf(logFP, " %02X", pBuffer[i]&0xff);
		printf(" %02X", pBuffer[i]&0xff);
		pLineBuffer[bLinePos++] = pBuffer[i];
	  }
	}
  }
}

int main(void)
{
  HANDLE h;
  DWORD dwRead, dwMaskCom1, dwMaskCom2, dwTemp;
  BYTE bLastSource, bSource;
  BYTE bPending = 0;
  HANDLE hEvents[2];

  logFP = fopen("log.txt", "w");
  assert(logFP);

  hCom[1] = OpenComm("COM1");
  if (hCom[1] != INVALID_HANDLE_VALUE) {
	hCom[2] = OpenComm("COM2");
	if (hCom[2] != INVALID_HANDLE_VALUE) {
	  ZeroMemory(&oCom[1], sizeof(OVERLAPPED));
	  ZeroMemory(&oCom[2], sizeof(OVERLAPPED));
	  ZeroMemory(&oComWrite[1], sizeof(OVERLAPPED));
	  ZeroMemory(&oComWrite[2], sizeof(OVERLAPPED));

	  oCom[1].hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	  assert(oCom[1].hEvent);
	  oCom[2].hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	  assert(oCom[2].hEvent);
	  
	  hEvents[0] = oCom[1].hEvent;
	  hEvents[1] = oCom[2].hEvent;

	  bLastSource = 0;
	  
	  while (1) {
		bSource = 0;
		if (!(bPending & 1)) {
		  /* Wait is not set on COM1, so set it */
		  if (WaitCommEvent(hCom[1], &dwMaskCom1, &oCom[1])) {
			if (dwMaskCom1 & EV_RXCHAR) {
			  bSource |= 1;
			}
		  }
		  else {
			/* It's pending */
			bPending |= 1;
		  }
		}
		if (!(bPending & 2)) {
		  /* Wait is not set on COM2, so set it */
		  if (WaitCommEvent(hCom[2], &dwMaskCom2, &oCom[2])) {
			if (dwMaskCom2 & EV_RXCHAR) {
			  bSource |= 2;
			}
		  }
		  else {
			/* It's pending */
			bPending |= 2;
		  }
		}
		if (bSource == 0) {
		  /* Nothing ready, so Wait */
		  dwTemp = WaitForMultipleObjects(2, hEvents, FALSE, 400);
		  if (dwTemp != WAIT_FAILED) {
			if (dwTemp == WAIT_TIMEOUT) {
			  bSource = 0;
			}
			else {
			  bSource = (BYTE)(dwTemp - WAIT_OBJECT_0 + 1);
			}
		  }
		}
		if (bSource == 3) {
		  printf("Both triggered.\n");
		  if ((bSource & bLastSource)==2) {
			HandleInput(2);
			HandleInput(1);
			bLastSource = 1;
		  }
		  else {
			HandleInput(1);
			HandleInput(2);
			bLastSource = 2;
		  }
		}
		else {
		  HandleInput(bSource);
		  bLastSource = bSource;
		}
		bPending &= ~bSource;
	  }
	  CloseHandle(hCom[1]);
	  CloseHandle(hCom[2]);
	}
	else {
	  CloseHandle(hCom[1]);
	  return -2;
	}
  }
  else {
	return -1;
  }
  return 0;
}
/*@}*/







