#include <windows.h>
#include <stdio.h>
#include <winioctl.h>
#include "digidga.h"


/*************************************************************************************
**
**	NOTE!!!!!!!!!!!!!!!
**
**	That the following defines are copied from ntddser.h from the DDK.  That is
**	 the reason they are not in the universal .h file.  ONLY NEEDED IN THE APPLICATION
**
**************************************************************************************/
#define IOCTL_SERIAL_WAIT_ON_MASK       CTL_CODE(FILE_DEVICE_SERIAL_PORT,18,METHOD_BUFFERED,FILE_ANY_ACCESS)

#define SERIAL_EV_RXCHAR           0x0001  // Any Character received
#define SERIAL_EV_RXFLAG           0x0002  // Received certain character
#define SERIAL_EV_TXEMPTY          0x0004  // Transmitt Queue Empty
#define SERIAL_EV_CTS              0x0008  // CTS changed state
#define SERIAL_EV_DSR              0x0010  // DSR changed state
#define SERIAL_EV_RLSD             0x0020  // RLSD changed state
#define SERIAL_EV_BREAK            0x0040  // BREAK received
#define SERIAL_EV_ERR              0x0080  // Line status error occurred
#define SERIAL_EV_RING             0x0100  // Ring signal detected
#define SERIAL_EV_PERR             0x0200  // Printer error occured
#define SERIAL_EV_RX80FULL         0x0400  // Receive buffer is 80 percent full
#define SERIAL_EV_EVENT1           0x0800  // Provider specific event 1
#define SERIAL_EV_EVENT2           0x1000  // Provider specific event 2

typedef struct
{
   OVERLAPPED Overlapped;
   LONG Status;
   CHAR Buffer[BUFFER_SIZE];

} TRACE;

HANDLE hAbortEvent;

