#include "mbftpch.h"
#include "nullmdm.h"


// #undef TRACE_OUT
// #define TRACE_OUT   WARNING_OUT


BYTE    g_szNULLMStartString[] = "NULLMDM";
ULONG   g_nConnID = 0;

DWORD __stdcall TPhysWorkerThreadProc(void *lpv);



CNullModem::CNullModem(HINSTANCE hDllInst)
:
    m_fInitialized(FALSE),
    m_hDllInst(hDllInst),
    m_pfnCallback(NULL),
    m_fListening(FALSE),
    m_hwnd(NULL),
    m_nTransportID(0),
    m_nConnectionID(0),
    m_dwThreadID(0),
    m_hThread(NULL),
    m_hevtOverlapped(NULL),
    m_dwEventMask(0),
    m_fCommPortInUse(FALSE)
{
    ::ZeroMemory(&m_Line, sizeof(m_Line));
    ::ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
    ::ZeroMemory(&m_DefaultTimeouts, sizeof(m_DefaultTimeouts));

    m_hevtOverlapped = ::CreateEvent(NULL, TRUE, FALSE, NULL); // manual reset
    ASSERT(NULL != m_hevtOverlapped);
}


CNullModem::~CNullModem(void)
{
    if (m_fInitialized)
    {
        TPhysTerminate();
    }

    if (NULL != m_hevtOverlapped)
    {
        ::CloseHandle(m_hevtOverlapped);
    }
}


//////////////////////////////////////////////////////////////////////////////
// TPhysInitialize
//////////////////////////////////////////////////////////////////////////////
TPhysicalError CNullModem::TPhysInitialize
(
    TPhysCallback   callback,
    UINT            nTransportID
)
{
    const char *pszNULLMClassName = "COMMWndClass";
    TPhysicalError rc = TPHYS_SUCCESS;

    TRACE_OUT(("TPhysInitialize"));

    //
    // If we have already been initialized then this is a reinit call from
    // the node controller so just do nothing.
    //
    if (! m_fInitialized)
    {
        //
        // zero out the SESSION INFO structure
        //
        ::ZeroMemory(&m_Line, sizeof(m_Line));

        //
        // Store control information
        //
        m_pfnCallback = callback;
        m_nTransportID  = nTransportID;
        m_fInitialized = TRUE;
        m_nConnectionID = ++g_nConnID;

        //
        // Create a window so we can decouple to the node controller context                                                          
        // Register the main window class.  Since it is invisible, leave
        // the wndclass structure sparse.
        //
        WNDCLASS  wc;
        ::ZeroMemory(&wc, sizeof(wc));
        wc.style         = 0;
        wc.lpfnWndProc   = TPhysWndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = m_hDllInst;
        wc.hIcon         = NULL;
        wc.hCursor       = NULL;
        wc.hbrBackground = NULL;
        wc.lpszMenuName  =  NULL;
        wc.lpszClassName = pszNULLMClassName;
        ::RegisterClass(&wc);

        //
        // Create the main window.
        //
        m_hwnd = ::CreateWindow(
            pszNULLMClassName,    // See RegisterClass() call.    
            NULL,                // It's invisible!                  
            0,                    // Window style.                
            0,                    // Default horizontal position.
            0,                    // Default vertical position.      
            0,                    // Default width.                  
            0,                    // Default height.                  
            NULL,                // No parent.                      
            NULL,                // No menu.
            m_hDllInst,            // This instance owns this window.
            NULL                // Pointer not needed.              
        );
        if (NULL == m_hwnd)
        {
            ERROR_OUT(( "Failed to create wnd"));
            return(TPHYS_RESULT_FAIL);
        }

        // rc = g_lpfnPTPhysicalInit(TPhysDriverCallback, NULL);
        ASSERT(TPHYS_SUCCESS == rc);
    }
   
    return rc;
}

