/***************************************************************************
 *                                                                         *
 *   SERIAL.C                                                              *
 *                                                                         *
 *   Copyright (c) 1996-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Serial/Modem support for Worldgroup NT.                               *
 *                                                                         *
 *                                        - Ilya Minkin            5/17/96 *
 *                                                                         *
 ***************************************************************************/

#include <windows.h>
#include "gcomm.h"
#include "majorbbs.h"
#include "wgsmajor.h"

#define FILREV "$Revision: 11 $"

static VOID parseDevName(const CHAR *pDevName,CHAR *pDevPrefix,INT iDPSize,
                         INT *pDevNo);
static VOID srlCycle(VOID);
static VOID handleChannel(INT iChan);
static USHORT chanSend(struct datstm *pDatStm,CHAR *pBuffer,USHORT iCount);
static VOID chanResetHook(VOID);
static VOID initDevice(INT iChan);
static VOID resetChannel(INT iChan);
static VOID lowerDTR(INT iChan);
static VOID raiseDTR(INT iChan);
static INT receiveInput(INT iChan,CHAR *pBuffer,INT iCount);
static VOID blowoffInput(INT iChan);
static USHORT sendBlock(INT iChan,CHAR *pBuffer,ULONG iCount);
static INT updatePendingStatus(struct tagSerialInfo *pSI,CHAR *pBlock,ULONG iCount);

static GBOOL getDCD(INT iChan);
static VOID newConfigHook(INT iUserNo);

#define SRLOSZ           128       // size of srl output staging buffer
#define HRTDTRLOW        2000      // DTR low, miliseconds
#define HRTIDLE          100       // idle time after raise DTR miliseconds

#define NUMQBLOCKS       4         // number of out queue blocks

#define COMM_QUESIZE     4096      // size of In/Out comm queue

struct tagOutQBlock {              // output queue information
     OVERLAPPED ov;                //   overlapped structure
     ULONG nSent;                  //   number of bytes to send
     CHAR *pBlock;                 //   block to send
};

struct tagSerialInfo {             // device specific channel information
     struct bufstm outSink;        //   output sink (MUST BE FIRST FIELD)
     struct datstm *cdiDst;        //   BBS channel's input sink
     OVERLAPPED ovRead;            //   overlapped struct for nonblocking read
     ULONG lTime;                  //   for timing
     LONG lBaud;                   //   baud rate
     HANDLE hCom;                  //   handle for specific chan
     INT iChan;                    //   device specific chan (0..numchans-1)
     INT iUserNo;                  //   BBS user number (0..nterms-1)
     INT iState;                   //   see status codes below
     GBOOL fDCDOn;                 //   carrier detected
     GBOOL fDTROn;                 //   since Win95 driver is stupid we need to
                                   //   track DTR state
     CHAR szDevName[16];           //   device name (COM1 ... )
     CHAR szOutStg[SRLOSZ];        //   output staging buffer
     ULONG srlQSize;               //   size of serial driver output queue
     struct tagOutQBlock qBlock[NUMQBLOCKS];// serial output queue
};

                                   // state codes
#define SRL_RESET 0                //   reset, keeping DTR low for 2 sec
#define SRL_IDLE  1                //   idle, waiting additional .1 sec
#define SRL_READY 2                //   ready

struct tagSerialInfo *siInfo;      // array[numchans] of device info
static VOID (*oldrst)(VOID);       // old (*hdlrst)() vector
static VOID (*oldsys)(VOID);       // old (*syscyc)() vector

static struct bufstm tplosk={      // template for output sink bufstm
     { swbufs,                     //   return how much room sink has now
       hobufs,                     //   pass one byte to the sink
       chanSend,                   //   sink moves bytes (nactual is fedback)
       dmbufs,                     //   source reports bytes moved to snkloc
       NULL,                       //   snkwin output: where source can store
       0,                          //   snkwin output: maximum eventual room
       0                           //   DSTOVF=sink reports overflow
     },
     NULL,                         //   accumulation buffer (outstg)
     SRLOSZ,                       //   size of buffer
     0                             //   buffer count, 0..size-1
};

