/*****************************************************************************
 * Copyright (C) Cinterion Wireless Modules GmbH
 *****************************************************************************
 * Project: CE-MUX-Driver
 *****************************************************************************
 * Author : Siemens AG Berlin COM WM RD SW1
 *****************************************************************************
 * $Workfile: O:/ctna0450_RIL/z_ms-d/src/working/Classic/MuxDrv/MuxChannel.cpp $
 * $Revision: /main/ms-d/48 $
 * $Date: 2009-12-08 14:52:01 $
 * $Author: ctna0450 $
 *****************************************************************************
 * Target:
 *  Microsoft Windows CE 4.2 or 5.0
 *****************************************************************************
 * Description:
 *  Cinterion Multiplex Driver virtual mux port class CMuxChannel implementation
 *****************************************************************************
 *
 * $Log: O:/ctna0450_RIL/z_ms-d/src/working/Classic/MuxDrv/MuxChannel.cpp$ 
 * 
 * /main/ms-d/48   2009-12-08 14:52:01   ctna0450 
 * Added trace zone for virtual modem signals
 *
 * /main/ms-d/47   2009-02-18 11:58:10   ctna0256
 * Set copyright text
 *
 * /main/ms-d/46   2009-01-13 14:22:09   ctna0450
 * Make mux port instancable to properly support handling of multiple WaitCommEvent() calls
 *
 * /main/ms-d/45   2008-12-02 13:47:32   ctna0450
 * Fixed problem with CeAllocAsynchronousBuffer() - 1st untested try
 *
 * /main/ms-d/44   2008-08-20 11:38:27   ctna0450
 * Prepared for new disclaimer handling
 *
 * /main/ms-d/43   2008-06-04 11:06:27   wm36264
 * Fixed problem with parsing of escape sequence
 *
 * /main/ms-d/42   2008-01-16 14:51:48   kr36712
 * correction for critical suspend
 *
 * /main/ms-d/41   2007-10-19 14:53:35   ht37010
 * added CE6 support (buffer management)
 *
 * /main/ms-d/40   2007-07-04 15:26:12   kr36712
 * acceleration of the port close if mux channel not connected
 *
 * /main/ms-d/39   2007-06-11 17:53:19   kr36712
 * correction:
 * ParseEscape()
 * Dlci_EmptyQueues() for protocol version 4
 * AddWaitEvent()
 *
 * /main/ms-d/38   2007-06-07 17:53:09   kr36712
 * changes for DlciOpen():
 * pepetition of the DlciOpen(), if the answer is lost from the module on
 * the serial interface.
 *
 * /main/ms-d/37   2006-08-23 17:15:05   wm36264
 * Added modifications necessary for new HDLC framing (Mux Step 2)
 *
 * /main/ms-d/36   2006-06-16 12:10:32   wm36264
 * Intruduced utility functions for active device registry access
 *
 * /main/ms-d/35   2006-05-18 13:33:02   wm36264
 * Fixed all useful lint warnings
 *
 * /main/ms-d/34   2006-05-16 15:51:08   wm36264
 * Simplyfied EV_TXEMPTY change
 *
 * /main/ms-d/33   2006-05-16 14:01:18   wm36264
 * Added support for EV_TXEMPTY com event
 *
 * /main/ms-d/32   2006-05-10 17:49:24   wm36264
 * Avoid spinning application threads after module loss
 *
 * /main/ms-d/31   2006-05-10 15:23:07   wm36264
 * Corrected handling of virtual control signals in case of flow control
 *
 * /main/ms-d/30   2006-05-10 10:21:39   wm36264
 * Consider RTS and DTR when handshake mechanism is changed
 *
 * /main/ms-d/29   2006-04-19 17:28:04   wm36264
 * Shifted some includes
 *
 * /main/ms-d/27   2006-03-03 13:42:57   wm36264
 * Fixed deadlock in case of unexpected module shutdown
 *
 * /main/ms-d/26   2006-03-01 13:46:36   wm36264
 * Fixed bug preventing a reopen of the virtual ports
 *
 * /main/ms-d/25   2006-02-24 19:36:50   wm36264
 * Fixed possible deadlock in MuxReceiveV24Status()
 *
 * /main/ms-d/24   2006-02-24 15:28:50   wm36264
 * Updated read timeout documentation
 *
 * /main/ms-d/23   2006-02-13 12:17:36   wm36264
 * Corrected handling of internal DCB structure
 *
 * /main/ms-d/22   2006-01-20 13:03:35   wm36264
 * Consider fForce parameter in Close() properly
 *
 * /main/ms-d/20   2005-12-21 18:24:32   wm36264
 * Fixed problem with write timeout
 *
 * /main/ms-d/19   2005-11-30 16:08:30   wm36264
 * Fixed sync problem with signaling of com event EV_RXCHAR
 *
 * /main/ms-d/18   2005-11-29 19:13:23   wm36264
 * Fixed exception caused by overlapping CloseHandle() and ReadFile() or WriteFile() calls on virtual ports
 *
 * /main/ms-d/17   2005-11-29 10:53:04   wm36264
 * Use "DTR_CONTROL_ENABLE" instaed of "DTR_CONTROL_DISABLE"
 *
 * /main/ms-d/16   2005-11-28 11:30:02   wm36264
 * Improved unexpected mux shutdown handling
 *
 * /main/ms-d/15   2005-11-24 16:42:52   wm36264
 * Properly handle unexpected mux shutdown
 *
 * /main/ms-d/14   2005-11-23 12:08:54   wm36264
 * Fixed inconsistency with virtual modem signals
 *
 * /main/ms-d/13   2005-11-09 10:59:19   wm36264
 * Switched to new instancable mux interface
 *
 * /main/ms-d/12   2005-10-28 13:15:09   WM36264
 * Fixed some return values, replaced some usages of "SYNCBLOCK()"
 *
 * /main/ms-d/11   2005-10-28 14:06:01   kr36712
 * handle permission for the application pointer
 *
 * /main/ms-d/10   2005-10-24 14:22:01   WM36264
 * Added file descriptions
 *
 * /main/ms-d/9   2005-10-23 14:48:07   WM36264
 * Removed obsolete pointer checks
 *
 * /main/ms-d/8   2005-10-20 13:12:27   WM36264
 * Added proper copyright header
 *
 * /main/ms-d/7   2005-10-20 12:28:55   WM36264
 * Added evaluation of now functionable OutFlowActive flag again
 *
 * /main/ms-d/6   2005-10-19 11:26:15   kr36712
 * MuxReceiveV24Status() changed
 * - flag OutFlowActive has no function (F.S.)
 * - DCD event (EV_RLSD) added
 *
 * /main/ms-d/5   2005-10-13 17:24:35   wm36264
 * Fixed bug in Close()
 *
 * /main/ms-d/4   2005-10-13 16:27:40   wm36264
 * Allow virtual port to be opened more than once
 *
 * /main/ms-d/3   2005-10-13 12:14:12   wm36264
 * Fixed some problems with "+++" detection
 *
 * /main/ms-d/2   2005-10-11 15:43:27   wm36264
 * Fixed some issues for multiple mux driver instances
 *
 * /main/ms-d/1   2005-10-07 16:29:52   wm36264
 * First version of Windows CE Muxer
 *
*/