//////////////////////////////////////////////////////////////////////////////////
// TPhysTerminate                                                                
//                                                                                
// The node controller is shutting down                                            
// Destroy our window and clean up transports.                                    
//////////////////////////////////////////////////////////////////////////////////
TPhysicalError CNullModem::TPhysTerminate(void)
{
    TRACE_OUT(("TPhysTerminate"));

    if (! m_fInitialized)
    {
        return(TPHYS_RESULT_NOT_INITIALIZED);
    }

    //
    // Clean up the PSTN transport
    //
    // g_lpfnPTPhysicalCleanup();

    //
    // Destroy the window
    //
    if (NULL != m_hwnd)
    {
        ::DestroyWindow(m_hwnd);
        m_hwnd = NULL;
    }

    m_pfnCallback = NULL;
    m_nTransportID = 0;
    m_fInitialized = FALSE;
    
    return(TPHYS_SUCCESS);
}

//////////////////////////////////////////////////////////////////////////////
// TPhysConnectRequest                                                        
//                                                                            
// The node controller wants to place a call and has determined that it        
// first needs a physical modem connection.  Make a call and flag an        
// outgoing call so that when the call comes active we can tell the modem    
// transport to begin negotiation.                                            
//////////////////////////////////////////////////////////////////////////////
TPhysicalError CNullModem::TPhysConnectRequest(LPSTR pszComPort)
{
    TPhysicalError  rc = TPHYS_SUCCESS;
    HANDLE          hCommLink;
    DCB             dcb;
    DWORD           dwWritten;

    TRACE_OUT(("TPhysConnectRequest"));

    if (! m_fInitialized)
    {
        ERROR_OUT(("NULL MODEM OOB not initialized"));
        return TPHYS_RESULT_NOT_INITIALIZED;
    }

    //
    // Select a comm port for the call.
    //
    if (CALL_STATE_IDLE == m_Line.eCallState || CALL_STATE_DROP == m_Line.eCallState)
    {
        //
        // Also prime our local copy of the conninfo structure for use in callbacks
        //
        m_Line.connInfo.resultCode   = 0;
        m_Line.connInfo.connectionID = m_nConnectionID;
    }
    else
    {
        ERROR_OUT(("No comm port is available"));
        return TPHYS_RESULT_COMM_PORT_BUSY;
    }

    // lonchanc: From now on, we can bail out thru the common exit point
    // because the cleanup checks for m_fCommPortInUse.

    //
    // Only alow one at at time
    //
    // lonchanc: g_COMM_Thread_Users is starting from -1
    // why can we simply use a flag???
    if (m_fCommPortInUse)
    {
        ERROR_OUT(("TPhysConnectRequest: Waiting for a previous null mode connection"));
        rc = TPHYS_RESULT_WAITING_FOR_CONNECTION;
        goto bail;
    }
    m_fCommPortInUse = TRUE;

    //
    // Open the comm port
    //
    hCommLink = ::CreateFile(pszComPort,
                             GENERIC_READ | GENERIC_WRITE,
                             0,                    // exclusive access
                             NULL,                 // no security attrs
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // overlapped I/O
                             NULL );
    if (hCommLink == INVALID_HANDLE_VALUE)
    {
        ERROR_OUT(("TPhysConnectRequest: CreateFile failed. err=%d", ::GetLastError()));
        rc = TPHYS_RESULT_COMM_PORT_BUSY;
        goto bail;
    }

    m_Line.hCommLink = hCommLink;

    //
    // remember the default timeouts
    //
    ::GetCommTimeouts(hCommLink, &m_DefaultTimeouts);


    //
    // Let the other side know that we are trying to connect
    //
    if (! ::EscapeCommFunction(hCommLink, SETDTR))
    {
        ERROR_OUT(("TPhysConnectRequest: Unable to Set DTR: err=%d", ::GetLastError()));
    }

    ::ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
    m_Overlapped.hEvent = m_hevtOverlapped;
    m_dwEventMask = 0;

    ::ResetEvent(m_hevtOverlapped);

    if (! ::WriteFile(hCommLink, g_szNULLMStartString, sizeof(g_szNULLMStartString),
                      &dwWritten, &m_Overlapped))
    {
        DWORD dwErr = ::GetLastError();
        if (ERROR_IO_PENDING != dwErr)
        {
            ERROR_OUT(("TPhysConnectRequest: WriteFile failed. err=%d", dwErr));
            ::ResetEvent(m_hevtOverlapped);
            rc = TPHYS_RESULT_COMM_PORT_BUSY;
            goto bail;
        }
        else
        {
            DWORD dwWait = ::WaitForSingleObject(m_hevtOverlapped, INFINITE);
            BOOL fRet = ::GetOverlappedResult(hCommLink, &m_Overlapped, &dwWritten, TRUE);
            ASSERT(fRet);
            ASSERT(dwWritten == sizeof(g_szNULLMStartString));
        }
    }
    else
    {
        ASSERT(dwWritten == sizeof(g_szNULLMStartString));
    }

    ::ResetEvent(m_hevtOverlapped);

    //
    // Get the default dcb
    //
    ::ZeroMemory(&dcb, sizeof(dcb));
    ::GetCommState(hCommLink, &dcb);
    dcb.BaudRate = 19200;    // Default: BaudRate

    //
    // Set our state so we can get notification in the comm port
    //
    dcb.DCBlength = sizeof(DCB);
    dcb.fBinary = 1;                        // binary mode, no EOF check 
    dcb.fParity = 0;                        // enable parity checking 
    dcb.fOutxCtsFlow = 1;                   // CTS output flow control 
    dcb.fOutxDsrFlow = 0;                   // DSR output flow control 
    dcb.fDtrControl = DTR_CONTROL_ENABLE;   // DTR flow control type 
    dcb.fDsrSensitivity = 0;                // DSR sensitivity 
    dcb.fTXContinueOnXoff = 0;              // XOFF continues Tx 
    dcb.fOutX = 0;                          // XON/XOFF out flow control 
    dcb.fInX = 0;                           // XON/XOFF in flow control 
    dcb.fErrorChar = 0;                     // enable error replacement 
    dcb.fNull = 0;                          // enable null stripping 
    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;// RTS flow control 
    dcb.XonLim = 0;                         // transmit XON threshold 
    dcb.XoffLim = 0;                        // transmit XOFF threshold 
    dcb.fErrorChar = 0;                     // enable error replacement 
    dcb.fNull = 0;                          // enable null stripping 
    dcb.fAbortOnError = 0;                  // abort reads/writes on error 
    ::SetCommState(hCommLink, &dcb);

    ::PurgeComm(hCommLink, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

    m_Line.eCallState = CALL_STATE_MAKE;
    m_Line.hevtCall = m_hevtOverlapped;
    m_Line.pstnHandle = (PHYSICAL_HANDLE) hCommLink;
    m_Line.fCaller = TRUE;

    if (! ::SetCommMask(hCommLink,
                EV_RXCHAR |         // Any Character received
                EV_CTS |            // CTS changed state
                EV_DSR |            // DSR changed state
                EV_RLSD|            // RLSD changed state
                EV_RXFLAG))         // Certain character
    {
        ERROR_OUT(("TPhysConnectRequest:  Unable to SetCommMask: err=%d", ::GetLastError()));
    }

    ::ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
    m_Overlapped.hEvent = m_hevtOverlapped;
    m_dwEventMask = 0;

    ::ResetEvent(m_hevtOverlapped);

    if (! ::WaitCommEvent(hCommLink, &m_dwEventMask, &m_Overlapped))
    {
        DWORD dwErr = ::GetLastError();
        if (ERROR_IO_PENDING != dwErr)
        {
            ERROR_OUT(("TPhysConnectRequest: WaitCommEvent failed, err=%d", dwErr));
            m_fCommPortInUse = FALSE;
        }
    }

#if 1
    WorkerThreadProc();
#else
    //
    // If the comm thread doesn't exist, create it now.
    //
    // lonchanc: I am not sure that the thread will exist, because
    // the while loop inside the thread will exit if m_fCommPortInUse is false.
    // If m_fCommPortInUse is true, then we should bail out already.
    //
    ASSERT(NULL == m_hThread);

    //
    // We need to create another thread that will wait on comm events.
    //
    m_hThread = ::CreateThread(NULL, 0, TPhysWorkerThreadProc, this, 0, &m_dwThreadID);
    ASSERT(NULL != m_hThread);
#endif

bail:

    return(rc);
}

//////////////////////////////////////////////////////////////////////////////
// TPhysDisconnect                                                                                                                      
//                                                                              
// The node controller wants us to bring the call down now. We must first   
// ask the transports to close down their physicall connection.                 
//                                                                              
// Note that in the case of the PSTN transport we use a NOWAIT call which   
// completes syncronously.  Because we will not get a follow on confirm         
// we simulate one from here.  If we switch to using WAIT mode then             
// take the event generation code out!                                          
//////////////////////////////////////////////////////////////////////////////
TPhysicalError CNullModem::TPhysDisconnect(void)
{
    TRACE_OUT(("TPhysDisconnect"));

    if (! m_fInitialized)
    {
        ERROR_OUT(( "Not initialised"));
        return(TPHYS_RESULT_NOT_INITIALIZED);
    }

    TRACE_OUT(("Disconnect call, state %u", m_Line.eCallState));

    //
    // Otherwise it must be a pstn call in progress so end that.  Note  
    // that NC may still think it is MODEM, but we still need to close  
    // PSTN.                                                            
    //
    // g_lpfnPTPhysicalDisconnectRequest(m_aLines[lineID].pstnHandle, TPHYSICAL_NO_WAIT);
    ::PostMessage(m_hwnd, WM_TPHYS_DISCONNECT_CONFIRM, (WPARAM) this, m_Line.pstnHandle);

    //
    // close the comport
    //
    DropCall();

    return(TPHYS_SUCCESS);
}

//////////////////////////////////////////////////////////////////////////////
// TPhysListen/TPhysUnlisten                                                
//                                                                              
// NULLMMAN does very little with listen/unlisten, just sets a state variable 
// that is interrogated to see if we should accept incoming calls.              
//                                                                            
// NOTE - This is different from the Listen/Unlisten that is used to tell   
// the PSTN transport about the presence of an incoming call.                   
//////////////////////////////////////////////////////////////////////////////
TPhysicalError CNullModem::TPhysListen(void)
{
    TRACE_OUT(("TPhysListen"));

    if (! m_fInitialized)
    {
        ERROR_OUT(("TPhysListen: not initialized"));
        return(TPHYS_RESULT_NOT_INITIALIZED);
    }

    m_fListening = TRUE;

    return(TPHYS_SUCCESS);
}

TPhysicalError CNullModem::TPhysUnlisten(void)
{
    TRACE_OUT(("TPhysUnlisten"));

    if (! m_fInitialized)
    {
        ERROR_OUT(("TPhysUnlisten: not initialized"));
        return(TPHYS_RESULT_NOT_INITIALIZED);
    }

    m_fListening = FALSE;

    return(TPHYS_SUCCESS);
}

//////////////////////////////////////////////////////////////////////////////
// DropCall - close the comm port
/////////////////////////////////////////////////////////////////////////////
void CNullModem::DropCall(void)
{
    TRACE_OUT(("DropCall"));

    //
    // close the device handle                                              
    //
    if (NULL != m_Line.hCommLink)
    {
        CALL_STATE eCallState = m_Line.eCallState;
        m_Line.eCallState = CALL_STATE_IDLE;

        //
        // If this call is connected it is not using the comm thread
        //
        if (eCallState != CALL_STATE_CONNECTED)
        {
            m_fCommPortInUse = FALSE;
        }

        //
        // restore comm timeouts
        //
        SetCommTimeouts(m_Line.hCommLink, &m_DefaultTimeouts);

        TRACE_OUT(("Closing device handle %x", m_Line.hCommLink));
        ::CloseHandle(m_Line.hCommLink);
        m_Line.hCommLink = NULL;
    }
}


//////////////////////////////////////////////////////////////////////////////
//                                                                              
// FUNCTION: PSTNCallback                                                       
//                                                                              
// DESCRIPTION:                                                                 
//                                                                              
// PSTN callback function, called by the PSTN driver with the resuts of         
// TPhysical operations                                                         
//                                                                              
// See MCATTPRT.H for definitions of the parameters                             
//                                                                              
//////////////////////////////////////////////////////////////////////////////
TPhysicalError CALLBACK TPhysDriverCallback(USHORT msg, ULONG lParam, void *userData)
{
    TRACE_OUT(("NULLM_PSTNCallback"));
    TRACE_OUT(("Hit TPhysical callback, %x %lx %lx", msg, lParam, userData));

    CNullModem *p = (CNullModem *) userData;
    BOOL fRet = ::PostMessage(p->GetHwnd(), msg, (WPARAM) p, lParam);
    ASSERT(fRet);

    return TPHYS_SUCCESS;
}

//////////////////////////////////////////////////////////////////////////////
//                                                                          
// FUNCTION:TPhysWndProc                                                      
//                                                                          
// Window procedure used to decouple callback requests                      
//                                                                          
//////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK TPhysWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CNullModem *p = (CNullModem *) wParam;
    if (uMsg >= WM_APP)
    {
        return p->TPhysProcessMessage(uMsg, lParam);
    }
    return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}