static INT iNumOfChan;             // total numer of serial channels
static GBOOL showAuditMsg;         // show extra Audit Trail messages for debug
static HANDLE hSerHeap;            // heap fo async. send

VOID
init_ntserial(VOID)                // serial support initialization
{
     struct tagSerialInfo *pSI;
     INT iChan;
     INT iGroup;
     INT iMsg;
     INT iUserNo;
     INT iUsersInGroup[NUMGRPS];
     INT iFirstUserNo[NUMGRPS];
     INT iDevNo[NUMGRPS];
     LONG lBaud[NUMGRPS];
     CHAR szDevPrefix[NUMGRPS][16];
     CHAR errMsg[256];

     memset(iFirstUserNo,0,sizeof(iFirstUserNo));
     memset(iUsersInGroup,0,sizeof(iUsersInGroup));
     memset(iDevNo,0,sizeof(iDevNo));
     memset(lBaud,0,sizeof(lBaud));
     memset(szDevPrefix,0,sizeof(szDevPrefix));
     setmbk(mjrmb);
     showAuditMsg=ynopt(SERDBG);
     for (iGroup=1,iMsg=0,iNumOfChan=0 ; iGroup < NUMGRPS ;
                                             iGroup++,iMsg+=GROUP2-GROUP1) {
          if (grtype[iGroup] != GTNONE) {
               iUsersInGroup[iGroup]=numopt(iMsg+NUMBR1,1,256);
          }
          iFirstUserNo[iGroup]=iFirstUserNo[iGroup-1]+iUsersInGroup[iGroup-1];
          if (grtype[iGroup] == GTMODEM
           || grtype[iGroup] == GTMLOCK
           || grtype[iGroup] == GTSERIAL) {
               parseDevName(getmsg(iMsg+DEVNAM1),szDevPrefix[iGroup],
                            sizeof(szDevPrefix[iGroup]),&iDevNo[iGroup]);
               if (sameas(szDevPrefix[iGroup],"NOHARD")) {  // non-hardware chan
                    iDevNo[iGroup]=-1;
               }
               else if (iDevNo[iGroup] == 0 || szDevPrefix[iGroup][0] == '\0') {
                    catastro("ERROR IN HARDWARE CONFIGURATION OPTION"
                             " \"DEVNAM%d\"",iGroup);
               }
               iNumOfChan+=iUsersInGroup[iGroup];
               lBaud[iGroup]=lngopt(iMsg+BAUD1,300L,230400L);
           }
     }
     if (iNumOfChan > 0) {
          hSerHeap=HeapCreate(HEAP_NO_SERIALIZE,iNumOfChan*1024,0);
          if (hSerHeap == NULL) {
               catastro("Unable to create heap for serial channels.  Error: %s",
                        getLastErrorText(errMsg,sizeof(errMsg)));
          }
          siInfo=alczer(iNumOfChan*sizeof(struct tagSerialInfo));
          for (iGroup=1,iChan=0 ; iGroup < NUMGRPS && iChan < iNumOfChan ;
                                                                     iGroup++) {
               if (iDevNo[iGroup] > 0) {
                    for (iUserNo=0 ; iUserNo < iUsersInGroup[iGroup] ;
                                                                    iUserNo++) {
                         pSI=&siInfo[iChan];
                         pSI->iChan=iChan;
                         pSI->iUserNo=iFirstUserNo[iGroup]+iUserNo;
                         sprintf(pSI->szDevName,"%s%d",szDevPrefix[iGroup],
                                                       iDevNo[iGroup]++);
                         pSI->lBaud=lBaud[iGroup];
                         movmem(&tplosk,&pSI->outSink,sizeof(struct bufstm));
                         pSI->outSink.buffer=pSI->szOutStg;
                         pSI->cdiDst=btucdi(pSI->iUserNo,&pSI->outSink.datstm);
                         if (pSI->cdiDst != NULL) {
                              btuset(pSI->iUserNo,RECNFG,(LONG)newConfigHook);
                              initDevice(iChan);
                              resetChannel(iChan);
                              iChan++;
                         }
                    }
               }
               else if (iDevNo[iGroup] < 0) {     // non-hardware chan
                    btuudf(iFirstUserNo[iGroup],iUsersInGroup[iGroup]);
               }
          }
          iNumOfChan=iChan;
          oldrst=hdlrst;
          hdlrst=chanResetHook;
          oldsys=syscyc;
          syscyc=srlCycle;
     }
}