/********************************************************************************************
**
**	BOOL WINAPI ControlHandler( DWORD dwControlType )
**
**	Purpose: To handle the control.
**
*********************************************************************************************/          
BOOL WINAPI ControlHandler( DWORD dwControlType )
{
   switch( dwControlType )
   {
      case CTRL_C_EVENT:

         // Tell all the threads that it's OK for them to report.
         SetEvent( hAbortEvent );
         // Double control-C will force process exit.
         SetConsoleCtrlHandler( ControlHandler, FALSE );

         return TRUE;

      default:
         return FALSE;
   }
}
/**********************************************************************************************
**
**	void HexDump( UCHAR *Prefix, UCHAR *Addr, ULONG Len )
**
**	Purpose: To dump the hex.
**
**********************************************************************************************/
void HexDump(UCHAR *Prefix, UCHAR *Addr, ULONG Len)
{
   UCHAR Buf[80];
   UCHAR *StartAddr = Addr;
   ULONG i, j;

   while (Len)
   {
      printf("%s:%04x:", Prefix, (ULONG)(Addr-StartAddr));
      if (Len>=16)
      {
         printf("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
                "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:",
                Addr[0], Addr[1], Addr[2], Addr[3],
                Addr[4], Addr[5], Addr[6], Addr[7],
                Addr[8], Addr[9], Addr[10], Addr[11],
                Addr[12], Addr[13], Addr[14], Addr[15]);
         for (i=0; i<16; i++)
         {
            Buf[i] = (Addr[i]>=' ') ? Addr[i] : '.';
         }
      }
      else
      {
         for (i=0; i<Len; i++)
         {
            printf("%02x:", Addr[i]);
            Buf[i] = (Addr[i]>=' ') ? Addr[i] : '.';
         }
         printf("%*s", (16-i)*3, " ");
      }
      Buf[i] = 0;
      Len -= i;
      printf("[%s]\n", Buf);
      Addr += 16;
   }
}
/************************************************************************************
**
**	void ResolveStatus( DWORD Status, CHAR *szStatus )
**
**	Purpose: To resolve the status.
**
*************************************************************************************/
void ResolveStatus(DWORD Status, CHAR *szStatus)
{
   switch (Status)
   {
      case 0x0:
         strcpy(szStatus, "SUCCESS");
         break;
      case 0x102:
         strcpy(szStatus, "TIMEOUT");
         break;
      case 0xC0000120:
         strcpy(szStatus, "CANCEL ");
         break;
      default:
         sprintf(szStatus, "0x%8x", Status);
         break;
   }
}
/***************************************************************************************
**
**	void ResolveModemSignals( BYTE Mstat, CHAR *szSignals )
**
**	Purpose: To resolve the modem signals and what they mean to the user.
**
***************************************************************************************/
void ResolveModemSignals(BYTE Mstat, CHAR *szSignals)
{
   strcpy(szSignals, (Mstat&0x02) ? "RTS " : "rts ");
   strcat(szSignals, (Mstat&0x10) ? "CTS " : "cts ");
   strcat(szSignals, (Mstat&0x01) ? "DTR " : "dtr ");
   strcat(szSignals, (Mstat&0x20) ? "DSR " : "dsr ");
   strcat(szSignals, (Mstat&0x80) ? "DCD " : "dcd ");
   strcat(szSignals, (Mstat&0x40) ? "RI " : "ri ");
}
/***************************************************************************************
**
**	void ReportTraceResults( char *Buffer, DWORD Length, DWORD Flags )
**
**	Purpose: To report the trace results returned by the driver that we called.
**
****************************************************************************************/
void ReportTraceResults(CHAR *Buffer, DWORD Length, DWORD Flags)
{
   TRACE_HEADER *Hdr;
   CHAR *BufferStart = Buffer;
   static double TimeBase=0.0;

   while (Length)
   {
      TRACE_HEADER *Hdr = (TRACE_HEADER*)Buffer;
      Buffer += sizeof(TRACE_HEADER);
      Length -= sizeof(TRACE_HEADER);
      if (TimeBase==0.0)
      {
         TimeBase = (double)((*(LONGLONG*)&Hdr->Time)/10000L)/1000.0;
      }
      switch (Hdr->FunctionType)
      {
         case TRACETYPE_WRITE:
         case TRACETYPE_READ:
         {
            READ_TRACE *Read = (READ_TRACE*)Buffer;
            double Time = (double)((*(LONGLONG*)&Hdr->Time)/10000L)/1000.0-TimeBase;
            CHAR szStatus[32], szModem[32];

            ResolveStatus((NTSTATUS)Read->Status, szStatus);
            ResolveModemSignals(Hdr->ModemStatus, szModem);
            if (Flags&MONITOR_MSTAT)
            {
               printf("%s: "TIMESTAMP_FORMAT" Len(%04x) Sts(%s) %s\n", 
                      ((Hdr->FunctionType==TRACETYPE_READ) ? "READ ":"WRITE"),
                      Time, 
                      Hdr->Length,
                      szStatus,
                      szModem);
            }

            Buffer += sizeof(READ_TRACE);
            Length -= sizeof(READ_TRACE);
            if (Flags&MONITOR_DATA)
            {
               HexDump(((Hdr->FunctionType==TRACETYPE_READ) ? "READ ":"WRITE"),
                       (BYTE*)Buffer, 
                       min((ULONG)Hdr->Length, (ULONG)(BUFFER_SIZE+BufferStart-Buffer)));
            }
            Buffer += min(Hdr->Length, Length);
            Length -= (DWORD)min(Hdr->Length, Length);
            break;
         }
         case TRACETYPE_IOCTL:
         {
            IOCTL_TRACE *Ioctl = (IOCTL_TRACE*)Buffer;
            double Time = (double)((*(LONGLONG*)&Hdr->Time)/10000L)/1000.0-TimeBase;
            CHAR szStatus[32], szModem[32];

            Buffer += sizeof(IOCTL_TRACE);
            Length -= sizeof(IOCTL_TRACE);
            ResolveStatus((NTSTATUS)Ioctl->Status, szStatus);
            ResolveModemSignals(Hdr->ModemStatus, szModem);

            switch (Ioctl->IoctlType)
            {
               case IOCTL_SERIAL_WAIT_ON_MASK:
               {
                  if (Flags&MONITOR_EVENTS)
                  {
                     DWORD Events = *(DWORD*)Buffer;
                     printf("EVENT: "TIMESTAMP_FORMAT"           Sts(%s) %s\n\t", 
                            Time,
                            szStatus,
                            szModem);
                     if (Events&SERIAL_EV_RXCHAR)
                     {
                        printf("RXCHAR ");
                     }
                     if (Events&SERIAL_EV_RXFLAG)
                     {
                        printf("RXFLAG ");
                     }
                     if (Events&SERIAL_EV_TXEMPTY)
                     {
                        printf("TXEMPTY ");
                     }
                     if (Events&SERIAL_EV_CTS)
                     {
                        printf("CTS ");
                     }
                     if (Events&SERIAL_EV_DSR)
                     {
                        printf("DSR ");
                     }
                     if (Events&SERIAL_EV_RLSD)
                     {
                        printf("RLSD ");
                     }
                     if (Events&SERIAL_EV_BREAK)
                     {
                        printf("BREAK ");
                     }
                     if (Events&SERIAL_EV_ERR)
                     {
                        printf("ERR ");
                     }
                     if (Events&SERIAL_EV_PERR)
                     {
                        printf("PERR ");  
                     }
                     if (Events&SERIAL_EV_RING)
                     {
                        printf("RING ");
                     }
                     if (Events&SERIAL_EV_RX80FULL)
                     {
                        printf("RX80FULL ");
                     }
                     printf("\n");
                  }
                  break;
               }
               default:
               {   
                  if ( Flags > 3 )
                  {
                     printf("IOCTL: "TIMESTAMP_FORMAT" Len(%04x) Sts(%s) %s\n", 
                            Time, 
                            Hdr->Length,
                            szStatus,
                            szModem);
                     HexDump("IOCTL",
                             (BYTE*)Buffer, 
                             min((ULONG)Hdr->Length, BUFFER_SIZE+(ULONG)(BufferStart-Buffer)));
                  }
                  break;
               }
            }
            Buffer += min(Hdr->Length, Length);
            Length -= (ULONG)min(Hdr->Length, Length);
            break;
         }
         case TRACETYPE_MODEM_STATUS:
         {
            double Time = (double)((*(LONGLONG*)&Hdr->Time)/10000L)/1000.0-TimeBase;
            CHAR szStatus[32], szModem[32];

            ResolveModemSignals(Hdr->ModemStatus, szModem);

            if (Flags&MONITOR_MSTAT)
            {
               printf("MODEM: "TIMESTAMP_FORMAT"                        %s\n", 
                      Time, 
                      szModem);
            }
            break;
         }
         default:
            break;
      }
   }

}
/*****************************************************************************************
**
**	void Monitor( HANDLE hCtrl, DWORD ComPort, DWORD Flags )
**
**  Purpose: To monitor OK 
**
*****************************************************************************************/
void Monitor(HANDLE hCtrl, DWORD ComPort, DWORD Flags)
{
   TRACE Trace[NUM_OVERLAPPED];
   DWORD i, CurrentTrace, BytesReturned;
   BOOL Status;

   if (!Flags)
   {
      printf("No Flags set!\n");
      return;
   }
   hAbortEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

   if( !SetConsoleCtrlHandler( ControlHandler, TRUE ) )
   {
      printf("Unable to establish break handler.\n");
      return;
   }

   for (i=0; i<NUM_OVERLAPPED; i++)
   {
      memset(Trace, 0, sizeof(Trace));
      Trace[i].Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
   }
   
   SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );

   printf("Monitoring COM%d.  Flags:\n", ComPort);
   if (Flags&MONITOR_DATA)
   {
      printf("\tData\n");
   }
   if (Flags&MONITOR_MSTAT)
   {
      printf("\tModem Status\n");
   }
   if (Flags&MONITOR_EVENTS)
   {
      printf("\tEvents\n");
   }
   printf("Press Ctrl+C to stop monitor...\n");

   // Issue the initial reads.
   CurrentTrace = 0;

   while (WaitForSingleObject(hAbortEvent, 0)==WAIT_TIMEOUT)
   {
      TRACE *t = &Trace[CurrentTrace];

      if (t->Status==ERROR_IO_PENDING)
      {
         Status = GetOverlappedResult(hCtrl,
                                      &t->Overlapped,
                                      &BytesReturned,
                                      TRUE);
         if (!Status)
         {
            printf("Error in tracing.\n");
            break;
         }
         ReportTraceResults(t->Buffer, BytesReturned, Flags);
      }
      ResetEvent(t->Overlapped.hEvent);
      Status = DeviceIoControl(hCtrl,
                               IOCTL_DIGI_GET_PORT_TRACE,
                               &ComPort, sizeof(ComPort),
                               t->Buffer, BUFFER_SIZE,
                               &BytesReturned,
                               &t->Overlapped);
      if (!Status)
      {
         t->Status = GetLastError();
      }
      else
      {
         printf("Ioctl complete too soon.\n");
      }
      CurrentTrace = (CurrentTrace + 1) % NUM_OVERLAPPED;
   }

}