LRESULT CNullModem::TPhysProcessMessage(UINT uMsg, LPARAM lParam)
{
    USHORT      rc;
    USHORT      reason;
    ULONG       status;
    LINE_INFO  *pLine;

    if (WM_TPHYS_STATUS_INDICATION == uMsg)
    {
        return 0;
    }
    
    ASSERT(lParam == (LPARAM) m_Line.pstnHandle);

    switch (uMsg)
    {
    case WM_TPHYS_CONNECT_INDICATION:
        TRACE_OUT(("got a WM_TPHYS_CONNECT_INDICATION"));

        m_Line.connInfo.resultCode = TPHYS_RESULT_INUSE;
        m_Line.eCallState = CALL_STATE_CONNECTED;

        if (NULL != m_pfnCallback)
        {
            (*m_pfnCallback)(WM_TPHYS_CONNECT_CONFIRM, &m_Line.connInfo, m_nTransportID);
        }
        break;

    case WM_TPHYS_CONNECT_CONFIRM:
        //
        // The transport has connected OK!  We will take the line   
        // back when the transport pulls DTR down                   
        //
        TRACE_OUT(("got a WM_TPHYS_CONNECT_CONFIRM"));
        m_Line.connInfo.resultCode = TPHYS_RESULT_SUCCESS_ALTERNATE;
        m_Line.eCallState = CALL_STATE_CONNECTED;

        if (NULL != m_pfnCallback)
        {
            (*m_pfnCallback)(WM_TPHYS_CONNECT_CONFIRM, &m_Line.connInfo, m_nTransportID);
        }
        break;

    case WM_TPHYS_DISCONNECT_INDICATION:
    case WM_TPHYS_DISCONNECT_CONFIRM:
        //
        // If the disconnect is the result of a failed connect request  
        // then tell the NC of the failure (Otherwise it is a           
        // successful disconnect)                                       
        //
        if (WM_TPHYS_DISCONNECT_INDICATION == uMsg)
        {
            TRACE_OUT(("WM_TPHYS_DISCONNECT_INDICATION, %ld", lParam));
        }
        else
        {
            TRACE_OUT(("WM_TPHYS_DISCONNECT_CONFIRM, %ld", lParam));
        }
        if ((m_Line.eCallState == CALL_STATE_MAKE)   ||
            (m_Line.eCallState == CALL_STATE_ANSWER) ||
            (m_Line.eCallState == CALL_STATE_CONNECTED))
        {
            TRACE_OUT(( "T120 connection attempt has failed"));
            if (m_Line.fCaller)
            {
                m_Line.connInfo.resultCode = TPHYS_RESULT_CONNECT_FAILED;
                if (NULL != m_pfnCallback)
                {
                    (*m_pfnCallback)(WM_TPHYS_CONNECT_CONFIRM, &m_Line.connInfo, m_nTransportID);
                }
            }
            DropCall();
        }
        else
        {
            //
            // The transport has disconnected OK.  We can take the line 
            // back as soon as the unlisten returns                     
            //
            TRACE_OUT(("T120 has disconnected - unlistening"));
            if (! m_Line.fCaller)
            {
                // g_lpfnPTPhysicalUnlisten(m_Line.pstnHandle);
            }
            else
            {
                //
                // We only drop the call for outgoing requests - leave  
                // incoming calls to drop when the line goes down       
                //
                DropCall();
            }
        }
        break;
    }
    
    return 0;
}