static VOID
parseDevName(                      // parse device name into prefix & number
const CHAR *pDevName,              //   device name (i.e. COM2)
CHAR *pDevPrefix,                  //   buffer for prefix to return (COM)
INT iDPSize,                       //   buffer size
INT *pDevNo)                       //   number to return (2)
{
     *pDevNo=0;
     if (iDPSize < 1) {
          return;
     }
     for ( ; *pDevName != '\0' && iDPSize > 1 && IsCharAlpha(*pDevName) ;
                                                                 iDPSize--) {
          *pDevPrefix++=*pDevName++;
     }
     *pDevPrefix='\0';
     *pDevNo=atoi(pDevName);
}

static VOID
srlCycle(VOID)                     // system cycle hook
{
     INT iChan;

     for (iChan=0 ; iChan < iNumOfChan ; iChan++) {
          handleChannel(iChan);
     }
     (*oldsys)();
}

static VOID
handleChannel(                     // handle input and states
INT iChan)                         //   channel number
{
     GBOOL fDCDOn;
     struct datstm *pDatStm;
     INT iCount,iRead;
     struct tagSerialInfo *pSI;

     pSI=&siInfo[iChan];
     switch(pSI->iState) {
     case SRL_RESET:
          if (GetTickCount()-pSI->lTime > HRTDTRLOW) {
               raiseDTR(iChan);
               pSI->iState=SRL_IDLE;
               pSI->lTime=GetTickCount();
          }
          break;
     case SRL_IDLE:
          if (GetTickCount()-pSI->lTime > HRTIDLE) {
               pSI->iState=SRL_READY;
          }
          break;
     case SRL_READY:
          fDCDOn=getDCD(iChan);
          if (pSI->fDCDOn && !fDCDOn) {
               btuinj(pSI->iUserNo,LOST2C);
          }
          pSI->fDCDOn=fDCDOn;
          pDatStm=pSI->cdiDst;
          if (pDatStm != NULL) {
               iCount=pDatStm->snkwin(pDatStm);
               iRead=receiveInput(iChan,pDatStm->snkloc,iCount);
               pDatStm->didmov(pDatStm,iRead);
          }
          break;
     }
     if (pSI->outSink.bufcnt > 0) {
          pSI->outSink.datstm.didmov(&pSI->outSink.datstm,0);
     }
}

static USHORT                      //   returns actual number of bytes moved
chanSend(                          // device "moveit()" routine
struct datstm *pDatStm,            //   DataStream structure
CHAR *pBuffer,                     //   source location
USHORT iCount)                     //   number of bytes desired to move
{
     struct tagSerialInfo *pSI;

     pSI=(struct tagSerialInfo *)pDatStm;
     if (pSI->iState == SRL_READY) {
          return(sendBlock(pSI->iChan,pBuffer,iCount));
     }
     else {
          return(0);
     }
}

static VOID
chanResetHook(VOID)                // intercepting a channel reset
{
     INT iChan;
     struct tagSerialInfo *pSI;

     (*(VOID (*)(VOID))oldrst)();
     for (iChan=0 ; iChan < iNumOfChan ; iChan++) {
          pSI=&siInfo[iChan];
          if (pSI->iUserNo == usrnum) {
               movmem(&tplosk,&pSI->outSink,sizeof(struct bufstm));
               pSI->outSink.buffer=pSI->szOutStg;
               pSI->cdiDst=btucdi(pSI->iUserNo,&pSI->outSink.datstm);
               if (pSI->cdiDst != NULL) {
                    btuset(pSI->iUserNo,RECNFG,(LONG)newConfigHook);
                    resetChannel(iChan);
               }
               break;
          }
     }
}

// --- device specific routines follow ---