//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Cinterion Wireless Modules GmbH
// Copyright (C) 2005-2008 Siemens AG
//
// Version: 1.0
// Date:    dummy
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// MuxChannel.cpp
//
// This file contains the implementation of the class "CMuxChannel" providing
// the functionality of the virtual COM ports.
//
//////////////////////////////////////////////////////////////////////////////

#include "precomp.h"

//////////////////////////////////////////////////////////////////////////////
//
// Public interface
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
// Parameters:
// szRegKey: The key of the drivers registry section.
//
//////////////////////////////////////////////////////////////////////////////
CMuxChannel::CMuxChannel(LPCTSTR szRegKey) {
  InitializeCriticalSection(&m_csLock);
  InitializeCriticalSection(&m_csLockDcb);
  InitializeCriticalSection(&m_csLockRead);
  InitializeCriticalSection(&m_csLockModuleState);

  memset(m_szMuxInstance, 0 , sizeof(m_szMuxInstance));
  memset(&m_ModuleState, 0, sizeof(MUX_V24STATUS_t));
  m_dwOpenCount = 0;
  m_dwDlci = (DWORD)-1;
  m_dwTimeLastChar = 0;
  m_dwTimeFirstPlus = 0;
  m_dwPlusCnt = 0;
  m_dwMuxInstance = 0;
  m_dwReadLen = 0;
  m_dwRead = 0;
  m_dwReadPending = 0;
  m_dwCurrentPermissions = 0;
  m_dwStartStopResult = 0;
  m_pReadBuffer= NULL;
  m_hDetectEscapeThread = NULL;
  m_hCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
  m_hStartStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  m_hReadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  m_hWriteContinueEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  m_hStopEscapeThreadEvent = CreateEvent(NULL, TRUE, TRUE, NULL);

  // Init our internal DCB structure with useful values
  memset(&m_Dcb, 0, sizeof(m_Dcb));
  m_Dcb.DCBlength                         = sizeof(DCB);
  m_Dcb.BaudRate                          = CBR_115200;
  m_Dcb.fBinary                           = TRUE;
  m_Dcb.fParity                           = TRUE;
  m_Dcb.fOutxCtsFlow                      = TRUE;
  m_Dcb.fDtrControl                       = DTR_CONTROL_ENABLE;
  m_Dcb.fTXContinueOnXoff                 = TRUE;
  m_Dcb.fRtsControl                       = RTS_CONTROL_HANDSHAKE;
  m_Dcb.ByteSize                          = 8;
  m_Dcb.Parity                            = NOPARITY;
  m_Dcb.StopBits                          = ONESTOPBIT;

  // Init our internal COMMTIMEOUTS structure with useful values
  memset(&m_Timeouts, 0, sizeof(m_Timeouts));
  m_Timeouts.ReadIntervalTimeout          = MAXDWORD;
  m_Timeouts.WriteTotalTimeoutConstant    = 3000;

  // Load settings from registry
  if (szRegKey) {
    // First get the mux driver instance to be used
    if (!RegGetDeviceString(szRegKey, REGVAL_VCOM_MUXINSTANCE, m_szMuxInstance, sizeof(m_szMuxInstance))) {
      _tcsncpy(m_szMuxInstance, TEXT("MPX1:"), sizeof(m_szMuxInstance) / sizeof(TCHAR));
    }
    // Now get the mux channel number
    if (!RegGetDeviceDWORD(szRegKey, REGVAL_VCOM_DLCI, &m_dwDlci)) {
      // We use the port index as DLCI if DLCI isn't given
      RegGetDeviceDWORD(szRegKey, TEXT("Index"), &m_dwDlci);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor
//
//////////////////////////////////////////////////////////////////////////////
CMuxChannel::~CMuxChannel() {
  Close(0);
  CloseHandle(m_hStopEscapeThreadEvent);
  CloseHandle(m_hWriteContinueEvent);
  CloseHandle(m_hReadEvent);
  CloseHandle(m_hStartStopEvent);
  CloseHandle(m_hCloseEvent);
  DeleteCriticalSection(&m_csLockModuleState);
  DeleteCriticalSection(&m_csLockRead);
  DeleteCriticalSection(&m_csLockDcb);
  DeleteCriticalSection(&m_csLock);
}

//////////////////////////////////////////////////////////////////////////////
//
// Open a virtual port device.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::Open(LPVOID pInstance) {
DWORD   dwReceived;
HANDLE  hMux;

  SYNCBLOCK(m_csLock);

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::Open() m_dwDlci = %d, instance count %d\r\n"), m_dwDlci, m_dwOpenCount + 1));

  // Check if we are not already open
  if (m_dwOpenCount) {
    m_dwOpenCount++;
    m_WaitCommEvent.AddInstance(pInstance);
    return TRUE;
  }

  // Init necessary data
  m_dwMuxInstance = 0;
  m_dwPlusCnt = 0;
  m_dwReadLen = 0;
  m_dwRead = 0;
  m_pReadBuffer = NULL;
  m_dwReadPending = 0;
  hMux = CreateFile(m_szMuxInstance, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
  if (hMux == INVALID_HANDLE_VALUE) {
    return FALSE;
  }

  // Check necessary resources
  if (!m_hCloseEvent) return FALSE;
  if (!m_hStartStopEvent) return FALSE;
  if (!m_hReadEvent) return FALSE;
  if (!m_hWriteContinueEvent) return FALSE;
  if (!m_hStopEscapeThreadEvent) return FALSE;

  // Register opened port
  if (!DeviceIoControl(hMux, IOCTL_MPX_ADD_VCOM, &m_dwDlci, sizeof(DWORD), &m_dwMuxInstance, sizeof(DWORD), &dwReceived, NULL)) {
    CloseHandle(hMux);
    return FALSE;
  }

  // Check received mux instance handle
  if (!m_dwMuxInstance || (dwReceived != sizeof(DWORD))) {
    CloseHandle(hMux);
    return FALSE;
  }

  memset(&m_ModuleState, 0, sizeof(MUX_V24STATUS_t));
  if (m_Dcb.fRtsControl != RTS_CONTROL_DISABLE) m_ModuleState.RTS = 1;
  if (m_Dcb.fDtrControl != DTR_CONTROL_DISABLE) m_ModuleState.DTR = 1;
  m_ModuleState.TxEmpty = 1;
  MUX_V24STATUS_t ModuleState = m_ModuleState;

  // Now open the virtual mux channel
  if (!DlciOpen()) {
    DeviceIoControl(hMux, IOCTL_MPX_REMOVE_VCOM, NULL, 0, NULL, 0, &dwReceived, NULL);
    CloseHandle(hMux);
    return FALSE;
  }

  // Init our virtual modem signals
  if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
    DlciClose();
    DeviceIoControl(hMux, IOCTL_MPX_REMOVE_VCOM, NULL, 0, NULL, 0, &dwReceived, NULL);
    CloseHandle(hMux);
    return FALSE;
  }

  CloseHandle(hMux);

  m_WaitCommEvent.AddInstance(pInstance);

  m_dwOpenCount++;

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close a virtual port device.
//
// Parameters:
// fForce: Force resources to be freed.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::Close(LPVOID pInstance) {
DWORD   dwReceived;
HANDLE  hMux;
BOOL    fRet = TRUE;

  SYNCBLOCK(m_csLock);

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::Close() m_dwDlci = %d\r\n"), m_dwDlci));

  // Check if we are really open
  if (!m_dwOpenCount) return FALSE;

  // Release pending WaitCommEvent() calls
  m_WaitCommEvent.RemoveInstance(pInstance);

  // Tidy up only if the last instance is closed
  if ((m_dwOpenCount == 1) || !pInstance) {
    hMux = CreateFile(m_szMuxInstance, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hMux == INVALID_HANDLE_VALUE) {
      return FALSE;
    }

    // Close the virtual mux channel
    if (!DlciClose()) {
      fRet = FALSE;
    }

    // Unregister opened port
    if (!DeviceIoControl(hMux, IOCTL_MPX_REMOVE_VCOM, NULL, 0, NULL, 0, &dwReceived, NULL)) {
      fRet = FALSE;
    }

    m_dwMuxInstance = 0;
    m_dwOpenCount = 0;

    CloseHandle(hMux);

    return fRet;
  }

  m_dwOpenCount--;

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Copy the internal used DCB structure into an external buffer.
//
// Parameters:
// pDst: The buffer where we should copy our DCB structure to.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetDcb(DCB* pDst) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  SYNCBLOCK2(m_csLockDcb);
  memcpy(pDst, &m_Dcb, sizeof(DCB));
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Copy an external DCB structure into the internal buffer and use the given
// values.
//
// Parameters:
// pDst: The buffer where we should copy the DCB structure from.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::SetDcb(DCB* pSrc) {
MUX_V24STATUS_t  ModuleState;
BOOL             fSendState = FALSE;

  SYNCBLOCK(m_csLock);

  if (!IsMuxRunning()) return FALSE;

  EnterCriticalSection(&m_csLockDcb);
  EnterCriticalSection(&m_csLockModuleState);

  memcpy(&m_Dcb, pSrc, sizeof(DCB));

  ModuleState = m_ModuleState;

  switch(m_Dcb.fDtrControl) {
    case DTR_CONTROL_DISABLE:
      m_ModuleState.DTR = 0;
      break;
    case DTR_CONTROL_ENABLE:
      m_ModuleState.DTR = 1;
      break;
    case DTR_CONTROL_HANDSHAKE:
      if (m_ModuleState.InFlowActive) {
        m_ModuleState.DTR = 0;
      } else {
        m_ModuleState.DTR = 1;
      }
      break;
  }

  switch(m_Dcb.fRtsControl) {
    case RTS_CONTROL_DISABLE:
      m_ModuleState.RTS = 0;
      break;
    case RTS_CONTROL_ENABLE:
      m_ModuleState.RTS = 1;
      break;
    case RTS_CONTROL_HANDSHAKE:
      if (m_ModuleState.InFlowActive) {
        m_ModuleState.RTS = 0;
      } else {
        m_ModuleState.RTS = 1;
      }
      break;
  }

  if (memcmp(&ModuleState, &m_ModuleState, sizeof(MUX_V24STATUS_t))) {
    ModuleState = m_ModuleState;
    fSendState = TRUE;
  }

  LeaveCriticalSection(&m_csLockModuleState);
  LeaveCriticalSection(&m_csLockDcb);

  if (fSendState) {
    if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
      return FALSE;
    }
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Copy the internal used COMMTIMEOUTS structure into an external buffer.
//
// Parameters:
// pDst: The buffer where we should copy our DCB structure to.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetTimeouts(COMMTIMEOUTS* pDst) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  memcpy(pDst, &m_Timeouts, sizeof(COMMTIMEOUTS));
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Copy an external COMMTIMEOUTS structure into the internal buffer and use the given
// values.
//
// Parameters:
// pDst: The buffer where we should copy the DCB structure from.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::SetTimeouts(COMMTIMEOUTS* pSrc) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  memcpy(&m_Timeouts, pSrc, sizeof(COMMTIMEOUTS));
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the current WaitCommEvent() mask.
//
// Parameters:
// pdwWaitMask: The buffer receiving the current wait mask.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetWaitMask(LPVOID pInstance, PDWORD pdwWaitMask) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  return m_WaitCommEvent.GetWaitMask(pInstance, pdwWaitMask);
}

//////////////////////////////////////////////////////////////////////////////
//
// Set the WaitCommEvent() mask. The masked is ored with the existing mask to
// properly handle consecutive calls. If the mask is set to zero all pending
// WaitCommEvent() calls are released.
//
// Parameters:
// dwWaitMask: The new wait mask.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::SetWaitMask(LPVOID pInstance, DWORD dwWaitMask) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) {
    // Release pending WaitCommEvent() calls in any case
    m_WaitCommEvent.SetWaitMask(pInstance, 0);
    return FALSE;
  }
  return m_WaitCommEvent.SetWaitMask(pInstance, dwWaitMask);
}