//////////////////////////////////////////////////////////////////////////////
// SetConnectedPort                                                        
//                                                                        
// Waits for comm port changes                                            
//////////////////////////////////////////////////////////////////////////////
void CNullModem::SetConnectedPort(void)
{
    DCB     dcb;

    TRACE_OUT(("SetConnectedPort"));

    ::ZeroMemory(&dcb, sizeof(dcb));

    //
    //  Set comm mask and state
    //
    ::SetCommMask(m_Line.hCommLink, 0);    // RLSD changed state

    ::GetCommState(m_Line.hCommLink, &dcb);
    dcb.DCBlength = sizeof(DCB);
    dcb.fBinary = 1;           // binary mode, no EOF check 
    dcb.fOutxDsrFlow = 0;      // DSR output flow control 
    dcb.fDsrSensitivity = 0;   // DSR sensitivity 
    dcb.fTXContinueOnXoff = 0; // XOFF continues Tx 
    dcb.fOutX = 0;             // XON/XOFF out flow control 
    dcb.fInX = 0;              // XON/XOFF in flow control 
    dcb.fErrorChar = 0;        // enable error replacement 
    dcb.fNull = 0;             // enable null stripping 
    dcb.XonLim = 0;            // transmit XON threshold 
    dcb.XoffLim = 0;           // transmit XOFF threshold 
    ::SetCommState(m_Line.hCommLink, &dcb);
}