VOID
initDevice(                        // device specific hardware init
INT iChan)                         // device channel number, 0..numchans-1
{
     INT i;
     DCB dcb;
     CHAR szTmpName[16];
     CHAR szMsgText[256];
     COMMTIMEOUTS timeOut;
     COMMPROP commProp;
     struct tagSerialInfo *pSI;

     pSI=&siInfo[iChan];
     strcpy(szTmpName,"\\\\.\\");
     strcat(szTmpName,pSI->szDevName);
     if ((pSI->hCom=CreateFile(szTmpName,
                               GENERIC_READ|GENERIC_WRITE,
                               0,
                               NULL,
                               OPEN_EXISTING,
                               FILE_FLAG_OVERLAPPED,
                               NULL)) == INVALID_HANDLE_VALUE) {
          catastro("Unable to open %s error: %s",pSI->szDevName,
                   getLastErrorText(szMsgText,sizeof(szMsgText)));
     }
     if (!GetCommState(pSI->hCom,&dcb)) {
          catastro("Unable to get comm state %s error: %s",
                   pSI->szDevName,
                   getLastErrorText(szMsgText,sizeof(szMsgText)));
     }
     dcb.BaudRate=pSI->lBaud;
     dcb.ByteSize=8;
     dcb.Parity=NOPARITY;
     dcb.StopBits=ONESTOPBIT;
     dcb.fDtrControl=DTR_CONTROL_ENABLE;
     dcb.fOutxCtsFlow=1;
     dcb.fRtsControl=RTS_CONTROL_HANDSHAKE;
     dcb.fInX=dcb.fOutX=0;
     dcb.fBinary=TRUE ;
     dcb.fParity = TRUE ;
     if (!SetCommState(pSI->hCom,&dcb)) {
          catastro("Unable to set comm state %s error: %s",
                   pSI->szDevName,
                   getLastErrorText(szMsgText,sizeof(szMsgText)));
     }
     // make sure we are in sync with GSBL
     btuset(pSI->iUserNo,BAUDRT,pSI->lBaud);
     btuset(pSI->iUserNo,PARITY,PARNON);
     btuset(pSI->iUserNo,DATABT,DABEGT);
     btuset(pSI->iUserNo,STOPBT,STBONE);
     if (!GetCommTimeouts(pSI->hCom,&timeOut)) {
          catastro("Unable to get comm timeouts %s error: %s",
                   pSI->szDevName,
                   getLastErrorText(szMsgText,sizeof(szMsgText)));
     }
     else {
          timeOut.ReadIntervalTimeout=MAXDWORD;
          timeOut.ReadTotalTimeoutMultiplier=0;
          timeOut.ReadTotalTimeoutConstant=0;
          if (!SetCommTimeouts(pSI->hCom,&timeOut)) {
               catastro("Unable to set comm timeouts %s error: %s",
                        pSI->szDevName,
                        getLastErrorText(szMsgText,sizeof(szMsgText)));
          }
     }
     SetupComm(pSI->hCom,COMM_QUESIZE*2,COMM_QUESIZE);
     GetCommProperties(pSI->hCom,&commProp);
     pSI->srlQSize=commProp.dwCurrentTxQueue;
     for (i=0 ; i < NUMQBLOCKS ; i++) {
          pSI->qBlock[i].ov.hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);
     }
}

VOID
resetChannel(                      // device specific channel reset
INT iChan)                         // device channel number, 0..numchans-1
{
     struct tagSerialInfo *pSI;

     pSI=&siInfo[iChan];
     btupcc(pSI->iUserNo,CDIQAP);
     pSI->iState=SRL_RESET;
     pSI->fDCDOn=FALSE;
     pSI->lTime=GetTickCount();
     blowoffInput(iChan);
     lowerDTR(iChan);
}

static VOID
lowerDTR(                          // device specific lowering of DTR
INT iChan)                         // device channel number, 0..numchans-1
{
     siInfo[iChan].fDTROn=FALSE;
     EscapeCommFunction(siInfo[iChan].hCom,CLRDTR);
}