//////////////////////////////////////////////////////////////////////////////
//
// Wait for COM events fitting to the COM mask. The function blocks until
// COM event occured or wait mask is set to zero.
//
// Parameters:
// pdwWaitEvent: The COM event caused the release of the function call.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::WaitOnMask(LPVOID pInstance, PDWORD pdwWaitEvent) {
  EnterCriticalSection(&m_csLock);
  if (!IsMuxRunning()) {
    LeaveCriticalSection(&m_csLock);
    Sleep(0);
    return FALSE;
  }
  LeaveCriticalSection(&m_csLock);
  return m_WaitCommEvent.WaitForEvent(pInstance, pdwWaitEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the properties of the virtual port.
//
// Parameters:
// pDst: The buffer where we should copy our COMMPROP structure to.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetProperties(COMMPROP* pDst) {
DWORD dwInSize;
DWORD dwOutSize;

  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;

  if (Dlci_GetQueueInformation(m_dwMuxInstance, m_dwDlci, NULL, NULL, &dwInSize, &dwOutSize)) {
    return FALSE;
  }

  memset(pDst, 0, sizeof(COMMPROP));

  pDst->wPacketLength        = 0xffff;
  pDst->wPacketVersion       = 0xffff;
  pDst->dwServiceMask        = SP_SERIALCOMM;
  pDst->dwMaxTxQueue         = dwOutSize;
  pDst->dwMaxRxQueue         = dwInSize;
  pDst->dwCurrentTxQueue     = dwOutSize;
  pDst->dwCurrentRxQueue     = dwInSize;
  pDst->dwMaxBaud            = BAUD_USER;
  pDst->dwProvSubType        = PST_RS232;
  pDst->dwProvCapabilities   = PCF_DTRDSR | PCF_RTSCTS | PCF_RLSD | PCF_TOTALTIMEOUTS | PCF_INTTIMEOUTS;
  pDst->dwSettableParams     = SP_HANDSHAKING | SP_RLSD;
  pDst->dwSettableBaud       = BAUD_USER;
  pDst->wSettableData        = DATABITS_8;
  pDst->wSettableStopParity  = STOPBITS_10 | PARITY_NONE;

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the virtual port status information.
//
// Parameters:
// pDst: The buffer where we should copy our COMSTAT structure to.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetComStat(COMSTAT* pDst) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  memset(pDst, 0, sizeof(COMSTAT));
  if (Dlci_GetQueueInformation(m_dwMuxInstance, m_dwDlci, &(pDst->cbInQue), &(pDst->cbOutQue), NULL, NULL)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Set the virtual RTS signal to low.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::ClearRts(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  EnterCriticalSection(&m_csLockDcb);
  if (m_Dcb.fRtsControl == RTS_CONTROL_HANDSHAKE) {
    LeaveCriticalSection(&m_csLockDcb);
    return FALSE;
  }
  EnterCriticalSection(&m_csLockModuleState);
  m_ModuleState.RTS = 0;
  MUX_V24STATUS_t ModuleState = m_ModuleState;
  LeaveCriticalSection(&m_csLockModuleState);
  LeaveCriticalSection(&m_csLockDcb);
  if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Set the virtual RTS signal to high.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::SetRts(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  EnterCriticalSection(&m_csLockDcb);
  if (m_Dcb.fRtsControl == RTS_CONTROL_HANDSHAKE) {
    LeaveCriticalSection(&m_csLockDcb);
    return FALSE;
  }
  EnterCriticalSection(&m_csLockModuleState);
  m_ModuleState.RTS = 1;
  MUX_V24STATUS_t ModuleState = m_ModuleState;
  LeaveCriticalSection(&m_csLockModuleState);
  LeaveCriticalSection(&m_csLockDcb);
  if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Set the virtual DTR signal to low.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::ClearDtr(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  EnterCriticalSection(&m_csLockDcb);
  if (m_Dcb.fDtrControl == DTR_CONTROL_HANDSHAKE) {
    LeaveCriticalSection(&m_csLockDcb);
    return FALSE;
  }
  EnterCriticalSection(&m_csLockModuleState);
  m_ModuleState.DTR = 0;
  MUX_V24STATUS_t ModuleState = m_ModuleState;
  LeaveCriticalSection(&m_csLockModuleState);
  LeaveCriticalSection(&m_csLockDcb);
  if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Set the virtual DTR signal to high.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::SetDtr(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  EnterCriticalSection(&m_csLockDcb);
  if (m_Dcb.fDtrControl == DTR_CONTROL_HANDSHAKE) {
    LeaveCriticalSection(&m_csLockDcb);
    return FALSE;
  }
  EnterCriticalSection(&m_csLockModuleState);
  m_ModuleState.DTR = 1;
  MUX_V24STATUS_t ModuleState = m_ModuleState;
  LeaveCriticalSection(&m_csLockModuleState);
  LeaveCriticalSection(&m_csLockDcb);
  if (Dlci_SetV24Status(m_dwMuxInstance, m_dwDlci, ModuleState)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Get the current modem state with the modem control signals.
//
// Parameters:
// pdwState: The buffer receiving the current modem state.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::GetModemState(PDWORD pdwState) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  SYNCBLOCK2(m_csLockModuleState);
  *pdwState = 0;
  if (m_ModuleState.CTS)  *pdwState |= MS_CTS_ON;
  if (m_ModuleState.DSR)  *pdwState |= MS_DSR_ON;
  if (m_ModuleState.RI)   *pdwState |= MS_RING_ON;
  if (m_ModuleState.DCD)  *pdwState |= MS_RLSD_ON;
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Clear all unsend data from the send buffer.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::ClearSendBuffer(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  if (Dlci_EmptyQueues(m_dwMuxInstance, m_dwDlci, FALSE, TRUE)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Clear all unread data from the receive buffer.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::ClearReceiveBuffer(void) {
  SYNCBLOCK(m_csLock);
  if (!IsMuxRunning()) return FALSE;
  if (Dlci_EmptyQueues(m_dwMuxInstance, m_dwDlci, TRUE, FALSE)) {
    return FALSE;
  }
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Read data from the virtual mux port.
//
// Parameters:
// pData: The buffer receiving the data.
// dwLen: The max. number of bytes to read.
//
// Return:
// The number of actually read bytes, -1 indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CMuxChannel::ReadData(PBYTE pData, DWORD dwLen) {
COMMTIMEOUTS  Timeouts;
HANDLE        hEvents[2];
BOOL          fReturnAfterAnyByte;
DWORD         dwWaitTime;
DWORD         dwStartTime;
DWORD         dwTotalTimeout;
DWORD         dwWaitRet;
DWORD         dwRead = 0;

  EnterCriticalSection(&m_csLock);
  if (!IsMuxRunning()) {
    LeaveCriticalSection(&m_csLock);
    Sleep(0);
    return (DWORD)-1;
  }

  hEvents[0] = m_hReadEvent;
  hEvents[1] = m_hCloseEvent;

  // Update the internal read data members
  EnterCriticalSection(&m_csLockRead);
  ResetEvent(m_hReadEvent);
  m_dwReadLen = dwLen;
  m_dwRead = 0;
#if (WINCEOSVER >= 600)
  PVOID   pAsyncBuffer = NULL;
  DWORD   dwArgDesc = ARG_O_PTR;
  HRESULT res = CeAllocAsynchronousBuffer(&pAsyncBuffer, (PVOID)pData, dwLen, dwArgDesc);
  if (FAILED(res)) {
    switch (res) {
      case E_INVALIDARG:
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::ReadData() - create async buffer failed: INVALIDARG\r\n")));
        break;
      case E_ACCESSDENIED:
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::ReadData() - create async buffer failed: ACCESSDENIED\r\n")));
        break;
      case E_OUTOFMEMORY:
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::ReadData() - create async buffer failed: OUTOFMEMORY\r\n")));
        break;
      default:
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CMuxChannel::ReadData() - create async buffer failed\r\n")));
        break;
    }
    LeaveCriticalSection(&m_csLockRead);
    LeaveCriticalSection(&m_csLock);
    return (DWORD)-1;
  }
  m_pReadBuffer = (PBYTE)pAsyncBuffer;
#else //WINCE6000
  m_pReadBuffer = pData;
  m_dwCurrentPermissions = GetCurrentPermissions();
#endif // WINCE600

  // Calculate the timeout setting
  GetTimeouts(&Timeouts);
  dwStartTime = GetTickCount();
  dwTotalTimeout = 0;
  dwWaitTime = 0;
  fReturnAfterAnyByte = FALSE;
  if ((Timeouts.ReadIntervalTimeout == MAXDWORD) && !Timeouts.ReadTotalTimeoutConstant && !Timeouts.ReadTotalTimeoutMultiplier) {
    // Documented special handling:
    // A value of MAXDWORD for ReadIntervalTimeout, combined with zero values for both the
    // ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the
    // read operation is to return immediately with the characters that have already been
    // received, even if no characters have been received.
    dwWaitTime = m_dwReadPending ? 1000 : 0;
    fReturnAfterAnyByte = TRUE;
  } else if ((Timeouts.ReadIntervalTimeout == MAXDWORD) && (Timeouts.ReadTotalTimeoutMultiplier == MAXDWORD) &&
             (Timeouts.ReadTotalTimeoutConstant != MAXDWORD) && Timeouts.ReadTotalTimeoutConstant) {
    // Documented special handling:
    // If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to
    // MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and
    // less than MAXDWORD, one of the following occurs when the ReadFile function
    // is called:
    //  - If there are any characters in the input buffer, ReadFile returns
    //    immediately with the characters in the buffer.
    //  - If there are no characters in the input buffer, ReadFile waits until a
    //    character arrives and then returns immediately.
    //  - If no characters arrive within the time specified by
    //    ReadTotalTimeoutConstant, ReadFile times out.
    dwWaitTime = Timeouts.ReadTotalTimeoutConstant;
    fReturnAfterAnyByte = TRUE;
  } else {
    // We've catched the special cases, now we can calculate the given timouts
    if ((Timeouts.ReadTotalTimeoutConstant == MAXDWORD) || (Timeouts.ReadTotalTimeoutMultiplier == MAXDWORD)) {
      dwTotalTimeout = INFINITE;
    } else {
      dwTotalTimeout = (Timeouts.ReadTotalTimeoutMultiplier * dwLen) + Timeouts.ReadTotalTimeoutConstant;
    }
    dwWaitTime = Timeouts.ReadIntervalTimeout;
    if (!dwWaitTime) {
      dwWaitTime = dwTotalTimeout;
    }
  }

  LeaveCriticalSection(&m_csLockRead);

  // Now tell the mux protocol implementation that we're ready to receive data
  if (Dlci_ReceiveDataContinue(m_dwMuxInstance, m_dwDlci)) {
    m_pReadBuffer = NULL;
#if (WINCEOSVER >= 600)
    CeFreeAsynchronousBuffer(pAsyncBuffer, (PVOID)pData, dwLen, dwArgDesc);
#endif // WINCE6000
    LeaveCriticalSection(&m_csLock);
    return (DWORD)-1;
  }

  LeaveCriticalSection(&m_csLock);

  // And wait for incoming data considering the timout setting
  while (TRUE) {
    dwWaitRet = WaitForMultipleObjects(2, hEvents, FALSE, dwWaitTime);
    SYNCBLOCK(m_csLockRead);
    if (dwWaitRet == WAIT_OBJECT_0 + 1) {
#if (WINCEOSVER >= 600)
      CeFreeAsynchronousBuffer(pAsyncBuffer, (PVOID)pData, dwLen, dwArgDesc);
#endif // WINCE6000
      return (DWORD)-1;
    }
    if ((dwWaitRet != WAIT_OBJECT_0) || fReturnAfterAnyByte) {
      if (((GetTickCount() - dwStartTime) >= dwTotalTimeout) || fReturnAfterAnyByte) {
        dwRead = m_dwRead;
        m_pReadBuffer = NULL;
        break;
      }
    }
    if (m_dwRead >= dwLen) {
      dwRead = m_dwRead;
      m_pReadBuffer = NULL;
      ResetEvent(m_hReadEvent);
      break;
    }
  }

#if (WINCEOSVER >= 600)
  CeFreeAsynchronousBuffer(pAsyncBuffer, (PVOID)pData, dwLen, dwArgDesc);
#endif // WINCE6000

  return dwRead;
}

//////////////////////////////////////////////////////////////////////////////
//
// Write data to the virtual mux port.
//
// Parameters:
// pData: The buffer containing the data.
// dwLen: The max. number of bytes to write.
//
// Return:
// The number of actually read bytes, -1 indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CMuxChannel::WriteData(PBYTE pData, DWORD dwLen) {
COMMTIMEOUTS  Timeouts;
HANDLE        hEvents[2];
DWORD         dwCurrentTime;
DWORD         dwStartTime;
DWORD         dwWaitTime;
DWORD         dwWaitRet;
DWORD         dwWritten = 0;
PBYTE         pWrite = pData;

  SYNCBLOCK(m_csLock);

  if (!IsMuxRunning()) {
    Sleep(0);
    return (DWORD)-1;
  }

  // Parse if we have a valid "+++" sequence
  ParseEscape((PCHAR)pData, dwLen);

  // Now feed the data into the mux protocol handling
  GetTimeouts(&Timeouts);
  dwStartTime = GetTickCount();
  while (dwWritten < dwLen) {
    if (!IsMuxRunning()) return (DWORD)-1;
    ResetEvent(m_hWriteContinueEvent);
    dwWritten += Dlci_SendData(m_dwMuxInstance, m_dwDlci, pWrite, (dwLen - dwWritten));

    // Consider the timeout setting if we've not written all data
    if (dwWritten < dwLen) {
      hEvents[0] = m_hWriteContinueEvent;
      hEvents[1] = m_hCloseEvent;
      pWrite = pData + dwWritten;
      dwWaitTime = (Timeouts.WriteTotalTimeoutMultiplier * dwLen) + Timeouts.WriteTotalTimeoutConstant;
      if (!dwWaitTime) {
        dwWaitTime = INFINITE;
      } else {
        dwCurrentTime = GetTickCount();
        if ((dwCurrentTime - dwStartTime) >= dwWaitTime) {
          break;
        } else {
          dwWaitTime = dwWaitTime - (dwCurrentTime - dwStartTime);
        }
      }

      UNSYNCBLOCK(m_csLock);
      dwWaitRet = WaitForMultipleObjects(2, hEvents, FALSE, dwWaitTime);
      if (dwWaitRet == WAIT_OBJECT_0 + 1) {
        return (DWORD)-1;
      }
      if (dwWaitRet != WAIT_OBJECT_0) {
        break;
      }
    }
  }

  return dwWritten;
}

//////////////////////////////////////////////////////////////////////////////
//
// Private implementation
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Stop the running threads.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::StopThreads(void) {
  SetEvent(m_hCloseEvent);

  // Stop the detect escape thread
  if (m_hDetectEscapeThread) {
    SetEvent(m_hStopEscapeThreadEvent);
    WaitForSingleObject(m_hDetectEscapeThread, 1000);
    CloseHandle(m_hDetectEscapeThread);
    m_hDetectEscapeThread = NULL;
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Establish a virtual mux channel.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::DlciOpen(void) {
DLCI_INTERFACE_t  CallBacks;
HANDLE            hEvents[2];

  ResetEvent(m_hCloseEvent);

  // Init callbacks
  CallBacks.pDlciEstablishResult   = MuxEstablishResult;
  CallBacks.pDlciReleaseResult     = MuxReleaseResult;
  CallBacks.pDlciSendDataContinue  = MuxSendDataContinue;
  CallBacks.pDlciReceiveData       = MuxReceiveData;
  CallBacks.pDlciReceiveV24Status  = MuxReceiveV24Status;
  CallBacks.pDlciShutdown          = MuxShutdown;

  // Open the mux channel
  ResetEvent(m_hStartStopEvent);
  m_dwStartStopResult = 0;
  if (Dlci_Establish(m_dwMuxInstance, (DWORD)this, m_dwDlci, &CallBacks)) {
    // Something went wrong
    return FALSE;
  }

  // Wait for the mux protocol to be started
  hEvents[0] = m_hStartStopEvent;
  hEvents[1] = m_hCloseEvent;
  if (WaitForMultipleObjects(2, hEvents, FALSE, 1000) != WAIT_OBJECT_0) {
    // channel connected ???
    // test release
    ResetEvent(m_hStartStopEvent);
    m_dwStartStopResult = 0;
    Dlci_Release(m_dwMuxInstance, m_dwDlci);
    // wait for release
    if (WaitForMultipleObjects(2, hEvents, FALSE, 1000) != WAIT_OBJECT_0) {
      // We timed out or have been closed
      return FALSE;
    } else {
      // ok: channel now released
      // new connection
      ResetEvent(m_hStartStopEvent);
      m_dwStartStopResult = 0;
      if (Dlci_Establish(m_dwMuxInstance, (DWORD)this, m_dwDlci, &CallBacks)) {
        // Something went wrong
        return FALSE;
      }
      if (WaitForMultipleObjects(2, hEvents, FALSE, 1000) != WAIT_OBJECT_0) {
        // We timed out or have been closed
        return FALSE;
      }
    }
  }

  // Check if everything was ok
  if (m_dwStartStopResult) {
    return FALSE;
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close a virtual mux channel.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CMuxChannel::DlciClose(void) {
HANDLE  hEvents[2];
BOOL    fRet = TRUE;

  if (IsMuxRunning()) {
    if (MUX_OK == Dlci_IsConnected(m_dwMuxInstance, m_dwDlci)) {
      // Close the mux channel
      ResetEvent(m_hStartStopEvent);
      m_dwStartStopResult = 0;

      if (Dlci_Release(m_dwMuxInstance, m_dwDlci)) {
        // Something went wrong
        fRet = FALSE;
      } else {
        // Wait for the mux protocol to be stopped
        hEvents[0] = m_hStartStopEvent;
        hEvents[1] = m_hCloseEvent;
        if (WaitForMultipleObjects(2, hEvents, FALSE, 2000) != WAIT_OBJECT_0) {
          // We timed out or have been closed
          fRet = FALSE;
        }
        // Check if everything was ok
        if (m_dwStartStopResult) {
          fRet = FALSE;
        }
      }
    }
  }

  // Finally stop the running threads
  if (!StopThreads()) {
    fRet = FALSE;
  }

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Parse for a valid "+++" sequence in the data stream written to the port.
// The rule for a valid escape sequence is as follows:
// The escape sequence consists of 3 consecutive '+' characters. It must be
// preceded and followed by a pause of at least 1000 ms. The +++ characters
// must be entered in quick succession, all within 1000 ms.
//
// Parameters:
// pData: The buffer containing the data written to the channel.
// dwLen: The number of bytes in the data buffer.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::ParseEscape(PCHAR pData, DWORD dwLen) {
  DWORD dwCurrentTickCount = GetTickCount();
  if ((dwLen + m_dwPlusCnt) <= 3) {
    if (!m_dwPlusCnt) {
      if ((dwCurrentTickCount - m_dwTimeLastChar) >= 1000) {
        ParsePlus(pData, dwLen);
      }
    } else {
      if ((dwCurrentTickCount - m_dwTimeFirstPlus) < 1000) {
        ParsePlus(pData, dwLen);
      } else {
        m_dwPlusCnt = 0;
        if ((dwCurrentTickCount - m_dwTimeLastChar) >= 1000) {
          ParsePlus(pData, dwLen);
        }
      }
    }
  } else {
    m_dwPlusCnt = 0;
  }
  m_dwTimeLastChar = dwCurrentTickCount;

  // Stop a possible running detect escape thread if not needed any more
  if (m_hDetectEscapeThread && !m_dwPlusCnt) {
    SetEvent(m_hStopEscapeThreadEvent);
    CloseHandle(m_hDetectEscapeThread);
    m_hDetectEscapeThread = NULL;
  }
}


//////////////////////////////////////////////////////////////////////////////
//
// Parse a given buffer for three plus characters. Helper function for
// "ParseEscape()".
//
// Parameters:
// pData: The buffer to be parsed.
// dwLen: The number of bytes in the data buffer.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::ParsePlus(PCHAR pData, DWORD dwLen) {
  for (DWORD i = 0; i < dwLen; i++) {
    if (pData[i] != '+') {
      m_dwPlusCnt = 0;
      break;
    }
    m_dwPlusCnt++;
    if (m_dwPlusCnt == 1) {
      m_dwTimeFirstPlus = GetTickCount();
    } else if (m_dwPlusCnt > 3) {
      m_dwPlusCnt = 0;
      break;
    }
  }

  if (m_dwPlusCnt == 3) {
    // We found the 1st part of a valid +++ sequence
    if (m_hDetectEscapeThread) {
      SetEvent(m_hStopEscapeThreadEvent);
      CloseHandle(m_hDetectEscapeThread);
    }
    ResetEvent(m_hStopEscapeThreadEvent);
    m_hDetectEscapeThread = CreateThread(NULL, 0, ThreadDetectEscape, this, 0, NULL);
  }
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called when a call of "Mux_DLCIEstablish()" has finished.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
// dwResult     : The result of the DLCI establish operation.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::MuxEstablishResult(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci, DWORD dwResult) {
  ((CMuxChannel*)dwUserData)->m_dwStartStopResult = dwResult;
  SetEvent(((CMuxChannel*)dwUserData)->m_hStartStopEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called when a call of "Mux_DLCIRelease()" has finished.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
// dwResult     : The result of the DLCI release operation.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::MuxReleaseResult(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci, DWORD dwResult) {
  ((CMuxChannel*)dwUserData)->m_dwStartStopResult = dwResult;
  SetEvent(((CMuxChannel*)dwUserData)->m_hStartStopEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called to tell us that we can continue to send data after it had been
// stalled.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
// dwLen        : The number of free bytes.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::MuxSendDataContinue(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci, DWORD dwLen) {
  SetEvent(((CMuxChannel*)dwUserData)->m_hWriteContinueEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called with available received data.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
// pData        : Pointer to received data.
// dwLen        : Number of received bytes.
//
// Return:
// The number of read bytes.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CMuxChannel::MuxReceiveData(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci, PBYTE pData, DWORD dwLen) {
CMuxChannel* pInstance = (CMuxChannel*)dwUserData;
DWORD        dwRead = 0;

  SYNCBLOCK(pInstance->m_csLockRead);
  pInstance->m_WaitCommEvent.SetWaitEvent(EV_RXCHAR);
  if (pInstance->m_pReadBuffer) {
    dwRead = min(dwLen, pInstance->m_dwReadLen - pInstance->m_dwRead);
#if (WINCEOSVER < 600)
    SetProcPermissions(pInstance->m_dwCurrentPermissions);
#endif // WINCE600
    memcpy(pInstance->m_pReadBuffer, pData, dwRead);
    pInstance->m_pReadBuffer += dwRead;
    pInstance->m_dwRead += dwRead;
    SetEvent(pInstance->m_hReadEvent);
  }
  pInstance->m_dwReadPending = dwLen - dwRead;

  return dwRead;
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called when a state change of the virtual modem signals has been received.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
// V24Status    : The received virtual V24 state.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::MuxReceiveV24Status(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci, MUX_V24STATUS_t V24Status) {
CMuxChannel*     pInstance = (CMuxChannel*)dwUserData;
BOOL             fSendState = FALSE;
MUX_V24STATUS_t  ModuleState;
DWORD            dwChangedMask;

  EnterCriticalSection(&(pInstance->m_csLockDcb));
  EnterCriticalSection(&(pInstance->m_csLockModuleState));

  // Store the new module state and check against event mask
  dwChangedMask = 0;
  if (V24Status.CTS  != pInstance->m_ModuleState.CTS)  dwChangedMask |= EV_CTS;
  if (V24Status.DSR  != pInstance->m_ModuleState.DSR)  dwChangedMask |= EV_DSR;
  if (V24Status.RI   != pInstance->m_ModuleState.RI)   dwChangedMask |= EV_RING;
  if (V24Status.DCD  != pInstance->m_ModuleState.DCD)  dwChangedMask |= EV_RLSD;
  if (V24Status.BreakInd)                              dwChangedMask |= EV_BREAK;
  if (V24Status.TxEmpty && !pInstance->m_ModuleState.TxEmpty) dwChangedMask |= EV_TXEMPTY;
  pInstance->m_ModuleState = V24Status;

  // Consider Dcb flow control setting for incoming data
  ModuleState = pInstance->m_ModuleState;
  if (V24Status.InFlowActive) {
    if (pInstance->m_Dcb.fDtrControl == DTR_CONTROL_HANDSHAKE)  pInstance->m_ModuleState.DTR = 0;
    if (pInstance->m_Dcb.fRtsControl == RTS_CONTROL_HANDSHAKE)  pInstance->m_ModuleState.RTS = 0;
  } else {
    if (pInstance->m_Dcb.fDtrControl == DTR_CONTROL_HANDSHAKE)  pInstance->m_ModuleState.DTR = 1;
    if (pInstance->m_Dcb.fRtsControl == RTS_CONTROL_HANDSHAKE)  pInstance->m_ModuleState.RTS = 1;
  }

  // Check if module state has been changed
  if (memcmp(&ModuleState, &(pInstance->m_ModuleState), sizeof(MUX_V24STATUS_t))) {
    fSendState = TRUE;
    ModuleState = pInstance->m_ModuleState;
  }

  LeaveCriticalSection(&(pInstance->m_csLockModuleState));
  LeaveCriticalSection(&(pInstance->m_csLockDcb));

  // Release pending WaitCommEvent() calls corresponding to changed state
  if (dwChangedMask) {
    MUXDBG(ZONE_MUX_VIRT_SIGNAL, (TEXT("MuxDrv: Detected virtual modem signal change 0x%X on DLCI %d - new state: 0x%X\r\n"), dwChangedMask, pInstance->m_dwDlci, V24Status));
    pInstance->m_WaitCommEvent.SetWaitEvent(dwChangedMask);
  }

  // Send new state to module if it has changed because of RTS/DTR flow control setting
  if (fSendState) {
    Dlci_SetV24Status(dwMuxInstance, dwDlci, ModuleState);
  }
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called when the multiplex protocol handling has been unexpectedly shut
// down.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwDlci       : The corresponding mux channel number.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CMuxChannel::MuxShutdown(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwDlci) {
CMuxChannel* pInstance = (CMuxChannel*)dwUserData;

  // Abort all possibly running operations
  pInstance->StopThreads();
  // Release pending WaitCommEvent() calls
  pInstance->m_WaitCommEvent.SetWaitMask(0, 0);
}

//////////////////////////////////////////////////////////////////////////////
//
// Thread started to detect the final 1 second without data of the
// 1sec > +++ > 1sec escape sequence after the +++ has been received.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CMuxChannel::ThreadDetectEscape(LPVOID lpParameter) {
CMuxChannel* pInstance = (CMuxChannel*)lpParameter;

  if (WaitForSingleObject(pInstance->m_hStopEscapeThreadEvent, 1000) == WAIT_TIMEOUT) {
    SYNCBLOCK(pInstance->m_csLock);
    if ((pInstance->m_dwPlusCnt == 3) && pInstance->IsMuxRunning()) {
      // Valid "+++" sequence detected
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: Escape sequence detected on DLCI %d\r\n"), pInstance->m_dwDlci));
      pInstance->m_dwPlusCnt = 0;
      Dlci_SendEsc(pInstance->m_dwMuxInstance, pInstance->m_dwDlci);
      return 0;
    }
    return (DWORD)-1;
  }
  return (DWORD)-2;
}