//////////////////////////////////////////////////////////////////////////////
// WaitForConnection                                                    
//                                                                        
// Waits for comm port changes                                            
//////////////////////////////////////////////////////////////////////////////
BOOL CNullModem::WaitForConnection(void)
{
    BOOL fRet = FALSE;

    TRACE_OUT(("WaitForConnection"));

    while (TRUE)
    {
        DWORD dwWait = ::WaitForSingleObject(m_hevtOverlapped, COMM_PORT_TIMEOUT);
        TRACE_OUT(("WaitForConnection: WaitForSingleObject returns %d", dwWait));

        ::ResetEvent(m_hevtOverlapped);

        if (dwWait == WAIT_ABANDONED || dwWait == WAIT_TIMEOUT || dwWait == WAIT_FAILED)
        {
            DropCall();
            m_fCommPortInUse = FALSE;
            ERROR_OUT(("WaitForConnection: Unable to WaitCommEvent: error = %d", ::GetLastError()));
            goto Failure;
        }

        ASSERT(m_hevtOverlapped == m_Line.hevtCall);
        if (CALL_STATE_MAKE != m_Line.eCallState && CALL_STATE_ANSWER != m_Line.eCallState)
        {
            ERROR_OUT(("WaitForConnection: Got a bad event = %d", m_hevtOverlapped));
            goto Failure;
        }

        TRACE_OUT(("WaitForConnection: m_dwEventMask = %d", m_dwEventMask));
        switch (m_Line.eCallState)
        {
        case CALL_STATE_MAKE:
            {
                //
                // The other side was connected and cleared DTR
                //
                if (m_dwEventMask & (EV_RXCHAR))
                {
                    ::EscapeCommFunction(m_Line.hCommLink, CLRDTR);
                    ::EscapeCommFunction(m_Line.hCommLink, SETDTR);
                    SetConnectedPort();
                    m_Line.fCaller = FALSE;
                    goto Success;
                }
                //
                // The other side just connected
                //
                else
                if(m_dwEventMask & (EV_DSR | EV_RLSD | EV_CTS))
                {
                    //
                    // Change the state of this connection so we dont get here again
                    //
                    m_Line.eCallState = CALL_STATE_ANSWER;

                    //
                    // Wait sometime so the other side can transition to the wait state
                    //
                    ::Sleep(2000);
                    
                    //
                    // Tell the other side we connected before
                    //
                    ::EscapeCommFunction(m_Line.hCommLink, SETBREAK);

                    ::ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
                    m_Overlapped.hEvent = m_Line.hevtCall;
                    m_dwEventMask = 0;

                    ::ResetEvent(m_Overlapped.hEvent);

                    if (! ::WaitCommEvent(m_Line.hCommLink, &m_dwEventMask, &m_Overlapped))
                    {
                        DWORD dwErr = ::GetLastError();
                        if (ERROR_IO_PENDING != dwErr)
                        {
                            ERROR_OUT(("TPhysConnectRequest:  Unable to WaitCommEvent: error = %d", dwErr));
                            DropCall();
                            goto Failure;
                        }
                    }
                }
            }
            break;
            
        case CALL_STATE_ANSWER:
            {
                ::EscapeCommFunction(m_Line.hCommLink, CLRBREAK);
                SetConnectedPort();
                goto Success;
            }
            break;
        }
    } // while

Success:

    fRet = TRUE;

Failure:

    return fRet;
}