static VOID
raiseDTR(                          // device specific raising of DTR
INT iChan)                         // device channel number, 0..numchans-1
{
     siInfo[iChan].fDTROn=TRUE;
     EscapeCommFunction(siInfo[iChan].hCom,SETDTR);
}

static INT                         //   returns actual number of bytes read
receiveInput(                      // device specific getting input data
INT iChan,                         //   device channel number, 0..numchans-1
CHAR *pBuffer,                     //   where to put the bytes
INT iCount)                        //   how much room there is for bytes
{
     DWORD dwRead;
     CHAR szMsgBuf[64];
     CHAR szExtMsgBuf[80];
     COMSTAT cs;
     DWORD dwErr;

     ClearCommError(siInfo[iChan].hCom,&dwErr,&cs);
     if (dwErr != 0) {
          if (showAuditMsg) {
               sprintf(szMsgBuf,"Comm error %X on %s",dwErr,
                                                      siInfo[iChan].szDevName);
               shocst(szMsgBuf,"");
          }
     }
     if (ReadFile(siInfo[iChan].hCom,pBuffer,iCount,&dwRead,&siInfo[iChan].ovRead)) {
          return((INT)dwRead);
     }
     if (showAuditMsg) {
          sprintf(szMsgBuf,"Read failed on %s",siInfo[iChan].szDevName);
          shocst(szMsgBuf,getLastErrorText(szExtMsgBuf,sizeof(szExtMsgBuf)));
     }
     return(0);
}

static VOID
blowoffInput(                      // device specific throwing input away
INT iChan)                         // device channel number, 0..numchans-1
{
     CHAR szMsgBuf[64];
     CHAR szExtMsgBuf[80];

     if (!PurgeComm(siInfo[iChan].hCom,PURGE_RXABORT|PURGE_TXABORT|PURGE_RXCLEAR|PURGE_TXCLEAR)) {
          if (showAuditMsg) {
               sprintf(szMsgBuf,"PurgeComm failed on %s",siInfo[iChan].szDevName);
               shocst(szMsgBuf,getLastErrorText(szExtMsgBuf,sizeof(szExtMsgBuf)));
          }
     }
}

static USHORT                      //   returns actual number sent
sendBlock(                         // device specific sending binary data
INT iChan,                         //   device channel number, 0..numchans-1
CHAR *pBlock,                      //   a block of bytes
ULONG iCount)                      //   number of bytes to send
{
     DWORD dwWritten=0;
     COMSTAT cs;
     DWORD dwErr;
     CHAR szMsgBuf[64];
     CHAR szExtMsgBuf[80];
     struct tagSerialInfo *pSI;
     INT idx;

     pSI=&siInfo[iChan];
     ClearCommError(pSI->hCom,&dwErr,&cs);
     if (dwErr != 0) {
          if (showAuditMsg) {
               sprintf(szMsgBuf,"Comm error %X on %s",dwErr,pSI->szDevName);
               shocst(szMsgBuf,"");
          }
     }
     // if queue size is available from the driver and output is bigger than
     // queue size limit send to queue size
     // that is not needed for NT but required for Win95
     // for some strange reason Win95 truncates blocks greater than queue size
     if (pSI->srlQSize > 0 && iCount > pSI->srlQSize) {
          iCount=pSI->srlQSize;
     }
     if ((idx=updatePendingStatus(pSI,pBlock,iCount)) >= 0) { // there is room in qBlock struct
          pSI->qBlock[idx].nSent=iCount;
          if (!WriteFile(pSI->hCom,pSI->qBlock[idx].pBlock,iCount,&dwWritten,&pSI->qBlock[idx].ov)) {
               if (GetLastError() == ERROR_IO_PENDING) {
                    dwWritten=iCount;
               }
               else {
                    if (showAuditMsg) {
                         sprintf(szMsgBuf,"Write failed on %s",pSI->szDevName);
                         shocst(szMsgBuf,
                                getLastErrorText(szExtMsgBuf,sizeof(szExtMsgBuf)));
                    }
               }
          }
     }
     return((USHORT)dwWritten);
}

static INT
updatePendingStatus(
struct tagSerialInfo *pSI,
CHAR *pBlock,                      //   a block of bytes
ULONG iCount)                      //   number of bytes to send
{
     INT i;
     DWORD nBytes;
     DWORD blockSize;
     ULONG minAllocSize;
     CHAR szMsgBuf[64];
     CHAR szExtMsgBuf[80];

     minAllocSize=max(iCount,128);
     for (i=0 ; i < NUMQBLOCKS ; i++) {
          if (pSI->qBlock[i].pBlock == NULL) {
               pSI->qBlock[i].pBlock=HeapAlloc(hSerHeap,0,minAllocSize);
               if (pSI->qBlock[i].pBlock != NULL) {
                    memmove(pSI->qBlock[i].pBlock,pBlock,iCount);
                    return(i);
               }
               else {
                    return(-1);
               }
          }
          else if (GetOverlappedResult(pSI->hCom,&pSI->qBlock[i].ov,&nBytes,FALSE)
           || GetLastError() != ERROR_IO_INCOMPLETE) {
               if (pSI->qBlock[i].nSent != nBytes) {
                    if (showAuditMsg) {
                         sprintf(szMsgBuf,"SEND failed on %s",pSI->szDevName);
                         sprintf(szExtMsgBuf,"Transmitted %d of %d, queue size %d",
                                 nBytes,pSI->qBlock[i].nSent,pSI->srlQSize);
                         shocst(szMsgBuf,szExtMsgBuf);
                    }
               }
               blockSize=HeapSize(hSerHeap,0,pSI->qBlock[i].pBlock);
               if (iCount > blockSize || minAllocSize < blockSize/2) {
                    if ((pSI->qBlock[i].pBlock=
                     HeapReAlloc(hSerHeap,0,pSI->qBlock[i].pBlock,minAllocSize))
                                                                      != NULL) {
                         memmove(pSI->qBlock[i].pBlock,pBlock,iCount);
                         return(i);
                    }
                    else {
                         HeapFree(hSerHeap,0,pSI->qBlock[i].pBlock);
                         pSI->qBlock[i].pBlock=NULL;
                         return(-1);
                    }
               }
               else {
                    memmove(pSI->qBlock[i].pBlock,pBlock,iCount);
                    return(i);
               }
          }
     }
     return(-1);
}

static GBOOL                       //   returns TRUE DCD on
getDCD(                            // device specific detecting DCD drop
INT iChan)                         //   device channel number
{

     DWORD dwMask=0;

     if (GetCommModemStatus(siInfo[iChan].hCom,&dwMask)
      && (dwMask&MS_RLSD_ON) != 0) {
          return(TRUE);
     }
     return(FALSE);
}

static VOID
newConfigHook(                // sets new praram after btuxxx() was called
INT iUserNo)                  //   channel number
{
     DCB dcb;
     INT iChan;
     LONG lVal;
     struct tagSerialInfo *pSI;

     for (iChan=0 ; iChan < iNumOfChan ; iChan++) {
          pSI=&siInfo[iChan];
          if (pSI->iUserNo == iUserNo) {
               GetCommState(pSI->hCom,&dcb);
               dcb.BaudRate=(DWORD)bturep(pSI->iUserNo,BAUDRT);
               lVal=bturep(pSI->iUserNo,PARITY);
               if (lVal == PAREVN) {
                    dcb.Parity=2;
               }
               else if (lVal == PARODD) {
                    dcb.Parity=1;
               }
               else {
                    dcb.Parity=0;
               }
               if (bturep(pSI->iUserNo,DATABT) == DABSVN) {
                    dcb.ByteSize=7;
               }
               else {
                    dcb.ByteSize=8;
               }
               if (bturep(pSI->iUserNo,STOPBT) == STBTWO) {
                    dcb.StopBits=2;
               }
               else {
                    dcb.StopBits=0;
               }
               pSI->lBaud=(LONG)dcb.BaudRate;
               dcb.fDtrControl=(pSI->fDTROn) ? DTR_CONTROL_ENABLE :
                                               DTR_CONTROL_DISABLE;
               SetCommState(pSI->hCom,&dcb);
               break;
          }
     }
}