//////////////////////////////////////////////////////////////////////////////
// COMMThread                                                            
//                                                                        
// Waits for comm port changes                                            
//////////////////////////////////////////////////////////////////////////////
DWORD __stdcall TPhysWorkerThreadProc(void *lParam)
{
    return ((CNullModem *) lParam)->WorkerThreadProc();
}


DWORD CNullModem::WorkerThreadProc(void)
{
    ULONG    rc = 0;
    ULONG    dwResult;

    TRACE_OUT(("TPhysWorkerThreadProc"));

    while (m_fCommPortInUse)
    {
        //
        // Wait for connection to happen
        //
        if (WaitForConnection())
        {
            SetBuffers();
            SetTimeouts();

            //
            // Call T120 physicall request
            //
            if (m_Line.fCaller)
            {
                // rc = g_lpfnPTPhysicalConnectRequest(0, // CALL_CONTROL_MANUAL
                //                        &m_Line.hCommLink, NULL, &m_Line.pstnHandle);
            }
            else
            {
                m_Line.connInfo.connectionID = m_nConnectionID;
                m_Line.connInfo.resultCode = TPHYS_SUCCESS;

                if (NULL != m_pfnCallback)
                {
                    (*m_pfnCallback)(WM_TPHYS_CONNECT_INDICATION, &m_Line.connInfo, m_nTransportID);
                }

                // rc = g_lpfnPTPhysicalListen(0, // CALL_CONTROL_MANUAL
                //                        &m_Line.hCommLink, NULL, &m_Line.pstnHandle);
            }
            
            if (rc != 0)
            {
                TRACE_OUT(( "Failed COMM connect, rc %d",rc));
                m_Line.connInfo.resultCode = TPHYS_RESULT_CONNECT_FAILED;

                if (NULL != m_pfnCallback)
                {
                    (*m_pfnCallback)(WM_TPHYS_CONNECT_CONFIRM, &m_Line.connInfo, m_nTransportID);
                }

                DropCall();
            }
            else
            {
                m_Line.eCallState = CALL_STATE_CONNECTED;
                m_Line.connInfo.resultCode = TPHYS_SUCCESS;

                //
                // This comm port doesn't need the thread anymore
                //
                m_fCommPortInUse = FALSE;
            }
        }
        else
        {
            TRACE_OUT(( "Failed COMM connect, rc %d",rc));
            m_Line.connInfo.resultCode = TPHYS_RESULT_CONNECT_FAILED;

            if (NULL != m_pfnCallback)
            {
                (*m_pfnCallback)(WM_TPHYS_CONNECT_CONFIRM, &m_Line.connInfo, m_nTransportID);
            }
            
            //
            // Something went wrong in the wait, get out of the loop
            //
            break;
        }
    }        

    //
    // We are going down.
    //
    if (NULL != m_hThread)
    {
        ::CloseHandle(m_hThread);
        m_hThread = NULL;
    }

    return 0;
}


void CNullModem::SetBuffers(void)
{
    BOOL fRet = ::SetupComm(m_Line.hCommLink, /* rx */ 10240, /* tx */ 1024);
    ASSERT(fRet);
}


void CNullModem::SetTimeouts(void)
{
    COMMTIMEOUTS    com_timeouts;
    ::ZeroMemory(&com_timeouts, sizeof(com_timeouts));
    com_timeouts.ReadIntervalTimeout = 10;
    com_timeouts.ReadTotalTimeoutMultiplier = 0;
    com_timeouts.ReadTotalTimeoutConstant = 100;
    com_timeouts.WriteTotalTimeoutMultiplier = 0;
    com_timeouts.WriteTotalTimeoutConstant = 10000;
    BOOL fRet = ::SetCommTimeouts(m_Line.hCommLink, &com_timeouts);
    ASSERT(fRet);

}