/*++

Copyright (c) 1995-1996  Microsoft Corporation

Module Name:

    esp.c

Abstract:

    This module contains

Author:

    Dan Knudson (DanKn)    18-Sep-1995

Revision History:


Notes:

    1. Regarding the SP filling in structures with variable length fields
       (dwXxxSize/dwXxxOffset) : "The SP's variable size fields start
       immediately after the fixed part of the data structure.  The order
       of filling of the variable size fields owned by the SP is not
       specified.  The SP can fill them in any order it desires.  Filling
       should be contiguous, starting at the beginning of the variable
       part." (Taken from Chapter 2 of the SPI Programmer's Guide.)

--*/


#include "stdarg.h"
#include "stdio.h"
#include "stdlib.h"
//#include "malloc.h"
#include "string.h"
#include "esp.h"
#include "devspec.h"
#include "vars.h"


#define MAX_NUM_PARKED_CALLS 16

LONG        glNextRequestResult = 0;
DWORD       gdwNextRequestCompletionType;
DWORD       gdwDevSpecificRequestID;
DWORD       gdwCallID = 1;
BOOL        gbExitPBXThread;
BOOL        gbDisableUI;
BOOL        gbAutoGatherGenerateMsgs;
BOOL        gbManualResults = FALSE;
BOOL        gbInteractWithDesktop = FALSE;
DWORD       gdwCallInstance = 0;
WCHAR       gszProviderInfo[] = L"ESP v2.0";
HANDLE      ghPBXThread = NULL;
PDRVCALL    gaParkedCalls[MAX_NUM_PARKED_CALLS];

static WCHAR *aszDeviceClasses[] =
{
    L"tapi/line",
    L"tapi/phone",
    L"wave",
    L"wave/in",
    L"wave/out",
    L"comm",
    L"comm/datamodem",
    (WCHAR *) NULL
};


BOOL
PASCAL
IsValidDrvCall(
    PDRVCALL    pCall,
    LPDWORD     pdwCallInstance
    );

BOOL
CALLBACK
ValuesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    );

LONG
PASCAL
CreateIncomingCall(
    LPCWSTR             lpszDestAddress,
    LPLINECALLPARAMS    lpCallParams,
    PDRVCALL            pOutgoingCall,
    BOOL               *pbValidESPAddress,
    PDRVLINE           *ppIncomingLine,
    PDRVCALL           *ppIncomingCall
    );

LONG
PASCAL
TransferCall(
    PFUNC_INFO  pInfo,
    PDRVCALL    pCall,
    DWORD       dwValidCurrentCallStates,
    DWORD       dwNewCallState,
    LPCWSTR     lpszDestAddress
    );


BOOL
WINAPI
DllMain(
    HANDLE  hDLL,
    DWORD   dwReason,
    LPVOID  lpReserved
    )
{
    static BOOL   bLoadedByTapisrv;
    static HANDLE hInitEvent;


    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        UINT uiResult;


        if (!_CRT_INIT (hDLL, dwReason, lpReserved))
        {
            OutputDebugString ("ESP: DllMain: _CRT_INIT() failed\n\r");
        }

        ghInstance = hDLL;


        //
        // Grab ini file settings
        //
#if DBG

        {
        HKEY    hKey;
        DWORD   dwDataSize, dwDataType;
        TCHAR   szTelephonyKey[] =
                    "Software\\Microsoft\\Windows\\CurrentVersion\\Telephony",
                szEsp32DebugLevel[] = "Esp32DebugLevel";


        RegOpenKeyEx(
            HKEY_LOCAL_MACHINE,
            szTelephonyKey,
            0,
            KEY_ALL_ACCESS,
            &hKey
            );

        dwDataSize = sizeof (DWORD);
        gdwDebugLevel=0;

        RegQueryValueEx(
            hKey,
            szEsp32DebugLevel,
            0,
            &dwDataType,
            (LPBYTE) &gdwDebugLevel,
            &dwDataSize
            );

        RegCloseKey (hKey);
        }

#endif
        //
        // Determine whether we're being loaded by tapisrv or some
        // other process (i.e. telephony ctrl panel)- this will tell
        // us whether we need to go thru all the necessary init or not
        //

        {
            char           *pszProcessName;
            STARTUPINFO     si;


            GetStartupInfoA (&si);

            pszProcessName = si.lpTitle + (lstrlenA (si.lpTitle) - 1);

            for (; pszProcessName != si.lpTitle; pszProcessName--)
            {
                if (*pszProcessName == '\\')
                {
                    pszProcessName++;
                    break;
                }
            }

            bLoadedByTapisrv = (lstrcmpiA (pszProcessName, "tapisrv.exe") == 0
                ? TRUE : FALSE);
        }

        if (bLoadedByTapisrv)
        {
            {
                typedef struct _XXX
                {
                    DWORD    dwDefValue;

                    LPCSTR   pszValueName;

                    LPDWORD  pdwValue;

                } XXX, *PXXX;

                XXX axxx[] =
                {
                    {   DEF_SPI_VERSION,
                        "TSPIVersion",
                        &gESPGlobals.dwSPIVersion },
                    {   DEF_NUM_LINES,
                        "NumLines",
                        &gESPGlobals.dwNumLines },
                    {   DEF_NUM_ADDRS_PER_LINE,
                        "NumAddrsPerLine",
                        &gESPGlobals.dwNumAddressesPerLine },
                    {   DEF_NUM_CALLS_PER_ADDR,
                        "NumCallsPerAddr",
                        &gESPGlobals.dwNumCallsPerAddress },
                    {   DEF_NUM_PHONES,
                        "NumPhones",
                        &gESPGlobals.dwNumPhones },
                    {   DEF_DEBUG_OPTIONS,
                        "DebugOutput",
                        &gESPGlobals.dwDebugOptions },
                    {   DEF_COMPLETION_MODE,
                        "Completion",
                        &gESPGlobals.dwCompletionMode },
                    {   0,
                        "DisableUI",
                        &gbDisableUI },
                    {   1,
                        "AutoGatherGenerateMsgs",
                        &gbAutoGatherGenerateMsgs },
                    {   0,
                        NULL,
                        NULL },
                };
                DWORD   i;


                for (i = 0; axxx[i].pszValueName; i++)
                {
                    *(axxx[i].pdwValue) = (DWORD) GetProfileInt(
                        "ESP32",
                        axxx[i].pszValueName,
                        (int) axxx[i].dwDefValue
                        );

                }
            }


            //
            //
            //

            InitializeCriticalSection (&gESPGlobals.CallListCritSec);
            InitializeCriticalSection (&gESPGlobals.PhoneCritSec);
            InitializeCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);

            if (gbDisableUI)
            {
                //
                // Don't bother doing all the stuff to sync/start up espexe.
                // However, we do want to make sure that we're not wasting
                // time spewing dbg output nor completing async requests in
                // any way other than inline (synchronously), since we're
                // not real smart about cleaning up pending async requests
                // when a call or line is closed/destroyed.
                //

                gESPGlobals.dwDebugOptions = 0;
                gESPGlobals.dwCompletionMode =
                    COMPLETE_ASYNC_EVENTS_SYNCHRONOUSLY;
                gbAutoGatherGenerateMsgs = FALSE; //TRUE;
            }
            else
            {
                //
                // Check to see if tapisrv has the "interact with
                // desktop" privilege enabled
                //

                {
                    SC_HANDLE hSCManager, hTapisrvSvc;


                    if ((hSCManager = OpenSCManager(
                            NULL,
                            NULL,
                            GENERIC_READ
                            )))
                    {
                        if ((hTapisrvSvc = OpenService(
                                hSCManager,
                                "tapisrv",
                                SERVICE_QUERY_CONFIG
                                )))
                        {
                            DWORD                   dwNeededSize;
                            QUERY_SERVICE_CONFIG    config;


                            if (!QueryServiceConfig(
                                    hTapisrvSvc,
                                    &config,
                                    sizeof (QUERY_SERVICE_CONFIG),
                                    &dwNeededSize
                                    ))
                            {
                                QUERY_SERVICE_CONFIG   *pConfig;


                                config.dwServiceType = 0;

                                if (GetLastError() ==
                                        ERROR_INSUFFICIENT_BUFFER)
                                {
                                    if ((pConfig = DrvAlloc (dwNeededSize)))
                                    {
                                        if (QueryServiceConfig(
                                                hTapisrvSvc,
                                                pConfig,
                                                dwNeededSize,
                                                &dwNeededSize
                                                ))
                                        {
                                            config.dwServiceType =
                                                pConfig->dwServiceType;
                                        }

                                        DrvFree (pConfig);
                                    }
                                }
                            }

                            gbInteractWithDesktop = (BOOL)
                                (config.dwServiceType &
                                SERVICE_INTERACTIVE_PROCESS);

                            CloseServiceHandle (hTapisrvSvc);
                        }

                        CloseServiceHandle (hSCManager);
                    }
                }

                if (!gbInteractWithDesktop)
                {
                    gESPGlobals.dwDebugOptions &= ~MANUAL_RESULTS;
                }


                //
                //
                //

                InitializeCriticalSection (&gESPGlobals.DebugBufferCritSec);
                InitializeCriticalSection (&gESPGlobals.EventBufferCritSec);

                gESPGlobals.dwDebugBufferTotalSize = 2048;
                gESPGlobals.dwDebugBufferUsedSize  = 0;

                gESPGlobals.pDebugBuffer =
                gESPGlobals.pDebugBufferIn =
                gESPGlobals.pDebugBufferOut = DrvAlloc(
                    gESPGlobals.dwDebugBufferTotalSize
                    );

                gESPGlobals.dwEventBufferTotalSize = 40 * sizeof (WIDGETEVENT);
                gESPGlobals.dwEventBufferUsedSize  = 0;

                gESPGlobals.pEventBuffer =
                gESPGlobals.pEventBufferIn =
                gESPGlobals.pEventBufferOut = DrvAlloc(
                    gESPGlobals.dwEventBufferTotalSize
                    );


                //
                // Create the events used to sync up w/ espexe, and
                // start espexe if it's not already running
                //

                ghDebugOutputEvent = CreateEvent(
                    (LPSECURITY_ATTRIBUTES) NULL,
                    TRUE,           // manual reset
                    FALSE,          // non-signaled
                    NULL            // unnamed
                    );

                ghWidgetEventsEvent = CreateEvent(
                    (LPSECURITY_ATTRIBUTES) NULL,
                    TRUE,           // manual reset
                    FALSE,          // non-signaled
                    NULL            // unnamed
                    );

                ghShutdownEvent = CreateEvent(
                    (LPSECURITY_ATTRIBUTES) NULL,
                    FALSE,          // auto reset
                    FALSE,          // non-signaled
                    NULL            // unnamed
                    );


                //
                // Enable rpc server interface
                //

                {
                    RPC_STATUS  status;
                    unsigned char * pszSecurity         = NULL;
                    unsigned int    cMaxCalls           = 20;


                    status = RpcServerUseProtseqEp(
                        "ncalrpc",
                        cMaxCalls,
                        "esplpc",
                        pszSecurity             // Security descriptor
                        );

                    DBGOUT((3, "RpcServerUseProtseqEp(lrpc) ret'd %d", status));

                    if (status)
                    {
                    }

                    status = RpcServerRegisterIf(
                        esp_ServerIfHandle,     // interface to register
                        NULL,                   // MgrTypeUuid
                        NULL                    // MgrEpv; null means use default
                        );

                    DBGOUT((3, "RpcServerRegisterIf ret'd %d", status));

                    if (status)
                    {
                    }
                }


                if ((hInitEvent = OpenEvent(
                        EVENT_ALL_ACCESS,
                        FALSE, "ESPevent"
                        )))
                {
                    SetEvent (hInitEvent);
                }
                else
                {
                    hInitEvent = CreateEvent(
                        (LPSECURITY_ATTRIBUTES) NULL,
                        FALSE,      // auto reset
                        TRUE,       // signaled
                        "ESPevent"
                        );

                    DBGOUT((3, "Starting espexe..."));

                    if ((uiResult = WinExec ("espexe.exe", SW_SHOW)) < 32)
                    {
                        DBGOUT((
                            1,
                            "WinExec(espexe.exe) failed, err=%d",
                            uiResult
                            ));

                        gESPGlobals.dwDebugOptions = 0;
                        gESPGlobals.dwCompletionMode =
                            COMPLETE_ASYNC_EVENTS_SYNCHRONOUSLY;
                    }
#if DBG
                    else
                    {
                        DBGOUT((3, "started espexe"));
                    }
#endif
                }
            }
        }

        break;
    }
    case DLL_PROCESS_DETACH:

        if (bLoadedByTapisrv)
        {
            if (gbDisableUI == FALSE)
            {
                SetEvent (ghShutdownEvent);

                //
                // Unregister out rpc server interface
                //

                {
                    RPC_STATUS  status;


                    status = RpcServerUnregisterIf(
                        esp_ServerIfHandle,         // interface to register
                        NULL,                       // MgrTypeUuid
                        0                           // wait for calls to complete
                        );

                    DBGOUT((3, "RpcServerUntegisterIf ret'd %d", status));
                }

                CloseHandle (ghDebugOutputEvent);
                CloseHandle (ghWidgetEventsEvent);
                CloseHandle (ghShutdownEvent);
                CloseHandle (hInitEvent);

                DeleteCriticalSection (&gESPGlobals.DebugBufferCritSec);
                DeleteCriticalSection (&gESPGlobals.EventBufferCritSec);

                DrvFree (gESPGlobals.pDebugBuffer);
                DrvFree (gESPGlobals.pEventBuffer);
            }

            DeleteCriticalSection (&gESPGlobals.CallListCritSec);
            DeleteCriticalSection (&gESPGlobals.PhoneCritSec);
            DeleteCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);
        }

        if (!_CRT_INIT (hDLL, dwReason, lpReserved))
        {
            OutputDebugString ("ESP: DllMain: _CRT_INIT() failed\n\r");
        }

        break;

    default:

        if (!_CRT_INIT (hDLL, dwReason, lpReserved))
        {
            OutputDebugString ("ESP: DllMain: _CRT_INIT() failed\n\r");
        }

        break;
    }

    return TRUE;
}


void
AsyncEventQueueServiceThread(
    LPVOID  pParams
    )
{
    while (1)
    {
        WaitForSingleObject (gESPGlobals.hAsyncEventsPendingEvent, INFINITE);

        while (1)
        {
            PASYNC_REQUEST_INFO pAsyncReqInfo;


            EnterCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);

            if (gESPGlobals.dwNumUsedQueueEntries == 0)
            {
                ResetEvent (gESPGlobals.hAsyncEventsPendingEvent);
                LeaveCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);
                break;
            }

            pAsyncReqInfo = (PASYNC_REQUEST_INFO)
                (*gESPGlobals.pAsyncRequestQueueOut);

            gESPGlobals.pAsyncRequestQueueOut++;

            if (gESPGlobals.pAsyncRequestQueueOut ==
                    (gESPGlobals.pAsyncRequestQueue +
                        gESPGlobals.dwNumTotalQueueEntries))
            {
                gESPGlobals.pAsyncRequestQueueOut =
                    gESPGlobals.pAsyncRequestQueue;
            }

            gESPGlobals.dwNumUsedQueueEntries--;

            LeaveCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);

            if (pAsyncReqInfo->pfnPostProcessProc)
            {
                (*(pAsyncReqInfo->pfnPostProcessProc))(
                    pAsyncReqInfo,
                    ASYNC
                    );
            }
            else
            {
                DoCompletion (pAsyncReqInfo, ASYNC);
            }

            DrvFree (pAsyncReqInfo);
        }

        if (gESPGlobals.bProviderShutdown)
        {
            break;
        }
    }

    ExitThread (0);
}


void
PBXThread(
    LPVOID  pParams
    )
{
    DWORD  *pPBXSettings = (LPDWORD) pParams,
            dwTickCount, dwElapsedTime,
            dwTimePerNewCall = pPBXSettings[0], dwLastNewCallTickCount,
            dwTimePerDisconnect = pPBXSettings[1], dwLastDisconnectTickCount;

/*
    DWORD   dwTickCount, dwElapsedTime,
            dwLastNewCallTickCount, dwLastDisconnectTickCount,
            dwTimePerNewCall = (gPBXSettings[0].dwNumber ?
                gPBXSettings[0].dwTime / gPBXSettings[0].dwNumber : 0),
            dwTimePerDisconnect = (gPBXSettings[1].dwNumber ?
                gPBXSettings[1].dwTime / gPBXSettings[1].dwNumber : 0);
*/

    ShowStr (TRUE, "PBXThread: enter");

    // BUGBUG acct for 49.7 day tick count wrap

    dwTickCount =
    dwLastNewCallTickCount =
    dwLastDisconnectTickCount = GetTickCount();

    ShowStr(
        TRUE,
        "dwTimePerNewCall = %d, dwTimePerDisconnect = %d",
        dwTimePerNewCall,
        dwTimePerDisconnect
        );

    while (1)
    {
        Sleep (1000);

        if (gbExitPBXThread)
        {
            break;
        }

        dwTickCount += 1000;

        if (dwTimePerNewCall)
        {
            dwElapsedTime = dwTickCount - dwLastNewCallTickCount;

            while (dwElapsedTime >= dwTimePerNewCall)
            {
                //
                // Generate new call (random line, random media mode)
                //

                DWORD   i = rand(), j;


                for (j = 0; j < gESPGlobals.dwInitialNumLines; j++)
                {
                    PDRVLINE    pLine = GetLineFromID(
                                    i % gESPGlobals.dwInitialNumLines +
                                    gESPGlobals.dwLineDeviceIDBase
                                    );


                    if (pLine->dwMediaModes)
                    {
                        DWORD       dwMediaMode;
                        PDRVCALL    pCall;


                        for(
                            dwMediaMode =
                                (LINEMEDIAMODE_INTERACTIVEVOICE << i % 13);
                            dwMediaMode <= LAST_LINEMEDIAMODE;
                            dwMediaMode <<= 1
                            )
                        {
                            if (pLine->dwMediaModes & dwMediaMode)
                            {
                                goto PBXThread_allocCall;
                            }
                        }

                        for(
                            dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
                            dwMediaMode <= LAST_LINEMEDIAMODE;
                            dwMediaMode <<= 1
                            )
                        {
                            if (pLine->dwMediaModes & dwMediaMode)
                            {
                                break;
                            }
                        }

PBXThread_allocCall:
                        if (AllocCall (pLine, NULL, NULL, &pCall) == 0)
                        {
                            pCall->dwMediaMode = dwMediaMode;

                            SendLineEvent(
                                pLine,
                                NULL,
                                LINE_NEWCALL,
                                (DWORD) pCall,
                                (DWORD) &pCall->htCall,
                                0
                                );

                            if (!pCall->htCall)
                            {
                                FreeCall (pCall, pCall->dwCallInstance);
                                continue;
                            }

                            SetCallState(
                                pCall,
                                pCall->dwCallInstance,
                                0xffffffff,
                                LINECALLSTATE_OFFERING,
                                0,
                                FALSE
                                );

                            break;
                        }

                    }

                    i++;
                }

                dwElapsedTime -= dwTimePerNewCall;

                dwLastNewCallTickCount = dwTickCount;
            }
        }

        if (dwTimePerDisconnect)
        {
            dwElapsedTime = dwTickCount - dwLastDisconnectTickCount;

            while (dwElapsedTime >= dwTimePerDisconnect)
            {
                //
                // Disconnect a random (non-idle) call (random disconnect mode)
                //

                DWORD   i = rand(), j, k;


                for (j = 0; j < gESPGlobals.dwInitialNumLines; j++)
                {
                    DWORD       dwInitialAddrID =
                                    i % gESPGlobals.dwNumAddressesPerLine,
                                dwLastAddrID =
                                    gESPGlobals.dwNumAddressesPerLine;
                    PDRVLINE    pLine = GetLineFromID(
                                    i % gESPGlobals.dwInitialNumLines +
                                    gESPGlobals.dwLineDeviceIDBase
                                    );

PBXThread_findCallToDisconnect:

                    for (
                        k = dwInitialAddrID;
                        k < dwLastAddrID;
                        k++
                        )
                    {
                        EnterCriticalSection (&gESPGlobals.CallListCritSec);

                        if (pLine->aAddrs[k].dwNumCalls)
                        {
                            PDRVCALL pCall = pLine->aAddrs[k].pCalls;

                            while (pCall &&
                                   pCall->dwCallState == LINECALLSTATE_IDLE)
                            {
                                pCall = pCall->pNext;
                            }

                            if (pCall)
                            {
                                DWORD   dwDisconnectMode =
                                          LINEDISCONNECTMODE_NORMAL;


                                // BUGBUG disconnectMode depends on curr state

                                SetCallState(
                                    pCall,
                                    pCall->dwCallInstance,
                                    0xffffffff,
                                    LINECALLSTATE_DISCONNECTED,
                                    dwDisconnectMode,
                                    FALSE
                                    );

                                SetCallState(
                                    pCall,
                                    pCall->dwCallInstance,
                                    0xffffffff,
                                    LINECALLSTATE_IDLE,
                                    0,
                                    FALSE
                                    );

                                LeaveCriticalSection(
                                    &gESPGlobals.CallListCritSec
                                    );

                                goto PBXThread_droppedCall;
                            }
                        }

                        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
                    }

                    if (dwInitialAddrID != 0)
                    {
                        dwLastAddrID = dwInitialAddrID;
                        dwInitialAddrID = 0;
                        goto PBXThread_findCallToDisconnect;
                    }

                    i++;
                }

PBXThread_droppedCall:

                dwElapsedTime -= dwTimePerDisconnect;

                dwLastDisconnectTickCount = dwTickCount;
            }
        }
    }

    DrvFree (pPBXSettings);

    ShowStr (TRUE, "PBXThread: exit");

    ExitThread (0);
}


void
PASCAL
InsertVarData(
    LPVOID      lpXxx,
    LPDWORD     pdwXxxSize,
    LPVOID      pData,
    DWORD       dwDataSize
    )
{
    DWORD       dwAlignedSize, dwUsedSize;
    LPVARSTRING lpVarString = (LPVARSTRING) lpXxx;


    if (dwDataSize != 0)
    {
        //
        // Align var data on 64-bit boundaries
        //

        if ((dwAlignedSize = dwDataSize) & 7)
        {
            dwAlignedSize += 8;
            dwAlignedSize &= 0xfffffff8;

        }


        //
        // The following if statement should only be TRUE the first time
        // we're inserting data into a given structure that does not have
        // an even number of DWORD fields
        //

        if ((dwUsedSize = lpVarString->dwUsedSize) & 7)
        {
            dwUsedSize += 8;
            dwUsedSize &= 0xfffffff8;

            lpVarString->dwNeededSize += dwUsedSize - lpVarString->dwUsedSize;
        }

        lpVarString->dwNeededSize += dwAlignedSize;

        if ((dwUsedSize + dwAlignedSize) <= lpVarString->dwTotalSize)
        {
            CopyMemory(
                ((LPBYTE) lpVarString) + dwUsedSize,
                pData,
                dwDataSize
                );

            *pdwXxxSize = dwDataSize;
            pdwXxxSize++;             // pdwXxxSize = pdwXxxOffset
            *pdwXxxSize = dwUsedSize;

            lpVarString->dwUsedSize = dwUsedSize + dwAlignedSize;
        }

    }
}


void
PASCAL
InsertVarDataString(
    LPVOID      lpXxx,
    LPDWORD     pdwXxxSize,
    WCHAR      *psz
    )
{
    DWORD       dwRealSize = (lstrlenW (psz) + 1) * sizeof (WCHAR),
                dwAlignedSize;
    LPVARSTRING lpVarString = (LPVARSTRING) lpXxx;


    if (dwRealSize % 4)
    {
        dwAlignedSize = dwRealSize - (dwRealSize % 4) + 4;
    }
    else
    {
        dwAlignedSize = dwRealSize;
    }

    lpVarString->dwNeededSize += dwAlignedSize;

    if ((lpVarString->dwUsedSize + dwAlignedSize) <= lpVarString->dwTotalSize)
    {
        CopyMemory(
            ((LPBYTE) lpVarString) + lpVarString->dwUsedSize,
            psz,
            dwRealSize
            );

        *pdwXxxSize = dwRealSize;
        pdwXxxSize++;
        *pdwXxxSize = lpVarString->dwUsedSize;

        lpVarString->dwUsedSize += dwAlignedSize;
    }
}


//
// We get a slough of C4047 (different levels of indrection) warnings down
// below in the initialization of FUNC_PARAM structs as a result of the
// real func prototypes having params that are types other than DWORDs,
// so since these are known non-interesting warnings just turn them off
//

#pragma warning (disable:4047)


//
// --------------------------- TSPI_lineXxx funcs -----------------------------
//

void
FAR
PASCAL
TSPI_lineAccept_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD      dwCallInstThen = pAsyncReqInfo->dwParam2;
        PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


        pAsyncReqInfo->lResult = SetCallState(
            pCall,
            dwCallInstThen,
            LINECALLSTATE_OFFERING,
            LINECALLSTATE_ACCEPTED,
            0,
            TRUE
            );
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineAccept(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCSTR          lpsUserUserInfo,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineAccept";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpsUserUserInfo",    lpsUserUserInfo },
        { szdwSize,             dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_lineAccept_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam2))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineAddToConference_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD      dwConfCallInstThen = pAsyncReqInfo->dwParam3,
                   dwConsultCallInstThen = pAsyncReqInfo->dwParam4,
                   dwConfCallInstNow;
        PDRVCALL   pConfCall = (PDRVCALL) pAsyncReqInfo->dwParam1;
        PDRVCALL   pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam2;



        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pConfCall, &dwConfCallInstNow)  &&
            dwConfCallInstNow == dwConfCallInstThen)
        {
            if ((pAsyncReqInfo->lResult = SetCallState(
                    pConsultCall,
                    dwConsultCallInstThen,
                    0xffffffff,
                    LINECALLSTATE_CONFERENCED,
                    0,
                    TRUE

                    )) == 0)
            {
                pConsultCall->pConfParent = pConfCall;
                pConsultCall->pNextConfChild = pConfCall->pNextConfChild;

                pConfCall->pNextConfChild = pConsultCall;
            }
        }
        else
        {
            pAsyncReqInfo->lResult == LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineAddToConference(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdConfCall,
    HDRVCALL        hdConsultCall
    )
{
    static char szFuncName[] = "lineAddToConference";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { "hdConfCall",     hdConfCall      },
        { "hdConsultCall",  hdConsultCall   }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        3,
        params,
        TSPI_lineAddToConference_postProcess
    };
    PDRVCALL pConfCall = (PDRVCALL) hdConfCall;
    PDRVCALL pConsultCall = (PDRVCALL) hdConsultCall;


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdConfCall,
                &info.pAsyncReqInfo->dwParam3
                ) &&

            IsValidDrvCall(
                (PDRVCALL) hdConsultCall,
                &info.pAsyncReqInfo->dwParam4
                ))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdConfCall;
            info.pAsyncReqInfo->dwParam2 = (DWORD) hdConsultCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineAnswer_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD      dwCallInstThen = pAsyncReqInfo->dwParam2;
        PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


        pAsyncReqInfo->lResult = SetCallState(
            pCall,
            dwCallInstThen,
            LINECALLSTATE_OFFERING | LINECALLSTATE_ACCEPTED,
            LINECALLSTATE_CONNECTED,
            0,
            TRUE
            );
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineAnswer(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCSTR          lpsUserUserInfo,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineAnswer";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpsUserUserInfo",    lpsUserUserInfo },
        { szdwSize,             dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_lineAnswer_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam2))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineBlindTransfer(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCWSTR         lpszDestAddress,
    DWORD           dwCountryCode
    )
{
    static char szFuncName[] = "lineBlindTransfer";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpszDestAddress",    lpszDestAddress },
        { "dwCountryCode",      dwCountryCode   }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 4, params, NULL };


    if (Prolog (&info))
    {
        info.lResult = TransferCall(
            &info,
            (PDRVCALL) hdCall,
            LINECALLSTATE_CONNECTED | LINECALLSTATE_ONHOLD,
            LINECALLSTATE_OFFERING,
            lpszDestAddress
            );
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineClose(
    HDRVLINE    hdLine
    )
{
    static char szFuncName[] = "lineClose";
    FUNC_PARAM params[] =
    {
        { szhdLine, hdLine  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;


    //
    // This is more of a "command" than a request, in that TAPI.DLL is
    // going to consider the line closed whether we like it or not.
    // Therefore we want to free up the line even if the user chooses
    // to return an error.
    //

    Prolog (&info);

    pLine->htLine = (HTAPILINE) NULL;
    pLine->dwMediaModes = 0;

    WriteEventBuffer (pLine->dwDeviceID,  WIDGETTYPE_LINE, 0, 0, 0, 0);

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineCloseCall(
    HDRVCALL    hdCall
    )
{
    DWORD       dwCallInst;
    static char szFuncName[] = "lineCloseCall";
    FUNC_PARAM params[] =
    {
        { szhdCall, hdCall  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    //
    // This is more of a "command" than a request, in that TAPI.DLL is
    // going to consider the call closed whether we like it or not.
    // Therefore we want to free up the call even if the user chooses
    // to return an error.
    //

    Prolog (&info);

    if (IsValidDrvCall (pCall, &dwCallInst))
    {
        WriteEventBuffer(
            ((PDRVLINE) pCall->pLine)->dwDeviceID, // BUGBUG: AV potential
            WIDGETTYPE_CALL,
            (DWORD) pCall,
            0,
            0,
            0
            );

        FreeCall (pCall, dwCallInst);
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineCompleteCall(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPDWORD         lpdwCompletionID,
    DWORD           dwCompletionMode,
    DWORD           dwMessageID
    )
{
    static char szFuncName[] = "lineCompleteCall";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID         },
        { szhdCall,             hdCall              },
        { "lpdwCompletionID",   lpdwCompletionID    },
        { "dwCompletionMode",   dwCompletionMode    },
        { "dwMessageID",        dwMessageID         }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 5, params, NULL };


    if (Prolog (&info))
    {
        if (dwMessageID >= MAX_NUM_COMPLETION_MESSAGES)
        {
            info.lResult = LINEERR_INVALMESSAGEID;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineCompleteTransfer_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD      dwCallInstThen        = pAsyncReqInfo->dwParam1,
               dwConsultCallInstThen = pAsyncReqInfo->dwParam2,
               dwConfCallInstThen    = pAsyncReqInfo->dwParam6,
               dwCallInstNow, dwConsultCallInstNow, dwConfCallInstNow;
    PDRVCALL   pCall        = (PDRVCALL) pAsyncReqInfo->dwParam3,
               pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam4,
               pConfCall    = (PDRVCALL) pAsyncReqInfo->dwParam5;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, &dwCallInstNow)  &&
            dwCallInstNow == dwCallInstThen  &&
            IsValidDrvCall (pConsultCall, &dwConsultCallInstNow)  &&
            dwConsultCallInstNow == dwConsultCallInstThen)
        {
            if (pConfCall)
            {
                if (IsValidDrvCall (pConfCall, &dwConfCallInstNow)  &&
                    dwConfCallInstNow == dwConfCallInstThen)
                {
                    pConfCall->pNextConfChild = pCall;
                    pCall->pNextConfChild     = pConsultCall;
                    pCall->pConfParent        = pConfCall;
                    pConsultCall->pConfParent = pConfCall;
                }
                else
                {
                    pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;
                }
            }
        }
        else
        {
            pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;

            if (pConfCall)
            {
                FreeCall (pConfCall, dwConfCallInstThen);
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        if (pConfCall)
        {
            if (SetCallState(
                    pConfCall,
                    dwConfCallInstNow,
                    0xffffffff, // BUGBUG specify valid call states
                    LINECALLSTATE_CONNECTED,
                    0,
                    TRUE

                    ) == 0)
            {
                SetCallState(
                    pCall,
                    dwCallInstNow,
                    0xffffffff, // BUGBUG specify valid call states
                    LINECALLSTATE_CONFERENCED,
                    0,
                    TRUE
                    );

                SetCallState(
                    pConsultCall,
                    dwConsultCallInstNow,
                    0xffffffff, // BUGBUG specify valid call states
                    LINECALLSTATE_CONFERENCED,
                    0,
                    TRUE
                    );
            }
        }
        else
        {
            SetCallState(
                pCall,
                dwCallInstNow,
                0xffffffff, // BUGBUG specify valid call states
                LINECALLSTATE_IDLE,
                0,
                TRUE
                );

            SetCallState(
                pConsultCall,
                dwConsultCallInstNow,
                0xffffffff, // BUGBUG specify valid call states
                LINECALLSTATE_IDLE,
                0,
                TRUE
                );
        }
    }
}


LONG
TSPIAPI
TSPI_lineCompleteTransfer(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    HDRVCALL        hdConsultCall,
    HTAPICALL       htConfCall,
    LPHDRVCALL      lphdConfCall,
    DWORD           dwTransferMode
    )
{
    static char szFuncName[] = "lineCompleteTransfer";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdCall,         hdCall          },
        { "hdConsultCall",  hdConsultCall   },
        { "htConfCall",     htConfCall      },
        { "lphdConfCall",   lphdConfCall    },
        { "dwTransferMode", dwTransferMode, aTransferModes  }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params,
        TSPI_lineCompleteTransfer_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdCall,
                &info.pAsyncReqInfo->dwParam1
                ) &&

            IsValidDrvCall(
                (PDRVCALL) hdConsultCall,
                &info.pAsyncReqInfo->dwParam2
                ))
        {
            info.pAsyncReqInfo->dwParam3 = (DWORD) hdCall;
            info.pAsyncReqInfo->dwParam4 = (DWORD) hdConsultCall;

            if (dwTransferMode == LINETRANSFERMODE_CONFERENCE)
            {
                LONG        lResult;
                PDRVCALL    pConfCall;
                PDRVLINE    pLine = ((PDRVCALL) hdCall)->pLine; // BUGBUG: AV


                if ((lResult = AllocCall(
                        pLine,
                        htConfCall,
                        NULL,
                        &pConfCall

                        )) == 0)
                {
                    *lphdConfCall = (HDRVCALL) pConfCall;
                    info.pAsyncReqInfo->dwParam5 = (DWORD) pConfCall;
                    info.pAsyncReqInfo->dwParam6 = pConfCall->dwCallInstance;
                }
                else
                {
                    info.lResult = lResult;
                }
            }
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineConditionalMediaDetection(
    HDRVLINE            hdLine,
    DWORD               dwMediaModes,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    static char szFuncName[] = "lineConditionalMediaDetection";
    FUNC_PARAM params[] =
    {
        { szhdLine,         hdLine                      },
        { "dwMediaModes",   dwMediaModes,   aMediaModes },
        { szlpCallParams,   lpCallParams                }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineDevSpecific_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    LPVOID lpParams = pAsyncReqInfo->dwParam1;
    DWORD  dwSize   = pAsyncReqInfo->dwParam2;


    if (pAsyncReqInfo->lResult == 0 && dwSize >= 22)
    {
        lstrcpyA (lpParams, "ESP dev specific info");
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineDevSpecific(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwAddressID,
    HDRVCALL        hdCall,
    LPVOID          lpParams,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineDevSpecific";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdLine,         hdLine          },
        { "dwAddressID",    dwAddressID     },
        { szhdCall,         hdCall          },
        { "lpParams",       lpParams        },
        { szdwSize,         dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params
    };
    PESPDEVSPECIFICINFO pInfo = (PESPDEVSPECIFICINFO) lpParams;


    if (Prolog (&info))
    {
        if (dwSize >= sizeof (ESPDEVSPECIFICINFO)  &&
            pInfo->dwKey == ESPDEVSPECIFIC_KEY)
        {
            switch (pInfo->dwType)
            {
            case ESP_DEVSPEC_MSG:

                switch (pInfo->u.EspMsg.dwMsg)
                {
                case LINE_ADDRESSSTATE:
                case LINE_CLOSE:
                case LINE_DEVSPECIFIC:
                case LINE_DEVSPECIFICFEATURE:
                case LINE_LINEDEVSTATE:

                    SendLineEvent(
                        (PDRVLINE) hdLine,
                        NULL,
                        pInfo->u.EspMsg.dwMsg,
                        pInfo->u.EspMsg.dwParam1,
                        pInfo->u.EspMsg.dwParam2,
                        pInfo->u.EspMsg.dwParam3
                        );

                    break;

                case LINE_CALLDEVSPECIFIC:
                case LINE_CALLDEVSPECIFICFEATURE:
                case LINE_CALLINFO:
                case LINE_MONITORDIGITS:
                case LINE_MONITORMEDIA:

                    if (hdCall)
                    {
                        SendLineEvent(
                            (PDRVLINE) hdLine,
                            (PDRVCALL) hdCall,
                            pInfo->u.EspMsg.dwMsg,
                            pInfo->u.EspMsg.dwParam1,
                            pInfo->u.EspMsg.dwParam2,
                            pInfo->u.EspMsg.dwParam3
                            );
                    }
                    else
                    {
                        info.lResult = LINEERR_OPERATIONFAILED;
                    }

                    break;

                case LINE_GATHERDIGITS:

                    if (hdCall)
                    {
                        DWORD       dwEndToEndID = 0;
                        PDRVCALL    pCall = (PDRVCALL) hdCall;


                        EnterCriticalSection (&gESPGlobals.CallListCritSec);

                        if (IsValidDrvCall (pCall, NULL))
                        {
                            if ((dwEndToEndID =
                                    pCall->dwGatherDigitsEndToEndID))
                            {
                                pCall->dwGatherDigitsEndToEndID = 0;
                            }
                            else
                            {
                                info.lResult = LINEERR_OPERATIONFAILED;
                            }
                        }
                        else
                        {
                            info.lResult = LINEERR_INVALCALLHANDLE;
                        }

                        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

                        if (dwEndToEndID)
                        {
                            SendLineEvent(
                                (PDRVLINE) hdLine,
                                (PDRVCALL) hdCall,
                                LINE_GATHERDIGITS,
                                pInfo->u.EspMsg.dwParam1,
                                dwEndToEndID,
                                0
                                );
                        }
                        else if (info.lResult == LINEERR_OPERATIONFAILED)
                        {
                            ShowStr(
                                TRUE,
                                "ERROR: TSPI_lineDevSpecific: attempt to " \
                                "send GATHERDIGITS msg with no " \
                                "lineGatherDigits request pending"
                                );
                        }
                    }
                    else
                    {
                        info.lResult = LINEERR_OPERATIONFAILED;
                    }

                    break;

                case LINE_GENERATE:

                    if (hdCall)
                    {
                        DWORD       dwEndToEndID = 0, *pdwXxxEndToEndID;
                        PDRVCALL    pCall = (PDRVCALL) hdCall;


                        EnterCriticalSection (&gESPGlobals.CallListCritSec);

                        if (IsValidDrvCall (pCall, NULL))
                        {
                            pdwXxxEndToEndID = (pInfo->u.EspMsg.dwParam3 ?
                                &pCall->dwGenerateToneEndToEndID :
                                &pCall->dwGenerateDigitsEndToEndID
                                );

                            if ((dwEndToEndID = *pdwXxxEndToEndID))
                            {
                                *pdwXxxEndToEndID = 0;
                            }
                            else
                            {
                                info.lResult = LINEERR_OPERATIONFAILED;
                            }
                        }

                        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

                        if (dwEndToEndID)
                        {
                            SendLineEvent(
                                (PDRVLINE) hdLine,
                                (PDRVCALL) hdCall,
                                LINE_GENERATE,
                                pInfo->u.EspMsg.dwParam1,
                                dwEndToEndID,
                                0
                                );
                        }
                        else if (info.lResult == LINEERR_OPERATIONFAILED)
                        {
                            ShowStr(
                                TRUE,
                                "ERROR: TSPI_lineDevSpecific: attempt to " \
                                "send GENERATE msg with no " \
                                "lineGenerateXxx request pending"
                                );
                        }
                    }
                    else
                    {
                        info.lResult = LINEERR_OPERATIONFAILED;
                    }

                    break;

                case LINE_MONITORTONE:

                    if (hdCall)
                    {
                        DWORD       dwToneListID = 0;
                        PDRVCALL    pCall = (PDRVCALL) hdCall;


                        EnterCriticalSection (&gESPGlobals.CallListCritSec);

                        if (IsValidDrvCall (pCall, NULL))
                        {
                            if ((dwToneListID =
                                    pCall->dwMonitorToneListID))
                            {
                                pCall->dwMonitorToneListID = 0;
                            }
                            else
                            {
                                info.lResult = LINEERR_OPERATIONFAILED;
                            }
                        }
                        else
                        {
                            info.lResult = LINEERR_INVALCALLHANDLE;
                        }

                        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

                        if (dwToneListID)
                        {
                            SendLineEvent(
                                (PDRVLINE) hdLine,
                                (PDRVCALL) hdCall,
                                LINE_MONITORTONE,
                                pInfo->u.EspMsg.dwParam1,
                                dwToneListID,
                                0
                                );
                        }
                        else if (info.lResult == LINEERR_OPERATIONFAILED)
                        {
                            ShowStr(
                                TRUE,
                                "ERROR: TSPI_lineDevSpecific: attempt to " \
                                "send MONITORTONE msg with no " \
                                "lineMonitorTone request pending"
                                );
                        }
                    }
                    else
                    {
                        info.lResult = LINEERR_OPERATIONFAILED;
                    }

                    break;

                case LINE_CALLSTATE:
                {
                    DWORD   dwCallInst;


                    if (hdCall  &&
                        IsValidDrvCall ((PDRVCALL) hdCall, &dwCallInst))
                    {
                        LONG lResult;


                        // BUGBUG changing to/from conf state cause ptr probs?
                        // BUGBUG check for bad call state vals

                        if ((lResult = SetCallState(
                                (PDRVCALL) hdCall,
                                dwCallInst,
                                0xffffffff,
                                pInfo->u.EspMsg.dwParam1,
                                pInfo->u.EspMsg.dwParam2,
                                TRUE

                                )) != 0)
                        {
                            info.lResult = lResult;
                        }
                    }
                    else
                    {
                        info.lResult = LINEERR_OPERATIONFAILED;
                    }

                    break;
                }
                case LINE_CREATE: // BUGBUG

                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_lineDevSpecific: no support " \
                            "for indicating LINE_CREATE yet"
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                    break;

                case LINE_NEWCALL: // BUGBUG

                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_lineDevSpecific: no support " \
                            "for indicating LINE_NEWCALL yet"
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                    break;

                default:

                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_lineDevSpecific: unrecognized " \
                            "ESPDEVSPECIFICINFO.u.EspMsg.dwMsg (=x%x)",
                        pInfo->u.EspMsg.dwMsg
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                    break;
                }

                break;

            case ESP_DEVSPEC_RESULT:
            {
                DWORD   dwResult = pInfo->u.EspResult.lResult;


                if (dwResult != 0  &&
                    (dwResult < LINEERR_ALLOCATED ||
                    dwResult > PHONEERR_REINIT ||
                    (dwResult > LINEERR_DIALVOICEDETECT &&
                    dwResult < PHONEERR_ALLOCATED)))
                {
                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_lineDevSpecific: invalid request" \
                            "result value (x%x)",
                        dwResult
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                }
                else if (pInfo->u.EspResult.dwCompletionType >
                            ESP_RESULT_CALLCOMPLPROCASYNC)
                {
                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_lineDevSpecific: invalid request" \
                            "completion type (x%x)",
                        pInfo->u.EspResult.dwCompletionType
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                }
                else
                {
                    glNextRequestResult = (LONG) dwResult;
                    gdwNextRequestCompletionType =
                        pInfo->u.EspResult.dwCompletionType;
                    gdwDevSpecificRequestID = dwRequestID;
                }

                break;
            }
            default:

                ShowStr(
                    TRUE,
                    "ERROR: TSPI_lineDevSpecific: unrecognized " \
                        "ESPDEVSPECIFICINFO.dwType (=x%x)",
                    pInfo->dwType
                    );

                info.lResult = LINEERR_OPERATIONFAILED;
                break;
            }
        }
        else
        {
            info.pAsyncReqInfo->dwParam1 = lpParams;
            info.pAsyncReqInfo->dwParam2 = dwSize;

            info.pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
                TSPI_lineDevSpecific_postProcess;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineDevSpecificFeature(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwFeature,
    LPVOID          lpParams,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineDevSpecificFeature";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdLine,         hdLine          },
        { "dwFeature",      dwFeature       },
        { "lpParams",       lpParams        },
        { szdwSize,         dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        5,
        params,
        TSPI_lineDevSpecific_postProcess
    };


    if (Prolog (&info))
    {
        info.pAsyncReqInfo->dwParam1 = lpParams;
        info.pAsyncReqInfo->dwParam2 = dwSize;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineDial(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCWSTR         lpszDestAddress,
    DWORD           dwCountryCode
    )
{
    static char szFuncName[] = "lineDial";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpszDestAddress",    lpszDestAddress },
        { "dwCountryCode",      dwCountryCode   }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 4, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineDrop_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if ((pAsyncReqInfo->lResult == 0))
    {
        DWORD       dwCallInstThen = pAsyncReqInfo->dwParam2;
        PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


        //
        // We need to make sure pCall is pointing at a valid call
        // structure because it's possible that tapi immediately
        // followed the drop request with a closeCall request
        // (without waiting for the result from the drop)
        //

        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (SetCallState(
                pCall,
                dwCallInstThen,
                0xffffffff,
                LINECALLSTATE_IDLE,
                0,
                TRUE

                ) == 0)
        {
            if (pCall->pConfParent)
            {
                //
                // Call is a conf child, so remove from conf list
                //

                PDRVCALL    pCall2 = pCall->pConfParent;


                while (pCall2 && (pCall2->pNextConfChild != pCall))
                {
                    pCall2 = pCall2->pNextConfChild;
                }

                if (pCall2)
                {
                    pCall2->pNextConfChild = pCall->pNextConfChild;
                }

                pCall->pConfParent = NULL;
            }
            else if (pCall->pNextConfChild)
            {
                //
                // Call is a conf parent, so IDLE-ize all children &
                // remove them from list
                //

                PDRVCALL    pConfChild = pCall->pNextConfChild;


                pCall->pNextConfChild = NULL;

                while (pConfChild)
                {
                    PDRVCALL    pNextConfChild = pConfChild->pNextConfChild;


                    pConfChild->pConfParent =
                    pConfChild->pNextConfChild = NULL;

                    SetCallState(
                        pConfChild,
                        pConfChild->dwCallInstance,
                        0xffffffff,
                        LINECALLSTATE_IDLE,
                        0,
                        TRUE
                        );

                    pConfChild = pNextConfChild;
                }
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineDrop(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCSTR          lpsUserUserInfo,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineDrop";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpsUserUserInfo",    lpsUserUserInfo },
        { szdwSize,             dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_lineDrop_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam2))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineForward_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwConsultCallInstThen = pAsyncReqInfo->dwParam2;
    PDRVCALL    pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        SetCallState(
            pConsultCall,
            dwConsultCallInstThen,
            0xffffffff, // BUGBUG specify valid call states
            LINECALLSTATE_CONNECTED,
            0,
            TRUE
            );
    }
    else
    {
        FreeCall (pConsultCall, dwConsultCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_lineForward(
    DRV_REQUESTID       dwRequestID,
    HDRVLINE            hdLine,
    DWORD               bAllAddresses,
    DWORD               dwAddressID,
    LPLINEFORWARDLIST   const lpForwardList,
    DWORD               dwNumRingsNoAnswer,
    HTAPICALL           htConsultCall,
    LPHDRVCALL          lphdConsultCall,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    static char szFuncName[] = "lineForward";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID         },
        { szhdLine,             hdLine              },
        { "bAllAddresses",      bAllAddresses       },
        { "dwAddressID",        dwAddressID         },
        { "lpForwardList",      lpForwardList       },
        { "dwNumRingsNoAnswer", dwNumRingsNoAnswer  },
        { "htConsultCall",      htConsultCall       },
        { "lphdConsultCall",    lphdConsultCall     },
        { szlpCallParams,       lpCallParams        }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        9,
        params,
        TSPI_lineForward_postProcess
    };


    if (Prolog (&info))
    {
        if (bAllAddresses  ||
            dwAddressID < gESPGlobals.dwNumAddressesPerLine)
        {
            if (lpForwardList)
            {
                LONG        lResult;
                PDRVCALL    pConsultCall;


                if ((lResult = AllocCall(
                        (PDRVLINE) hdLine,
                        htConsultCall,
                        lpCallParams,
                        &pConsultCall

                        )) == 0)
                {
                    info.pAsyncReqInfo->dwParam1 = (DWORD) pConsultCall;
                    info.pAsyncReqInfo->dwParam2 = pConsultCall->dwCallInstance;

                    *lphdConsultCall = (HDRVCALL) pConsultCall;
                }
                else
                {
                    info.lResult = lResult;
                }
            }
            else
            {
                info.pAsyncReqInfo->pfnPostProcessProc = NULL;
                *lphdConsultCall = (HDRVCALL) NULL;
            }
        }
        else
        {
            info.lResult = LINEERR_INVALADDRESSID;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGatherDigits(
    HDRVCALL    hdCall,
    DWORD       dwEndToEndID,
    DWORD       dwDigitModes,
    LPWSTR      lpsDigits,
    DWORD       dwNumDigits,
    LPCWSTR     lpszTerminationDigits,
    DWORD       dwFirstDigitTimeout,
    DWORD       dwInterDigitTimeout
    )
{
    static char szFuncName[] = "lineGatherDigits";
    FUNC_PARAM params[] =
    {
        { szhdCall,                 hdCall                  },
        { "dwEndToEndID",           dwEndToEndID            },
        { "dwDigitModes",           dwDigitModes,   aDigitModes },
        { "lpsDigits",              lpsDigits               },
        { "dwNumDigits",            dwNumDigits             },
        { "lpszTerminationDigits",  lpszTerminationDigits   },
        { "dwFirstDigitTimeout",    dwFirstDigitTimeout     },
        { "dwInterDigitTimeout",    dwInterDigitTimeout     }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 8, params };
    PDRVCALL    pCall = (PDRVCALL) hdCall;
    DWORD       dwReason = 0;
    HTAPILINE   htLine;
    HTAPICALL   htCall;


    if (Prolog (&info))
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            htLine = ((PDRVLINE) pCall->pLine)->htLine;
            htCall = pCall->htCall;

            if (lpsDigits)
            {
                lstrcpynW (lpsDigits, L"1234567890", dwNumDigits);

                if (dwNumDigits == 10)
                {
                    lpsDigits[9] = L'0';
                }
            }

            if (gbAutoGatherGenerateMsgs)
            {
                if (lpsDigits)
                {
                    dwReason = (dwNumDigits > 10 ? LINEGATHERTERM_INTERTIMEOUT
                        : LINEGATHERTERM_BUFFERFULL);
                }
            }
            else
            {
                DWORD dwEndToEndIDTmp = dwEndToEndID;


                if ((dwEndToEndID = pCall->dwGatherDigitsEndToEndID))
                {
                    dwReason = LINEGATHERTERM_CANCEL;
                }

                pCall->dwGatherDigitsEndToEndID = (lpsDigits ?
                    dwEndToEndIDTmp : 0);
            }
        }
        else
        {
            htLine = (HTAPILINE) (htCall = (HTAPICALL) NULL);
            dwReason = (lpsDigits ? LINEGATHERTERM_CANCEL : 0);
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        if (dwReason)
        {
            (gESPGlobals.pfnLineEvent)(
                htLine,
                htCall,
                LINE_GATHERDIGITS,
                dwReason,
                dwEndToEndID,
                0
                );
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGenerateDigits(
    HDRVCALL    hdCall,
    DWORD       dwEndToEndID,
    DWORD       dwDigitMode,
    LPCWSTR     lpszDigits,
    DWORD       dwDuration
    )
{
    static char szFuncName[] = "lineGenerateDigits";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "dwEndToEndID",   dwEndToEndID    },
        { "dwDigitMode",    dwDigitMode,    aDigitModes },
        { "lpszDigits",     lpszDigits      },
        { "dwDuration",     dwDuration      }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 5, params };
    PDRVCALL    pCall = (PDRVCALL) hdCall;
    HTAPILINE   htLine;
    HTAPICALL   htCall;
    DWORD       dwReason = 0;


    if (Prolog (&info))
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            htLine = ((PDRVLINE) pCall->pLine)->htLine;
            htCall = pCall->htCall;

            if (gbAutoGatherGenerateMsgs)
            {
                dwReason = (lpszDigits ? LINEGENERATETERM_DONE : 0);
            }
            else
            {
                DWORD dwEndToEndIDTmp = dwEndToEndID;


                if ((dwEndToEndID = pCall->dwGenerateDigitsEndToEndID))
                {
                    dwReason = LINEGENERATETERM_CANCEL;
                }

                pCall->dwGenerateDigitsEndToEndID = (lpszDigits ?
                    dwEndToEndIDTmp : 0);
            }
        }
        else
        {
            htLine = (HTAPILINE) (htCall = (HTAPICALL) NULL);
            dwReason = (lpszDigits ? LINEGENERATETERM_CANCEL : 0);
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        if (dwReason)
        {
            (gESPGlobals.pfnLineEvent)(
                htLine,
                htCall,
                LINE_GENERATE,
                dwReason,
                dwEndToEndID,
                0
                );
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGenerateTone(
    HDRVCALL            hdCall,
    DWORD               dwEndToEndID,
    DWORD               dwToneMode,
    DWORD               dwDuration,
    DWORD               dwNumTones,
    LPLINEGENERATETONE  const lpTones
    )
{
    static char szFuncName[] = "lineGenerateTone";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "dwEndToEndID",   dwEndToEndID    },
        { "dwToneMode",     dwToneMode, aToneModes  },
        { "dwDuration",     dwDuration      },
        { "dwNumTones",     dwNumTones      },
        { "lpTones",        lpTones         }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 6, params };
    PDRVCALL    pCall = (PDRVCALL) hdCall;
    HTAPILINE   htLine;
    HTAPICALL   htCall;
    DWORD       dwReason = 0;


    if (Prolog (&info))
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            htLine = ((PDRVLINE) pCall->pLine)->htLine;
            htCall = pCall->htCall;

            if (gbAutoGatherGenerateMsgs)
            {
                dwReason = (dwToneMode ? LINEGENERATETERM_DONE : 0);
            }
            else
            {
                DWORD dwEndToEndIDTmp = dwEndToEndID;


                if ((dwEndToEndID = pCall->dwGenerateToneEndToEndID))
                {
                    dwReason = LINEGENERATETERM_CANCEL;
                }

                pCall->dwGenerateToneEndToEndID = (dwToneMode ?
                    dwEndToEndIDTmp : 0);
            }
        }
        else
        {
            htLine = (HTAPILINE) (htCall = (HTAPICALL) NULL);
            dwReason = (dwToneMode ? LINEGENERATETERM_CANCEL : 0);
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        if (dwReason)
        {
            (gESPGlobals.pfnLineEvent)(
                htLine,
                htCall,
                LINE_GENERATE,
                dwReason,
                dwEndToEndID,
                0
                );
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetAddressCaps(
    DWORD              dwDeviceID,
    DWORD              dwAddressID,
    DWORD              dwTSPIVersion,
    DWORD              dwExtVersion,
    LPLINEADDRESSCAPS  lpAddressCaps
    )
{
    static char szFuncName[] = "lineGetAddressCaps";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwAddressID",    dwAddressID     },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "dwExtVersion",   dwExtVersion    },
        { "lpAddressCaps",  lpAddressCaps   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };
    DWORD dwUsedSize;
    PDRVLINE pLine = GetLineFromID (dwDeviceID);


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    if (dwAddressID >= gESPGlobals.dwNumAddressesPerLine)
    {
        info.lResult = LINEERR_INVALADDRESSID;
        return (Epilog (&info));
    }

    //lpAddressCaps->dwTotalSize
    //lpAddressCaps->dwNeededSize
    //lpAddressCaps->dwUsedSize
    //lpAddressCaps->dwLineDeviceID
    //
    //lpAddressCaps->dwAddressOffset

    {
        WCHAR buf[20];


        wsprintfW (buf, L"%d#%d", dwDeviceID, dwAddressID);

        InsertVarDataString(
            lpAddressCaps,
            &lpAddressCaps->dwAddressSize,
            buf
            );
    }

    //lpAddressCaps->dwDevSpecificSize
    //lpAddressCaps->dwDevSpecificOffset
    //lpAddressCaps->dwAddressSharing
    //lpAddressCaps->dwAddressStates
    //lpAddressCaps->dwCallInfoStates
    //lpAddressCaps->dwCallerIDFlags
    //lpAddressCaps->dwCalledIDFlags
    //lpAddressCaps->dwConnectedIDFlags
    //lpAddressCaps->dwRedirectionIDFlags
    //lpAddressCaps->dwRedirectingIDFlags
    //lpAddressCaps->dwCallStates
    //lpAddressCaps->dwDialToneModes
    //lpAddressCaps->dwBusyModes
    //lpAddressCaps->dwSpecialInfo
    //lpAddressCaps->dwDisconnectModes
    lpAddressCaps->dwMaxNumActiveCalls = gESPGlobals.dwNumCallsPerAddress;
    //lpAddressCaps->dwMaxNumOnHoldCalls
    //lpAddressCaps->dwMaxNumOnHoldPendingCalls
    //lpAddressCaps->dwMaxNumConference
    //lpAddressCaps->dwMaxNumTransConf
    lpAddressCaps->dwAddrCapFlags = AllAddrCaps1_0;
    lpAddressCaps->dwCallFeatures = AllCallFeatures1_0;
    //lpAddressCaps->dwRemoveFromConfCaps
    //lpAddressCaps->dwRemoveFromConfState
    //lpAddressCaps->dwTransferModes
    //lpAddressCaps->dwParkModes
    //lpAddressCaps->dwForwardModes
    //lpAddressCaps->dwMaxForwardEntries
    //lpAddressCaps->dwMaxSpecificEntries
    //lpAddressCaps->dwMinFwdNumRings
    //lpAddressCaps->dwMaxFwdNumRings
    //lpAddressCaps->dwMaxCallCompletions
    //lpAddressCaps->dwCallCompletionConds
    //lpAddressCaps->dwCallCompletionModes
    lpAddressCaps->dwNumCompletionMessages = MAX_NUM_COMPLETION_MESSAGES;
    //lpAddressCaps->dwCompletionMsgTextEntrySize
    //lpAddressCaps->dwCompletionMsgTextSize
    //lpAddressCaps->dwCompletionMsgTextOffset

    if (dwTSPIVersion >= 0x00010004)
    {
        lpAddressCaps->dwCallFeatures = AllCallFeatures1_4;

        lpAddressCaps->dwAddressFeatures = AllAddrFeatures1_0;

        if (dwTSPIVersion >= 0x00020000)
        {
            lpAddressCaps->dwAddrCapFlags = AllAddrCaps2_0;
            lpAddressCaps->dwCallFeatures = AllCallFeatures2_0;
            lpAddressCaps->dwAddressFeatures = AllAddrFeatures2_0;

            //lpAddressCaps->dwPredictiveAutoTransferStates
            //lpAddressCaps->dwNumCallTreatments
            //lpAddressCaps->dwCallTreatmentListSize
            //lpAddressCaps->dwCallTreatmentListOffset
            //lpAddressCaps->dwDeviceClassesSize
            //lpAddressCaps->dwDeviceClassesOffset
            //lpAddressCaps->dwMaxCallDataSize
            lpAddressCaps->dwCallFeatures2 = AllCallFeaturesTwo;
            //lpAddressCaps->dwMaxNoAnswerTimeout
            //lpAddressCaps->dwConnectedModes
            //lpAddressCaps->dwOfferingModes
            lpAddressCaps->dwAvailableMediaModes = AllMediaModes1_4;
        }
    }


    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetAddressID(
    HDRVLINE    hdLine,
    LPDWORD     lpdwAddressID,
    DWORD       dwAddressMode,
    LPCWSTR     lpsAddress,
    DWORD       dwSize
    )
{
    static char szFuncName[] = "lineGetAddressID";
    FUNC_PARAM params[] =
    {
        { szhdLine,         hdLine          },
        { "lpdwAddressID",  lpdwAddressID   },
        { "dwAddressMode",  dwAddressMode   },
        { "lpsAddress",     lpsAddress      },
        { szdwSize,         dwSize          }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetAddressStatus(
    HDRVLINE            hdLine,
    DWORD               dwAddressID,
    LPLINEADDRESSSTATUS lpAddressStatus
    )
{
    static char szFuncName[] = "lineGetAddressStatus";
    FUNC_PARAM params[] =
    {
        { szhdLine,             hdLine          },
        { "dwAddressID",        dwAddressID     },
        { "lpAddressStatus",    lpAddressStatus }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    if (dwAddressID >= gESPGlobals.dwNumAddressesPerLine)
    {
        info.lResult = LINEERR_INVALADDRESSID;
        return (Epilog (&info));
    }

    //lpAddressStatus->dwNeededSize
    //lpAddressStatus->dwUsedSize
    //lpAddressStatus->dwNumInUse

    if (pLine->aAddrs[dwAddressID].dwNumCalls != 0)
    {
        PDRVCALL    pCall;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        for(
            pCall = pLine->aAddrs[dwAddressID].pCalls;
            pCall != NULL;
            pCall = pCall->pNext
            )
        {
            switch (pCall->dwCallState)
            {
            case LINECALLSTATE_IDLE:

                continue;

            case LINECALLSTATE_ONHOLD:

                lpAddressStatus->dwNumOnHoldCalls++;
                continue;

            case LINECALLSTATE_ONHOLDPENDCONF:
            case LINECALLSTATE_ONHOLDPENDTRANSFER:

                lpAddressStatus->dwNumOnHoldPendCalls++;
                continue;

            default:

                lpAddressStatus->dwNumActiveCalls++;
                continue;
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    lpAddressStatus->dwAddressFeatures = (gESPGlobals.dwSPIVersion > 0x10004 ?
        AllAddrFeatures1_0 : AllAddrFeatures2_0);
    //lpAddressStatus->dwNumRingsNoAnswer
    //lpAddressStatus->dwForwardNumEntries
    //lpAddressStatus->dwForwardSize
    //lpAddressStatus->dwForwardOffset
    //lpAddressStatus->dwTerminalModesSize
    //lpAddressStatus->dwTerminalModesOffset
    //lpAddressStatus->dwDevSpecificSize
    //lpAddressStatus->dwDevSpecificOffset

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetCallAddressID(
    HDRVCALL    hdCall,
    LPDWORD     lpdwAddressID
    )
{
    static char szFuncName[] = "lineGetCallAddressID";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "lpdwAddressID",  lpdwAddressID   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
        *lpdwAddressID = ((PDRVCALL) hdCall)->dwAddressID;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetCallInfo(
    HDRVCALL        hdCall,
    LPLINECALLINFO  lpCallInfo
    )
{
    static char szFuncName[] = "lineGetCallInfo";
    FUNC_PARAM params[] =
    {
        { szhdCall,     hdCall      },
        { "lpCallInfo", lpCallInfo  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVCALL  pCall = (PDRVCALL) hdCall;
    DWORD     dwUsedSize;


    if (Prolog (&info))
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            //lpCallInfo->dwNeededSize
            //lpCallInfo->dwUsedSize
            lpCallInfo->dwLineDeviceID = ((PDRVLINE) pCall->pLine)->dwDeviceID;
            lpCallInfo->dwAddressID    = pCall->dwAddressID;
            lpCallInfo->dwBearerMode   = pCall->dwBearerMode;
            //lpCallInfo->dwRate
            lpCallInfo->dwMediaMode    = pCall->dwMediaMode;
            lpCallInfo->dwAppSpecific  = pCall->dwAppSpecific;
            lpCallInfo->dwCallID       = pCall->dwCallID;
            //lpCallInfo->dwRelatedCallID
            //lpCallInfo->dwCallParamFlags
            //lpCallInfo->dwCallStates

            CopyMemory(
                &lpCallInfo->DialParams,
                &pCall->DialParams,
                sizeof(LINEDIALPARAMS)
                );

            //lpCallInfo->dwOrigin
            //lpCallInfo->dwReason
            //lpCallInfo->dwCompletionID
            //lpCallInfo->dwCountryCode
            //lpCallInfo->dwTrunk
            //lpCallInfo->dwCallerIDFlags
            //lpCallInfo->dwCallerIDSize
            //lpCallInfo->dwCallerIDOffset
            //lpCallInfo->dwCallerIDNameSize
            //lpCallInfo->dwCallerIDNameOffset
            //lpCallInfo->dwCalledIDFlags
            //lpCallInfo->dwCalledIDSize
            //lpCallInfo->dwCalledIDOffset
            //lpCallInfo->dwCalledIDNameSize
            //lpCallInfo->dwCalledIDNameOffset
            //lpCallInfo->dwConnectedIDFlags
            //lpCallInfo->dwConnectedIDSize
            //lpCallInfo->dwConnectedIDOffset
            //lpCallInfo->dwConnectedIDNameSize
            //lpCallInfo->dwConnectedIDNameOffset
            //lpCallInfo->dwRedirectionIDFlags
            //lpCallInfo->dwRedirectionIDSize
            //lpCallInfo->dwRedirectionIDOffset
            //lpCallInfo->dwRedirectionIDNameSize
            //lpCallInfo->dwRedirectionIDNameOffset
            //lpCallInfo->dwRedirectingIDFlags
            //lpCallInfo->dwRedirectingIDSize
            //lpCallInfo->dwRedirectingIDOffset
            //lpCallInfo->dwRedirectingIDNameSize
            //lpCallInfo->dwRedirectingIDNameOffset
            //lpCallInfo->dwDisplaySize
            //lpCallInfo->dwDisplayOffset
            //lpCallInfo->dwUserUserInfoSize
            //lpCallInfo->dwUserUserInfoOffset
            //lpCallInfo->dwHighLevelCompSize
            //lpCallInfo->dwHighLevelCompOffset
            //lpCallInfo->dwLowLevelCompSize
            //lpCallInfo->dwLowLevelCompOffset
            //lpCallInfo->dwChargingInfoSize
            //lpCallInfo->dwChargingInfoOffset
            //lpCallInfo->dwTerminalModesSize
            //lpCallInfo->dwTerminalModesOffset
            //lpCallInfo->dwDevSpecificSize
            //lpCallInfo->dwDevSpecificOffset

            if (gESPGlobals.dwSPIVersion >= 0x00020000)
            {
                lpCallInfo->dwCallTreatment = pCall->dwTreatment;

                InsertVarData(
                    lpCallInfo,
                    &lpCallInfo->dwCallDataSize,
                    pCall->pCallData,
                    pCall->dwCallDataSize
                    );

                InsertVarData(
                    lpCallInfo,
                    &lpCallInfo->dwSendingFlowspecSize,
                    pCall->pSendingFlowspec,
                    pCall->dwSendingFlowspecSize
                    );

                InsertVarData(
                    lpCallInfo,
                    &lpCallInfo->dwReceivingFlowspecSize,
                    pCall->pReceivingFlowspec,
                    pCall->dwReceivingFlowspecSize
                    );
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetCallStatus(
    HDRVCALL            hdCall,
    LPLINECALLSTATUS    lpCallStatus
    )
{
    DWORD   dwCallState, dwCallStateMode;
    static char szFuncName[] = "lineGetCallStatus";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "lpCallStatus",   lpCallStatus    }
    };
    PDRVCALL  pCall = (PDRVCALL) hdCall;
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    EnterCriticalSection (&gESPGlobals.CallListCritSec);

    if (IsValidDrvCall (pCall, NULL))
    {
        dwCallState     = pCall->dwCallState;
        dwCallStateMode = pCall->dwCallStateMode;

    }
    else
    {
        info.lResult = LINEERR_INVALCALLHANDLE;
    }

    LeaveCriticalSection (&gESPGlobals.CallListCritSec);

    if (info.lResult == 0)
    {
        //lpCallStatus->dwNeededSize
        //lpCallStatus->dwUsedSize
        lpCallStatus->dwCallState     = dwCallState;
        lpCallStatus->dwCallStateMode = dwCallStateMode;


        //
        // If the call is IDLE we won't let apps do anything with it,
        // otherwise they can do anything they want to (all valid
        // 1.0/1.4 LINECALLFEATURE_XXX flags)
        //

        switch (dwCallState)
        {
        case LINECALLSTATE_IDLE:

            lpCallStatus->dwCallFeatures = 0;
            break;

        default:

            lpCallStatus->dwCallFeatures =
                (gESPGlobals.dwSPIVersion == 0x10003 ?
                AllCallFeatures1_0 : AllCallFeatures1_4);
            break;
        }

        //lpCallStatus->dwDevSpecificSize
        //lpCallStatus->dwDevSpecificOffset

        if (gESPGlobals.dwSPIVersion >= 0x00020000)
        {
            switch (dwCallState)
            {
            case LINECALLSTATE_IDLE:

                lpCallStatus->dwCallFeatures = LINECALLFEATURE_SETCALLDATA;
                break;

            default:

                lpCallStatus->dwCallFeatures  = AllCallFeatures2_0;
                lpCallStatus->dwCallFeatures2 = AllCallFeaturesTwo;
                break;
            }

            //lpCallStatus->dwCallFeatures2
            //lpCallStatus->tStateEntryTime
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetDevCaps(
    DWORD           dwDeviceID,
    DWORD           dwTSPIVersion,
    DWORD           dwExtVersion,
    LPLINEDEVCAPS   lpLineDevCaps
    )
{
    static char szFuncName[] = "lineGetDevCaps";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "dwExtVersion",   dwExtVersion    },
        { "lpLineDevCaps",  lpLineDevCaps   }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 4, params };
    PDRVLINE    pLine = GetLineFromID (dwDeviceID);
    WCHAR       buf[32];

    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    InsertVarDataString(
        lpLineDevCaps,
        &lpLineDevCaps->dwProviderInfoSize,
        gszProviderInfo
        );

    InsertVarDataString(
        lpLineDevCaps,
        &lpLineDevCaps->dwSwitchInfoSize,
        L"ESP switch info"
        );

    lpLineDevCaps->dwPermanentLineID =
        (gESPGlobals.dwPermanentProviderID << 16) |
            (dwDeviceID - gESPGlobals.dwLineDeviceIDBase);

    wsprintfW (buf, L"ESP Line %d", dwDeviceID);

    InsertVarDataString(
        lpLineDevCaps,
        &lpLineDevCaps->dwLineNameSize,
        buf
        );

    lpLineDevCaps->dwStringFormat = STRINGFORMAT_ASCII;
    lpLineDevCaps->dwAddressModes = LINEADDRESSMODE_ADDRESSID |
                                    LINEADDRESSMODE_DIALABLEADDR;
    lpLineDevCaps->dwNumAddresses = gESPGlobals.dwNumAddressesPerLine;
    lpLineDevCaps->dwBearerModes  = AllBearerModes1_0;
    lpLineDevCaps->dwMaxRate      = 0x00100000;
    lpLineDevCaps->dwMediaModes   = AllMediaModes1_0;
    //lpLineDevCaps->dwGenerateToneModes
    //lpLineDevCaps->dwGenerateToneMaxNumFreq
    //lpLineDevCaps->dwGenerateDigitModes
    //lpLineDevCaps->dwMonitorToneMaxNumFreq
    //lpLineDevCaps->dwMonitorToneMaxNumEntries
    //lpLineDevCaps->dwMonitorDigitModes
    //lpLineDevCaps->dwGatherDigitsMinTimeout
    //lpLineDevCaps->dwGatherDigitsMaxTimeout
    //lpLineDevCaps->dwMedCtlDigitMaxListSize
    //lpLineDevCaps->dwMedCtlMediaMaxListSize
    //lpLineDevCaps->dwMedCtlToneMaxListSize;
    //lpLineDevCaps->dwMedCtlCallStateMaxListSize
    //lpLineDevCaps->dwDevCapFlags
    lpLineDevCaps->dwMaxNumActiveCalls = gESPGlobals.dwNumAddressesPerLine *
                                         gESPGlobals.dwNumCallsPerAddress;
    //lpLineDevCaps->dwAnswerMode
    //lpLineDevCaps->dwRingModes
    //lpLineDevCaps->dwLineStates
    //lpLineDevCaps->dwUUIAcceptSize
    //lpLineDevCaps->dwUUIAnswerSize
    //lpLineDevCaps->dwUUIMakeCallSize
    //lpLineDevCaps->dwUUIDropSize
    //lpLineDevCaps->dwUUISendUserUserInfoSize
    //lpLineDevCaps->dwUUICallInfoSize
    //lpLineDevCaps->MinDialParams
    //lpLineDevCaps->MaxDialParams
    //lpLineDevCaps->DefaultDialParams
    //lpLineDevCaps->dwNumTerminals
    //lpLineDevCaps->dwTerminalCapsSize
    //lpLineDevCaps->dwTerminalCapsOffset
    //lpLineDevCaps->dwTerminalTextEntrySize
    //lpLineDevCaps->dwTerminalTextSize;
    //lpLineDevCaps->dwTerminalTextOffset
    //lpLineDevCaps->dwDevSpecificSize
    //lpLineDevCaps->dwDevSpecificOffset

    if (dwTSPIVersion >= 0x00010004)
    {
        lpLineDevCaps->dwBearerModes = AllBearerModes1_4;
        lpLineDevCaps->dwMediaModes  = AllMediaModes1_4;

        lpLineDevCaps->dwLineFeatures = AllLineFeatures1_0;

        if (dwTSPIVersion >= 0x00020000)
        {
            lpLineDevCaps->dwBearerModes  = AllBearerModes2_0;

            lpLineDevCaps->dwLineFeatures = AllLineFeatures2_0;

            //lpLineDevCaps->dwSettableDevStatus
            //lpLineDevCaps->dwDeviceClassesSize
            //lpLineDevCaps->dwDeviceClassesOffset
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetDevConfig(
    DWORD       dwDeviceID,
    LPVARSTRING lpDeviceConfig,
    LPCWSTR     lpszDeviceClass
    )
{
    static char szFuncName[] = "lineGetDevConfig";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "lpDeviceConfig",     lpDeviceConfig  },
        { "lpszDeviceClass",    lpszDeviceClass }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        // BUGBUG TSPI_lineGetDevConfig: fill in dev config
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetExtensionID(
    DWORD               dwDeviceID,
    DWORD               dwTSPIVersion,
    LPLINEEXTENSIONID   lpExtensionID
    )
{
    static char szFuncName[] = "lineGetExtensionID";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "lpExtensionID",  lpExtensionID   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        // BUGBUG TSPI_lineGetExtensionID: fill in ext id
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetIcon(
    DWORD   dwDeviceID,
    LPCWSTR lpszDeviceClass,
    LPHICON lphIcon
    )
{
    static char szFuncName[] = "lineGetIcon";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "lpszDeviceClass",    lpszDeviceClass },
        { "lphIcon",            lphIcon         }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        if (lpszDeviceClass  &&
            lstrcmpiW (lpszDeviceClass, L"tapi/InvalidDeviceClass") == 0)
        {
            info.lResult = LINEERR_INVALDEVICECLASS;
        }
        else
        {
            *lphIcon = gESPGlobals.hIconLine;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetID(
    HDRVLINE    hdLine,
    DWORD       dwAddressID,
    HDRVCALL    hdCall,
    DWORD       dwSelect,
    LPVARSTRING lpDeviceID,
    LPCWSTR     lpszDeviceClass,
    HANDLE      hTargetProcess
    )
{
    static char szFuncName[] = "lineGetID";
    FUNC_PARAM params[] =
    {
        { szhdLine,             hdLine          },
        { "dwAddressID",        dwAddressID     },
        { szhdCall,             hdCall          },
        { "dwSelect",           dwSelect,   aCallSelects    },
        { "lpDeviceID",         lpDeviceID      },
        { "lpszDeviceClass",    lpszDeviceClass },
        { "hTargetProcess",     hTargetProcess }
    };
    FUNC_INFO info = { szFuncName, SYNC, 7, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;
    PDRVCALL pCall = (PDRVCALL) hdCall;
    DWORD    i, dwDeviceID, dwNeededSize = sizeof(VARSTRING) + sizeof(DWORD);


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    for (i = 0; aszDeviceClasses[i]; i++)
    {
        if (lstrcmpiW (lpszDeviceClass, aszDeviceClasses[i]) == 0)
        {
            break;
        }
    }

    if (!aszDeviceClasses[i])
    {
        info.lResult = LINEERR_NODEVICE;
        return (Epilog (&info));
    }

    if (dwSelect == LINECALLSELECT_ADDRESS  &&
        dwAddressID >= gESPGlobals.dwNumAddressesPerLine)
    {
        info.lResult = LINEERR_INVALADDRESSID;
        return (Epilog (&info));
    }

    if (lpDeviceID->dwTotalSize < dwNeededSize)
    {
        lpDeviceID->dwNeededSize = dwNeededSize;
        lpDeviceID->dwUsedSize = 3 * sizeof(DWORD);

        return (Epilog (&info));
    }

    if (i == 0)
    {
        if (dwSelect == LINECALLSELECT_CALL)
        {
            dwDeviceID = ((PDRVLINE) pCall->pLine)->dwDeviceID;
        }
        else
        {
            dwDeviceID = pLine->dwDeviceID;
        }
    }
    else
    {
// BUGBUG TSPI_lineGetID: if (gbShowLineGetIDDlg)
//        {
//        }
//        else
//        {
            dwDeviceID = 0;
//        }
    }

    lpDeviceID->dwNeededSize   =
    lpDeviceID->dwUsedSize     = dwNeededSize;
    lpDeviceID->dwStringFormat = STRINGFORMAT_BINARY;
    lpDeviceID->dwStringSize   = sizeof(DWORD);
    lpDeviceID->dwStringOffset = sizeof(VARSTRING);

    *((LPDWORD)(lpDeviceID + 1)) = dwDeviceID;

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetLineDevStatus(
    HDRVLINE        hdLine,
    LPLINEDEVSTATUS lpLineDevStatus
    )
{
    static char szFuncName[] = "lineGetLineDevStatus";
    FUNC_PARAM params[] =
    {
        { szhdLine,             hdLine          },
        { "lpLineDevStatus",    lpLineDevStatus }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 2, params };
    PDRVLINE    pLine = (PDRVLINE) hdLine;
    DWORD       dwTotalSize, dwNeededSize;


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    //lpLineDevStatus->dwNeededSize
    //lpLineDevStatus->dwUsedSize
    //lpLineDevStatus->dwNumOpens             tapi fills this in
    //lpLineDevStatus->dwOpenMediaModes       tapi fills this in


    //
    // Safely determine the # of active, on hold, & onhold pending
    // conference/transfer calls on this line
    //

    {
        DWORD       i;
        PDRVCALL    pCall;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        for (i = 0; i < gESPGlobals.dwNumAddressesPerLine; i++)
        {
            for(
                pCall = pLine->aAddrs[i].pCalls;
                pCall != NULL;
                pCall = pCall->pNext
                )
            {
                switch (pCall->dwCallState)
                {
                case LINECALLSTATE_IDLE:

                    continue;

                case LINECALLSTATE_ONHOLD:

                    lpLineDevStatus->dwNumOnHoldCalls++;
                    continue;

                case LINECALLSTATE_ONHOLDPENDCONF:
                case LINECALLSTATE_ONHOLDPENDTRANSFER:

                    lpLineDevStatus->dwNumOnHoldPendCalls++;
                    continue;

                default:

                    lpLineDevStatus->dwNumActiveCalls++;
                    continue;
                }
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    lpLineDevStatus->dwLineFeatures = AllLineFeatures1_0;
    //lpLineDevStatus->dwNumCallCompletions
    //lpLineDevStatus->dwRingMode
    //lpLineDevStatus->dwSignalLevel
    //lpLineDevStatus->dwBatteryLevel
    //lpLineDevStatus->dwRoamMode
    lpLineDevStatus->dwDevStatusFlags = LINEDEVSTATUSFLAGS_CONNECTED |
                                        LINEDEVSTATUSFLAGS_INSERVICE;
    //lpLineDevStatus->dwTerminalModesSize
    //lpLineDevStatus->dwTerminalModesOffset
    //lpLineDevStatus->dwDevSpecificSize
    //lpLineDevStatus->dwDevSpecificOffset

    if (gESPGlobals.dwSPIVersion >= 0x20000)
    {
        lpLineDevStatus->dwLineFeatures = AllLineFeatures2_0;

        lpLineDevStatus->dwAvailableMediaModes = AllMediaModes1_4;

        //lpLineDevStatus->dwAppInfoSize;     tapi fills this in
        //lpLineDevStatus->dwAppInfoOffset;   tapi fills this in
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineGetNumAddressIDs(
    HDRVLINE    hdLine,
    LPDWORD     lpdwNumAddressIDs
    )
{
    static char szFuncName[] = "lineGetNumAddressIDs";
    FUNC_PARAM params[] =
    {
        { szhdLine,             hdLine              },
        { "lpdwNumAddressIDs",  lpdwNumAddressIDs   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVLINE pLine = (PDRVLINE) hdLine;


    if (Prolog (&info))
    {
        *lpdwNumAddressIDs = gESPGlobals.dwNumAddressesPerLine;
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineHold_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam2;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


    if (pAsyncReqInfo->lResult == 0)
    {
        pAsyncReqInfo->lResult = SetCallState(
            pCall,
            dwCallInstThen,
            LINECALLSTATE_CONNECTED,
            LINECALLSTATE_ONHOLD,
            0,
            TRUE
            );
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineHold(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall
    )
{
    static char szFuncName[] = "lineHold";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        2,
        params,
        TSPI_lineHold_postProcess

    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam2))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineMakeCall_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       bDestAddress   = pAsyncReqInfo->dwParam3,
                bValidLineID   = pAsyncReqInfo->dwParam4,
                dwCallInstThen = pAsyncReqInfo->dwParam6,
                dwCallInstNow;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam1,
                pDestCall = (PDRVCALL) pAsyncReqInfo->dwParam2;
    PDRVLINE    pDestLine = (PDRVLINE) pAsyncReqInfo->dwParam5;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        if (bDestAddress)
        {
            if (bValidLineID && !pDestCall)
            {
                SetCallState(
                    pCall,
                    dwCallInstThen,
                    0xffffffff,
                    LINECALLSTATE_BUSY,
                    LINEBUSYMODE_UNAVAIL,
                    TRUE
                    );
            }
            else
            {
                SetCallState(
                    pCall,
                    dwCallInstThen,
                    0xffffffff,
                    LINECALLSTATE_DIALING,
                    0,
                    FALSE
                    );

                SetCallState(
                    pCall,
                    dwCallInstThen,
                    0xffffffff,
                    LINECALLSTATE_RINGBACK,
                    0,
                    TRUE
                    );
            }

            if (pDestCall)
            {
                EnterCriticalSection (&gESPGlobals.CallListCritSec);

                if (IsValidDrvCall (pCall, &dwCallInstNow) &&
                    dwCallInstNow == dwCallInstThen)
                {
                    SendLineEvent(
                        pDestLine,
                        NULL,
                        LINE_NEWCALL,
                        (DWORD) pDestCall,
                        (DWORD) &pDestCall->htCall,
                        0
                        );

                    if (pDestCall->htCall != NULL)
                    {
                        SetCallState(
                            pDestCall,
                            pDestCall->dwCallInstance,
                            0xffffffff,
                            LINECALLSTATE_OFFERING,
                            0,
                            TRUE
                            );
                    }
                    else
                    {
                        FreeCall (pDestCall, pDestCall->dwCallInstance);
                    }
                }
                else
                {
                    FreeCall (pDestCall, pDestCall->dwCallInstance);
                }

                LeaveCriticalSection (&gESPGlobals.CallListCritSec);
            }
        }
        else
        {
            SetCallState(
                pCall,
                dwCallInstThen,
                0xffffffff,
                LINECALLSTATE_DIALTONE,
                0,
                TRUE
                );
        }
    }
    else
    {
        FreeCall (pCall, dwCallInstThen);

        if (pDestCall)
        {
            FreeCall (pDestCall, pDestCall->dwCallInstance);
        }
    }
}


LONG
TSPIAPI
TSPI_lineMakeCall(
    DRV_REQUESTID       dwRequestID,
    HDRVLINE            hdLine,
    HTAPICALL           htCall,
    LPHDRVCALL          lphdCall,
    LPCWSTR             lpszDestAddress,
    DWORD               dwCountryCode,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    static char szFuncName[] = "lineMakeCall";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdLine,             hdLine          },
        { "htCall",             htCall          },
        { "lphdCall",           lphdCall        },
        { "lpszDestAddress",    lpszDestAddress },
        { "dwCountryCode",      dwCountryCode   },
        { szlpCallParams,       lpCallParams    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        7,
        params,
        TSPI_lineMakeCall_postProcess

    };

    if (Prolog (&info))
    {
        BOOL        bValidLineID = FALSE;
        LONG        lResult;
        PDRVCALL    pCall, pDestCall;
        PDRVLINE    pLine = (PDRVLINE) hdLine, pDestLine;


        if ((lResult = AllocCall (pLine, htCall, lpCallParams, &pCall)) == 0)
        {
            *lphdCall = (HDRVCALL) pCall;

            CreateIncomingCall(
                lpszDestAddress,
                lpCallParams,
                pCall,
                &bValidLineID,
                &pDestLine,
                &pDestCall
                );

            info.pAsyncReqInfo->dwParam1 = pCall;
            info.pAsyncReqInfo->dwParam2 = pDestCall;
            info.pAsyncReqInfo->dwParam3 = (DWORD) lpszDestAddress;
            info.pAsyncReqInfo->dwParam4 = (DWORD) bValidLineID;
            info.pAsyncReqInfo->dwParam5 = pDestLine;
            info.pAsyncReqInfo->dwParam6 = pCall->dwCallInstance;
        }
        else
        {
            info.lResult = lResult;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineMonitorDigits(
    HDRVCALL    hdCall,
    DWORD       dwDigitModes
    )
{
    static char szFuncName[] = "lineMonitorDigits";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "dwDigitModes",   dwDigitModes,   aDigitModes }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineMonitorMedia(
    HDRVCALL    hdCall,
    DWORD       dwMediaModes
    )
{
    static char szFuncName[] = "lineMonitorMedia";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall                      },
        { "dwMediaModes",   dwMediaModes,   aMediaModes }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineMonitorTones(
    HDRVCALL            hdCall,
    DWORD               dwToneListID,
    LPLINEMONITORTONE   const lpToneList,
    DWORD               dwNumEntries
    )
{
    static char szFuncName[] = "lineMonitorTones";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "dwToneListID",   dwToneListID    },
        { "lpToneList",     lpToneList      },
        { "dwNumEntries",   dwNumEntries    }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    if (Prolog (&info))
    {
        DWORD       dwLastToneListID = 0;
        HTAPICALL   htCall;
        HTAPILINE   htLine;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            htLine = ((PDRVLINE) pCall->pLine)->htLine;
            htCall = pCall->htCall;

            if (gbAutoGatherGenerateMsgs)
            {
                dwLastToneListID = dwToneListID;
            }
            else
            {
                dwLastToneListID = pCall->dwMonitorToneListID;
                pCall->dwMonitorToneListID = dwToneListID;
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        if (dwLastToneListID)
        {
            (gESPGlobals.pfnLineEvent)(
                htLine,
                htCall,
                LINE_MONITORTONE,
                0,
                dwLastToneListID,
                0
                );
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineNegotiateExtVersion(
    DWORD   dwDeviceID,
    DWORD   dwTSPIVersion,
    DWORD   dwLowVersion,
    DWORD   dwHighVersion,
    LPDWORD lpdwExtVersion
    )
{
    static char szFuncName[] = "lineNegotiateExtVersion";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "dwLowVersion",   dwLowVersion    },
        { "dwHighVersion",  dwHighVersion   },
        { "lpdwExtVersion", lpdwExtVersion  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };


    if (Prolog (&info))
    {
        if (dwLowVersion == 0 ||
            dwHighVersion == 0xffffffff ||
            dwLowVersion > dwHighVersion)
        {
            info.lResult = LINEERR_INCOMPATIBLEEXTVERSION;
        }
        else
        {
            *lpdwExtVersion = dwHighVersion;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineNegotiateTSPIVersion(
    DWORD   dwDeviceID,
    DWORD   dwLowVersion,
    DWORD   dwHighVersion,
    LPDWORD lpdwTSPIVersion
    )
{
    static char szFuncName[] = "lineNegotiateTSPIVersion";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "dwLowVersion",       dwLowVersion    },
        { "dwHighVersion",      dwHighVersion   },
        { "lpdwTSPIVersion",    lpdwTSPIVersion }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };


    if (Prolog (&info))
    {
        *lpdwTSPIVersion = gESPGlobals.dwSPIVersion;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineOpen(
    DWORD       dwDeviceID,
    HTAPILINE   htLine,
    LPHDRVLINE  lphdLine,
    DWORD       dwTSPIVersion,
    LINEEVENT   lpfnEventProc
    )
{
    static char szFuncName[] = "lineOpen";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "htLine",         htLine          },
        { "lphdLine",       lphdLine        },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "lpfnEventProc",  lpfnEventProc   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };
    PDRVLINE pLine;


    if (Prolog (&info))
    {
        if ((pLine = GetLineFromID (dwDeviceID)))
        {
            pLine->htLine = htLine;

            *lphdLine = (HDRVLINE) pLine;

            WriteEventBuffer(
                pLine->dwDeviceID,
                WIDGETTYPE_LINE,
                (DWORD) pLine,
                (DWORD) htLine,
                0,
                0
                );
        }
        else
        {
            info.lResult = LINEERR_OPERATIONFAILED;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_linePark_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam2,
                dwParkIndex = pAsyncReqInfo->dwParam4;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam1,
                pParkedCall = (PDRVCALL) pAsyncReqInfo->dwParam3,
                pDestCall;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        pDestCall = pCall->pDestCall;
        pCall->pDestCall = NULL;

        pParkedCall->bConnectedToDestCall =
            pCall->bConnectedToDestCall;

        pAsyncReqInfo->lResult = SetCallState(
            pCall,
            dwCallInstThen,
            LINECALLSTATE_CONNECTED | LINECALLSTATE_ONHOLD,
            LINECALLSTATE_IDLE,
            0,
            TRUE
            );

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pDestCall, NULL))
        {
            pDestCall->pDestCall = pParkedCall;
            pParkedCall->pDestCall = pDestCall;
        }

// BUGBUG TSPI_linePark: what if dest call state chg while buddy parked???

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }
    else
    {
        //
        // Clean up parked call
        //

        if (pParkedCall->pSendingFlowspec)
        {
            DrvFree (pParkedCall->pSendingFlowspec);
        }

        if (pParkedCall->pReceivingFlowspec)
        {
            DrvFree (pParkedCall->pReceivingFlowspec);
        }

        if (pParkedCall->pCallData)
        {
            DrvFree (pParkedCall->pCallData);
        }

        DrvFree (pParkedCall);

        gaParkedCalls[dwParkIndex] = NULL;
    }
}


LONG
TSPIAPI
TSPI_linePark(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    DWORD           dwParkMode,
    LPCWSTR         lpszDirAddress,
    LPVARSTRING     lpNonDirAddress
    )
{
    static char szFuncName[] = "linePark";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "dwParkMode",         dwParkMode      },
        { "lpszDirAddress",     lpszDirAddress  },
        { "lpNonDirAddress",    lpNonDirAddress }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 5, params, NULL };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    if (Prolog (&info))
    {
        if (dwParkMode == LINEPARKMODE_DIRECTED)
        {
            info.lResult = TransferCall(
                &info,
                pCall,
                LINECALLSTATE_CONNECTED | LINECALLSTATE_ONHOLD,
                LINECALLSTATE_ONHOLD,
                lpszDirAddress
                );
        }
        else
        {
            //
            // First check to see if buf is big enough to return parked addr
            //

            if (lpNonDirAddress->dwTotalSize <
                    (sizeof (VARSTRING) + 9 * sizeof(WCHAR))) // L"9999#123"
            {
                lpNonDirAddress->dwNeededSize = sizeof (VARSTRING) +
                    9 * sizeof(WCHAR);

                info.lResult = LINEERR_STRUCTURETOOSMALL;

                return (Epilog (&info));
            }

            EnterCriticalSection (&gESPGlobals.CallListCritSec);

            if (IsValidDrvCall (pCall, NULL) == FALSE)
            {
                info.lResult = LINEERR_INVALCALLHANDLE;
            }
            else if (pCall->dwCallState != LINECALLSTATE_CONNECTED  &&
                     pCall->dwCallState != LINECALLSTATE_ONHOLD)
            {
                info.lResult = LINEERR_INVALCALLSTATE;
            }
            else
            {
                DWORD i;


                for (i = 0; i < MAX_NUM_PARKED_CALLS; i++)
                {
                    if (gaParkedCalls[i] == NULL)
                    {
                        break;
                    }
                }

                if (i < MAX_NUM_PARKED_CALLS)
                {
                    //
                    // Create a new call struct, dup-ing all the info of
                    // the existing call, & stick it in the parking place
                    //

                    DWORD       dwStringSize;
                    PDRVCALL    pParkedCall;


                    if ((pParkedCall = DrvAlloc (sizeof (DRVCALL))))
                    {
                        char buf[16];


                        CopyMemory(
                            &pParkedCall->dwMediaMode,
                            &pCall->dwMediaMode,
                            8 * sizeof (DWORD) + sizeof (LINEDIALPARAMS)
                            );

                        if (pCall->pSendingFlowspec  &&
                            (pParkedCall->pSendingFlowspec =
                                DrvAlloc (pCall->dwSendingFlowspecSize)))

                        {
                            pParkedCall->dwSendingFlowspecSize =
                                pCall->dwSendingFlowspecSize;
                        }

                        if (pCall->pReceivingFlowspec  &&
                            (pParkedCall->pReceivingFlowspec =
                                DrvAlloc (pCall->dwReceivingFlowspecSize)))

                        {
                            pParkedCall->dwReceivingFlowspecSize =
                                pCall->dwReceivingFlowspecSize;
                        }

                        if (pCall->pCallData  &&
                            (pParkedCall->pCallData =
                                DrvAlloc (pCall->dwCallDataSize)))
                        {
                            pParkedCall->dwCallDataSize =
                                pCall->dwCallDataSize;
                        }

                        pParkedCall->dwCallInstance = gdwCallInstance++;
                        pParkedCall->dwCallID = pCall->dwCallID;

                        gaParkedCalls[i] = pParkedCall;

                        wsprintf (buf, "9999#%d", i);

                        dwStringSize = (DWORD) MultiByteToWideChar(
                           GetACP(),
                           MB_PRECOMPOSED,
                           (LPCSTR) buf,
                           lstrlenA (buf) + 1,
                           (LPWSTR) (lpNonDirAddress + 1),
                           9
                           ) * sizeof (WCHAR);

                        lpNonDirAddress->dwNeededSize += dwStringSize;
                        lpNonDirAddress->dwUsedSize   += dwStringSize;

                        lpNonDirAddress->dwStringFormat = STRINGFORMAT_UNICODE;
                        lpNonDirAddress->dwStringSize   = dwStringSize;
                        lpNonDirAddress->dwStringOffset = sizeof (VARSTRING);

                        info.pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
                            TSPI_linePark_postProcess;

                        info.pAsyncReqInfo->dwParam1 = pCall;
                        info.pAsyncReqInfo->dwParam2 = pCall->dwCallInstance;
                        info.pAsyncReqInfo->dwParam3 = pParkedCall;
                        info.pAsyncReqInfo->dwParam4 = i;
                    }
                    else
                    {
                        info.lResult = LINEERR_NOMEM;
                    }
                }
                else
                {
                    ShowStr(
                        TRUE,
                        "TSPI_linePark (undirected): no available " \
                            "parking spaces"
                        );

                    info.lResult = LINEERR_OPERATIONFAILED;
                }
            }

            LeaveCriticalSection (&gESPGlobals.CallListCritSec);
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_linePickup_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD      dwCallInstThen = pAsyncReqInfo->dwParam2;
    PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        SetCallState(
            pCall,
            dwCallInstThen,
            0xffffffff,
            LINECALLSTATE_OFFERING,
            0,
            TRUE
            );
    }
    else
    {
        FreeCall (pCall, dwCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_linePickup(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwAddressID,
    HTAPICALL       htCall,
    LPHDRVCALL      lphdCall,
    LPCWSTR         lpszDestAddress,
    LPCWSTR         lpszGroupID
    )
{
    static char szFuncName[] = "linePickup";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdLine,             hdLine          },
        { "dwAddressID",        dwAddressID     },
        { "htCall",             htCall          },
        { "lphdCall",           lphdCall        },
        { "lpszDestAddress",    lpszDestAddress },
        { "lpszGroupID",        lpszGroupID     }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        7,
        params,
        TSPI_linePickup_postProcess
    };


    if (Prolog (&info))
    {
        LONG        lResult;
        PDRVCALL    pCall;


        if ((lResult = AllocCall(
                (PDRVLINE) hdLine,
                htCall,
                NULL,
                &pCall

                )) == 0)
        {
            // BUGBUG deal w/ addr id

            *lphdCall = (HDRVCALL) pCall;

            info.pAsyncReqInfo->dwParam1 = (DWORD) pCall;
            info.pAsyncReqInfo->dwParam2 = pCall->dwCallInstance;
        }
        else
        {
            info.lResult = lResult;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_linePrepareAddToConference_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwConfCallInstThen    = pAsyncReqInfo->dwParam1,
                dwConsultCallInstThen = pAsyncReqInfo->dwParam4;
    PDRVCALL    pConfCall    = (PDRVCALL) pAsyncReqInfo->dwParam2,
                pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam3;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        SetCallState(
            pConfCall,
            dwConfCallInstThen,
            0xffffffff,
            LINECALLSTATE_ONHOLDPENDCONF,
            0,
            TRUE
            );

        SetCallState(
            pConsultCall,
            dwConsultCallInstThen,
            0xffffffff,
            LINECALLSTATE_DIALTONE,
            0,
            TRUE
            );
    }
    else
    {
        FreeCall (pConsultCall, dwConsultCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_linePrepareAddToConference(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdConfCall,
    HTAPICALL           htConsultCall,
    LPHDRVCALL          lphdConsultCall,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    LONG        lResult;
    PDRVCALL    pConsultCall;
    static char szFuncName[] = "linePrepareAddToConference";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID         },
        { "hdConfCall",         hdConfCall          },
        { "htConsultCall",      htConsultCall       },
        { "lphdConsultCall",    lphdConsultCall     },
        { szlpCallParams,       lpCallParams        }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        5,
        params,
        TSPI_linePrepareAddToConference_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdConfCall,
                &info.pAsyncReqInfo->dwParam1
                ))
        {
            info.pAsyncReqInfo->dwParam2 = (DWORD) hdConfCall;

            if ((lResult = AllocCall(
                    ((PDRVCALL) hdConfCall)->pLine, // BUGBUG: AV potential
                    htConsultCall,
                    lpCallParams,
                    &pConsultCall

                    )) == 0)
            {
                info.pAsyncReqInfo->dwParam3 = (DWORD) pConsultCall;
                info.pAsyncReqInfo->dwParam4 = pConsultCall->dwCallInstance;

                *lphdConsultCall = (HDRVCALL) pConsultCall;
            }
            else
            {
                info.lResult = lResult;
            }
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineRedirect(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCWSTR         lpszDestAddress,
    DWORD           dwCountryCode
    )
{
    static char szFuncName[] = "lineRedirect";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpszDestAddress",    lpszDestAddress },
        { "dwCountryCode",      dwCountryCode   }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 4, params, NULL };
    PDRVCALL pCall = (PDRVCALL) hdCall;


    if (Prolog (&info))
    {
        info.lResult = TransferCall(
            &info,
            (PDRVCALL) hdCall,
            LINECALLSTATE_OFFERING,
            LINECALLSTATE_OFFERING,
            lpszDestAddress
            );
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineReleaseUserUserInfo(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall
    )
{
    static char szFuncName[] = "lineReleaseUserUserInfo";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 2, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineRemoveFromConference_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if (pAsyncReqInfo->lResult == 0)
    {

        DWORD      dwCallInstThen = pAsyncReqInfo->dwParam2;
        PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if ((pAsyncReqInfo->lResult = SetCallState(
                pCall,
                dwCallInstThen,
                LINECALLSTATE_CONFERENCED,
                LINECALLSTATE_CONNECTED,
                0,
                TRUE

                )) == 0)
        {
            PDRVCALL   pCall2 = (PDRVCALL) pCall->pConfParent;


            while (pCall2 && (pCall2->pNextConfChild != pCall))
            {
                pCall2 = pCall2->pNextConfChild;
            }

            if (pCall2)
            {
                pCall2->pNextConfChild = pCall->pNextConfChild;
            }

            pCall->pConfParent = NULL;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineRemoveFromConference(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall
    )
{
    static char szFuncName[] = "lineRemoveFromConference";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        2,
        params,
        TSPI_lineRemoveFromConference_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam2))
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSecureCall(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall
    )
{
    static char szFuncName[] = "lineSecureCall";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 2, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSelectExtVersion(
    HDRVLINE    hdLine,
    DWORD       dwExtVersion
    )
{
    static char szFuncName[] = "lineSelectExtVersion";
    FUNC_PARAM params[] =
    {
        { szhdLine,         hdLine          },
        { "dwExtVersion",   dwExtVersion    }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSendUserUserInfo(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall,
    LPCSTR          lpsUserUserInfo,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "lineSendUserUserInfo";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "lpsUserUserInfo",    lpsUserUserInfo },
        { szdwSize,             dwSize          }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 4, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetAppSpecific(
    HDRVCALL    hdCall,
    DWORD       dwAppSpecific
    )
{
    static char szFuncName[] = "lineSetAppSpecific";
    FUNC_PARAM params[] =
    {
        { szhdCall,         hdCall          },
        { "dwAppSpecific",  dwAppSpecific   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
        PDRVCALL pCall = (PDRVCALL) hdCall;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            if (pCall->dwAppSpecific != dwAppSpecific)
            {
                pCall->dwAppSpecific = dwAppSpecific;

                SendLineEvent(
                    pCall->pLine,
                    pCall,
                    LINE_CALLINFO,
                    LINECALLINFOSTATE_APPSPECIFIC,
                    0,
                    0
                    );
            }
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetCallData_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam1, dwCallInstNow,
                dwCallDataSize = pAsyncReqInfo->dwParam4;
    LPVOID      pCallData = (LPVOID) pAsyncReqInfo->dwParam3, pToFree;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam2;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, &dwCallInstNow) &&
            dwCallInstNow == dwCallInstThen)
        {
            pToFree               = pCall->pCallData;
            pCall->pCallData      = pCallData;
            pCall->dwCallDataSize = dwCallDataSize;

            SendLineEvent(
                pCall->pLine,
                pCall,
                LINE_CALLINFO,
                LINECALLINFOSTATE_CALLDATA,
                0,
                0
                );
        }
        else
        {
            pToFree = NULL;
            pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        DrvFree (pToFree);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult != 0)
    {
        DrvFree (pCallData);
    }
}


LONG
TSPIAPI
TSPI_lineSetCallData(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    LPVOID              lpCallData,
    DWORD               dwSize
    )
{
    static char szFuncName[] = "lineSetCallData";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      },
        { "lpCallData",     lpCallData  },
        { szdwSize,         dwSize      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_lineSetCallData_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdCall,
                &info.pAsyncReqInfo->dwParam1
                ))
        {
            LPVOID  pCallData;


            if (dwSize)
            {
                if ((pCallData = DrvAlloc (dwSize)))
                {
                    CopyMemory (pCallData, lpCallData, dwSize);
                }
                else
                {
                    info.lResult = LINEERR_NOMEM;
                }
            }
            else
            {
                pCallData = NULL;
            }

            info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
            info.pAsyncReqInfo->dwParam3 = (DWORD) pCallData;
            info.pAsyncReqInfo->dwParam4 = (DWORD) dwSize;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetCallParams_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD               dwCallInstThen = pAsyncReqInfo->dwParam1,
                        dwCallInstNow,
                        dwBearerMode = pAsyncReqInfo->dwParam3,
                        dwMinRate = pAsyncReqInfo->dwParam4,
                        dwMaxRate = pAsyncReqInfo->dwParam5;
    PDRVCALL            pCall = pAsyncReqInfo->dwParam2;
    LPLINEDIALPARAMS    pDialParams = pAsyncReqInfo->dwParam6;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, &dwCallInstNow)  &&
            dwCallInstNow == dwCallInstThen)
        {
            DWORD   dwCallInfoStates = 0;


            if (pCall->dwBearerMode != dwBearerMode)
            {
                pCall->dwBearerMode = dwBearerMode;
                dwCallInfoStates |= LINECALLINFOSTATE_BEARERMODE;
            }

            if (pCall->dwMinRate != dwMinRate ||
                pCall->dwMaxRate != dwMaxRate)
            {
                pCall->dwMinRate = dwMinRate;
                pCall->dwMaxRate = dwMaxRate;
                dwCallInfoStates |= LINECALLINFOSTATE_RATE;
            }

            if (pDialParams &&
                (pCall->DialParams.dwDialPause != pDialParams->dwDialPause ||
                pCall->DialParams.dwDialSpeed  != pDialParams->dwDialSpeed ||
                pCall->DialParams.dwDigitDuration !=
                    pDialParams->dwDigitDuration ||
                pCall->DialParams.dwWaitForDialtone !=
                    pDialParams->dwWaitForDialtone))
            {
                pCall->DialParams.dwDialPause       = pDialParams->dwDialPause;
                pCall->DialParams.dwDialSpeed       = pDialParams->dwDialSpeed;
                pCall->DialParams.dwDigitDuration   =
                    pDialParams->dwDigitDuration;
                pCall->DialParams.dwWaitForDialtone =
                    pDialParams->dwWaitForDialtone;

                dwCallInfoStates |= LINECALLINFOSTATE_DIALPARAMS;
            }

            if (dwCallInfoStates)
            {
                SendLineEvent(
                    pCall->pLine,
                    pCall,
                    LINE_CALLINFO,
                    dwCallInfoStates,
                    0,
                    0
                    );
            }
        }
        else
        {
            pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pDialParams)
    {
        DrvFree (pDialParams);
    }
}


LONG
TSPIAPI
TSPI_lineSetCallParams(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    DWORD               dwBearerMode,
    DWORD               dwMinRate,
    DWORD               dwMaxRate,
    LPLINEDIALPARAMS    const lpDialParams
    )
{
    static char szFuncName[] = "lineSetCallParams";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID                     },
        { szhdCall,         hdCall                          },
        { "dwBearerMode",   dwBearerMode,   aBearerModes    },
        { "dwMinRate",      dwMinRate                       },
        { "dwMaxRate",      dwMaxRate                       },
        { "lpDialParams",   lpDialParams                    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params,
        TSPI_lineSetCallParams_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdCall,
                &info.pAsyncReqInfo->dwParam1
                ))
        {
            info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
            info.pAsyncReqInfo->dwParam3 = dwBearerMode;
            info.pAsyncReqInfo->dwParam4 = dwMinRate;
            info.pAsyncReqInfo->dwParam5 = dwMaxRate;

            if (lpDialParams)
            {
                LPLINEDIALPARAMS pDialParams;


                if ((pDialParams = DrvAlloc (sizeof (LINEDIALPARAMS))))
                {
                    CopyMemory(
                        pDialParams,
                        lpDialParams,
                        sizeof (LINEDIALPARAMS)
                        );

                    info.pAsyncReqInfo->dwParam8 = (DWORD) pDialParams;
                }
                else
                {
                    info.lResult = LINEERR_NOMEM;
                }
            }
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetCallQualityOfService_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam1, dwCallInstNow,
                dwSendingFlowspecSize = pAsyncReqInfo->dwParam4,
                dwReceivingFlowspecSize = pAsyncReqInfo->dwParam6;
    LPVOID      pSendingFlowspec = (LPVOID) pAsyncReqInfo->dwParam3,
                pReceivingFlowspec = (LPVOID) pAsyncReqInfo->dwParam5,
                pToFree, pToFree2;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam2;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, &dwCallInstNow)  &&
            dwCallInstNow == dwCallInstThen)
        {
            pToFree                      = pCall->pSendingFlowspec;
            pCall->pSendingFlowspec      = pSendingFlowspec;
            pCall->dwSendingFlowspecSize = dwSendingFlowspecSize;

            pToFree2                       = pCall->pReceivingFlowspec;
            pCall->pReceivingFlowspec      = pReceivingFlowspec;
            pCall->dwReceivingFlowspecSize = dwReceivingFlowspecSize;

            SendLineEvent(
                pCall->pLine,
                pCall,
                LINE_CALLINFO,
                LINECALLINFOSTATE_QOS,
                0,
                0
                );
        }
        else
        {
            pToFree = pToFree2 = NULL;
            pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);

        DrvFree (pToFree);
        DrvFree (pToFree2);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult != 0)
    {
        DrvFree (pSendingFlowspec);
        DrvFree (pReceivingFlowspec);
    }

}


LONG
TSPIAPI
TSPI_lineSetCallQualityOfService(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    LPVOID              lpSendingFlowspec,
    DWORD               dwSendingFlowspecSize,
    LPVOID              lpReceivingFlowspec,
    DWORD               dwReceivingFlowspecSize
    )
{
    static char szFuncName[] = "lineSetCallQualityOfService";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,                dwRequestID             },
        { szhdCall,                     hdCall                  },
        { "lpSendingFlowspec",          lpSendingFlowspec       },
        { "dwSendingFlowspecSize",      dwSendingFlowspecSize   },
        { "lpReceivingFlowspec",        lpReceivingFlowspec     },
        { "dwReceivingFlowspecSize",    dwReceivingFlowspecSize }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params,
        TSPI_lineSetCallQualityOfService_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam1))
        {
            LPVOID  pSendingFlowspec, pReceivingFlowspec;


            if (dwSendingFlowspecSize)
            {
                if ((pSendingFlowspec = DrvAlloc (dwSendingFlowspecSize)))
                {
                    CopyMemory(
                        pSendingFlowspec,
                        lpSendingFlowspec,
                        dwSendingFlowspecSize
                        );
                }
                else
                {
                    info.lResult = LINEERR_NOMEM;
                    goto TSPI_lineSetCallQualityOfService_epilog;
                }
            }
            else
            {
                pSendingFlowspec = NULL;
            }

            if (dwReceivingFlowspecSize)
            {
                if ((pReceivingFlowspec = DrvAlloc (dwReceivingFlowspecSize)))
                {
                    CopyMemory(
                        pReceivingFlowspec,
                        lpReceivingFlowspec,
                        dwReceivingFlowspecSize
                        );
                }
                else
                {
                    info.lResult = LINEERR_NOMEM;

                    if (pSendingFlowspec)
                    {
                        DrvFree (pSendingFlowspec);

                    }

                    goto TSPI_lineSetCallQualityOfService_epilog;
                }
            }
            else
            {
                pReceivingFlowspec = NULL;
            }

            info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
            info.pAsyncReqInfo->dwParam3 = (DWORD) pSendingFlowspec;
            info.pAsyncReqInfo->dwParam4 = (DWORD) dwSendingFlowspecSize;
            info.pAsyncReqInfo->dwParam5 = (DWORD) pReceivingFlowspec;
            info.pAsyncReqInfo->dwParam6 = (DWORD) dwReceivingFlowspecSize;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

TSPI_lineSetCallQualityOfService_epilog:

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetCallTreatment_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam1, dwCallInstNow,
                dwTreatment = pAsyncReqInfo->dwParam3;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam2;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, &dwCallInstNow) &&
            dwCallInstNow == dwCallInstThen)
        {
            if (pCall->dwTreatment != dwTreatment)
            {
                pCall->dwTreatment = dwTreatment;

                SendLineEvent(
                    pCall->pLine,
                    pCall,
                    LINE_CALLINFO,
                    LINECALLINFOSTATE_TREATMENT,
                    0,
                    0
                    );
            }
        }
        else
        {
            pAsyncReqInfo->lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineSetCallTreatment(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    DWORD               dwTreatment
    )
{
    static char szFuncName[] = "lineSetCallTreatment";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      },
        { "dwTreatment",    dwTreatment }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        3,
        params,
        TSPI_lineSetCallTreatment_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam1))
        {
            info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
            info.pAsyncReqInfo->dwParam3 = dwTreatment;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetCurrentLocation(
    DWORD   dwLocation
    )
{
    static char szFuncName[] = "lineSetCurrentLocation";
    FUNC_PARAM params[] =
    {
        { "dwLocation", dwLocation }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetDefaultMediaDetection(
    HDRVLINE    hdLine,
    DWORD       dwMediaModes
    )
{
    static char szFuncName[] = "lineSetDefaultMediaDetection";
    FUNC_PARAM params[] =
    {
        { szhdLine,         hdLine                      },
        { "dwMediaModes",   dwMediaModes,   aMediaModes }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 2, params };
    PDRVLINE    pLine = (PDRVLINE) hdLine;


    if (Prolog (&info))
    {
        pLine->dwMediaModes = dwMediaModes;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetDevConfig(
    DWORD   dwDeviceID,
    LPVOID  const lpDeviceConfig,
    DWORD   dwSize,
    LPCWSTR lpszDeviceClass
    )
{
    static char szFuncName[] = "lineSetDevConfig";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "lpDeviceConfig",     lpDeviceConfig  },
        { szdwSize,             dwSize          },
        { "lpszDeviceClass",    lpszDeviceClass }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetLineDevStatus(
    DRV_REQUESTID       dwRequestID,
    HDRVLINE            hdLine,
    DWORD               dwStatusToChange,
    DWORD               fStatus
    )
{
    static char szFuncName[] = "lineSetLineDevStatus";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID      },
        { szhdLine,             hdLine           },
        { "dwStatusToChange",   dwStatusToChange },
        { "fStatus",            fStatus          }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 4, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetMediaControl(
    HDRVLINE                    hdLine,
    DWORD                       dwAddressID,
    HDRVCALL                    hdCall,
    DWORD                       dwSelect,
    LPLINEMEDIACONTROLDIGIT     const lpDigitList,
    DWORD                       dwDigitNumEntries,
    LPLINEMEDIACONTROLMEDIA     const lpMediaList,
    DWORD                       dwMediaNumEntries,
    LPLINEMEDIACONTROLTONE      const lpToneList,
    DWORD                       dwToneNumEntries,
    LPLINEMEDIACONTROLCALLSTATE const lpCallStateList,
    DWORD                       dwCallStateNumEntries
    )
{
    static char szFuncName[] = "lineSetMediaControl";
    FUNC_PARAM params[] =
    {
        { szhdLine,                 hdLine                  },
        { "dwAddressID",            dwAddressID             },
        { szhdCall,                 hdCall                  },
        { "dwSelect",               dwSelect,   aCallSelects    },
        { "lpDigitList",            lpDigitList             },
        { "dwDigitNumEntries",      dwDigitNumEntries       },
        { "lpMediaList",            lpMediaList             },
        { "dwMediaNumEntries",      dwMediaNumEntries       },
        { "lpToneList",             lpToneList              },
        { "dwToneNumEntries",       dwToneNumEntries        },
        { "lpCallStateList",        lpCallStateList         },
        { "dwCallStateNumEntries",  dwCallStateNumEntries   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 12, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetMediaMode(
    HDRVCALL    hdCall,
    DWORD       dwMediaMode
    )
{
    static char szFuncName[] = "lineSetMediaMode";
    FUNC_PARAM params[] =
    {
        { szhdCall,         szhdCall                  },
        { "dwMediaMode",    dwMediaMode,  aMediaModes }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
        PDRVCALL pCall = (PDRVCALL) hdCall;


        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall (pCall, NULL))
        {
            if (pCall->dwMediaMode != dwMediaMode)
            {
                pCall->dwMediaMode = dwMediaMode;

                SendLineEvent(
                    pCall->pLine,
                    pCall,
                    LINE_CALLINFO,
                    LINECALLINFOSTATE_MEDIAMODE,
                    0,
                    0
                    );
            }
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetStatusMessages(
    HDRVLINE    hdLine,
    DWORD       dwLineStates,
    DWORD       dwAddressStates
    )
{
    static char szFuncName[] = "lineSetStatusMessages";
    FUNC_PARAM params[] =
    {
        { szhdLine,             hdLine          },
        { "dwLineStates",       dwLineStates,   aLineStates },
        { "dwAddressStates",    dwAddressStates,    aAddressStates  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineSetTerminal(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwAddressID,
    HDRVCALL        hdCall,
    DWORD           dwSelect,
    DWORD           dwTerminalModes,
    DWORD           dwTerminalID,
    DWORD           bEnable
    )
{
    static char szFuncName[] = "lineSetTerminal";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdLine,             hdLine          },
        { "dwAddressID",        dwAddressID     },
        { szhdCall,             hdCall          },
        { "dwSelect",           dwSelect,   aCallSelects    },
        { "dwTerminalModes",    dwTerminalModes,    aTerminalModes  },
        { "dwTerminalID",       dwTerminalID    },
        { "bEnable",            bEnable         }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 8, params, NULL };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetupConference_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam1,
                dwConfCallInstThen = pAsyncReqInfo->dwParam4,
                dwConsultCallInstThen = pAsyncReqInfo->dwParam6;
    PDRVCALL    pCall        = (PDRVCALL) pAsyncReqInfo->dwParam2,
                pConfCall    = (PDRVCALL) pAsyncReqInfo->dwParam3,
                pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam5;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (SetCallState(
                pConfCall,
                dwConfCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_ONHOLDPENDCONF,
                0,
                TRUE

                ) == 0)
        {
            if (pCall  &&
                SetCallState(
                    pCall,
                    dwCallInstThen,
                    0xffffffff, // BUGBUG specify vald call states
                    LINECALLSTATE_CONFERENCED,
                    0,
                    TRUE

                    ) == 0)
            {
                pCall->pConfParent = pConfCall;
                pConfCall->pNextConfChild = pCall;
            }

            // The consult call isn't in the conf initially

            SetCallState(
                pConsultCall,
                dwConsultCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_DIALTONE,
                0,
                TRUE
                );
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }
    else
    {
        FreeCall (pConfCall, dwConfCallInstThen);
        FreeCall (pConsultCall, dwConsultCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_lineSetupConference(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    HDRVLINE            hdLine,
    HTAPICALL           htConfCall,
    LPHDRVCALL          lphdConfCall,
    HTAPICALL           htConsultCall,
    LPHDRVCALL          lphdConsultCall,
    DWORD               dwNumParties,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    static char szFuncName[] = "lineSetupConference";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { szhdLine,             hdLine          },
        { "htConfCall",         htConfCall      },
        { "lphdConfCall",       lphdConfCall    },
        { "htConsultCall",      htConsultCall   },
        { "lphdConsultCall",    lphdConsultCall },
        { "dwNumParties",       dwNumParties    },
        { szlpCallParams,       lpCallParams    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        9,
        params,
        TSPI_lineSetupConference_postProcess
    };


    if (Prolog (&info))
    {
        LONG        lResult;
        PDRVCALL    pConfCall, pConsultCall;
        PDRVLINE    pLine;


        //info.pAsyncReqInfo->dwParam1 = (DWORD) hdCall;

        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (hdCall  &&
            !IsValidDrvCall(
                (PDRVCALL) hdCall,
                &info.pAsyncReqInfo->dwParam1
                ))
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
            goto TSPI_lineSetupConference_leaveCritSec;
        }

        pLine = (hdCall ? (PDRVLINE) ((PDRVCALL) hdCall)->pLine :
            (PDRVLINE) hdLine);

        if ((lResult = AllocCall(
                pLine,
                htConfCall,
                lpCallParams,
                &pConfCall

                )) == 0)
        {
            if ((lResult = AllocCall(
                    pLine,
                    htConsultCall,
                    lpCallParams,
                    &pConsultCall

                    )) == 0)
            {
                info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
                info.pAsyncReqInfo->dwParam3 = (DWORD) pConfCall;
                info.pAsyncReqInfo->dwParam4 = pConfCall->dwCallInstance;
                info.pAsyncReqInfo->dwParam5 = (DWORD) pConsultCall;
                info.pAsyncReqInfo->dwParam6 = pConsultCall->dwCallInstance;

                *lphdConfCall = (HDRVCALL) pConfCall;
                *lphdConsultCall = (HDRVCALL) pConsultCall;
            }
            else
            {
                FreeCall (pConfCall, pConfCall->dwCallInstance);
                info.lResult = lResult;
            }
        }
        else
        {
            info.lResult = lResult;
        }

TSPI_lineSetupConference_leaveCritSec:

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSetupTransfer_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam1,
                dwConsultCallInstThen = pAsyncReqInfo->dwParam4;
    PDRVCALL    pCall        = (PDRVCALL) pAsyncReqInfo->dwParam2,
                pConsultCall = (PDRVCALL) pAsyncReqInfo->dwParam3;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        if (SetCallState(
                pConsultCall,
                dwConsultCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_DIALTONE,
                0,
                TRUE

                )  == 0)
        {
            SetCallState(
                pCall,
                dwCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_ONHOLD,
                0,
                TRUE
                );
        }
    }
    else
    {
        FreeCall (pConsultCall, dwConsultCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_lineSetupTransfer(
    DRV_REQUESTID       dwRequestID,
    HDRVCALL            hdCall,
    HTAPICALL           htConsultCall,
    LPHDRVCALL          lphdConsultCall,
    LPLINECALLPARAMS    const lpCallParams
    )
{
    static char szFuncName[] = "lineSetupTransfer";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdCall,             hdCall          },
        { "htConsultCall",      htConsultCall   },
        { "lphdConsultCall",    lphdConsultCall },
        { szlpCallParams,       lpCallParams    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        5,
        params,
        TSPI_lineSetupTransfer_postProcess
    };


    if (Prolog (&info))
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam1))
        {
            LONG        lResult;
            PDRVCALL    pConsultCall;


            if ((lResult = AllocCall(
                    ((PDRVCALL) hdCall)->pLine,
                    htConsultCall,
                    lpCallParams,
                    &pConsultCall

                    )) == 0)
            {
                *lphdConsultCall = (HDRVCALL) pConsultCall;

                info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
                info.pAsyncReqInfo->dwParam3 = (DWORD) pConsultCall;
                info.pAsyncReqInfo->dwParam4 = pConsultCall->dwCallInstance;
            }
            else
            {
                info.lResult = lResult;
            }
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineSwapHold_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwActiveCallInstThen = pAsyncReqInfo->dwParam1,
                dwHeldCallInstThen   = pAsyncReqInfo->dwParam2,
                dwActiveCallInstNow, dwHeldCallInstNow;
    PDRVCALL    pActiveCall = (PDRVCALL) pAsyncReqInfo->dwParam3,
                pHeldCall = (PDRVCALL) pAsyncReqInfo->dwParam4;


    if ((pAsyncReqInfo->lResult == 0))
    {
        if (SetCallState(
                pActiveCall,
                dwActiveCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_ONHOLD,
                0,
                TRUE

                ) == 0)
        {
            SetCallState(
                pHeldCall,
                dwHeldCallInstThen,
                0xffffffff, // BUGBUG specify vald call states
                LINECALLSTATE_CONNECTED,
                0,
                TRUE
                );
        }
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineSwapHold(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdActiveCall,
    HDRVCALL        hdHeldCall
    )
{
    static char szFuncName[] = "lineSwapHold";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { "hdActiveCall",   hdActiveCall    },
        { "hdHeldCall",     hdHeldCall      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        3,
        params,
        TSPI_lineSwapHold_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall(
                (PDRVCALL) hdActiveCall,
                &info.pAsyncReqInfo->dwParam1
                ) &&

            IsValidDrvCall(
                (PDRVCALL) hdHeldCall,
                &info.pAsyncReqInfo->dwParam2
                ))
        {
            info.pAsyncReqInfo->dwParam3 = (DWORD) hdActiveCall;
            info.pAsyncReqInfo->dwParam4 = (DWORD) hdHeldCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_lineUncompleteCall(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwCompletionID
    )
{
    static char szFuncName[] = "lineUncompleteCall";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdLine,         hdLine          },
        { "dwCompletionID", dwCompletionID  }
    };
    FUNC_INFO info = { szFuncName, ASYNC, 3, params, NULL };


    if (Prolog (&info))
    {
        if (dwCompletionID == 0xffffffff)
        {
            info.lResult = LINEERR_INVALCOMPLETIONID;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineUnhold_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD      dwCallInstThen = pAsyncReqInfo->dwParam1;
        PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam2;


        pAsyncReqInfo->lResult == SetCallState(
            pCall,
            dwCallInstThen,
            LINECALLSTATE_ONHOLD,
            LINECALLSTATE_CONNECTED,
            0,
            TRUE
            );
    }

    DoCompletion (pAsyncReqInfo, bAsync);
}


LONG
TSPIAPI
TSPI_lineUnhold(
    DRV_REQUESTID   dwRequestID,
    HDRVCALL        hdCall
    )
{
    static char szFuncName[] = "lineUnhold";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdCall,         hdCall      },
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        2,
        params,
        TSPI_lineUnhold_postProcess
    };


    if (Prolog (&info))
    {
        if (IsValidDrvCall ((PDRVCALL) hdCall, &info.pAsyncReqInfo->dwParam1))
        {
            info.pAsyncReqInfo->dwParam2 = (DWORD) hdCall;
        }
        else
        {
            info.lResult = LINEERR_INVALCALLHANDLE;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_lineUnpark_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD      dwCallInstThen = pAsyncReqInfo->dwParam2,
               dwParkIndex = pAsyncReqInfo->dwParam3,
               dwCallInstNow;
    PDRVCALL   pCall = (PDRVCALL) pAsyncReqInfo->dwParam1;


    if (pAsyncReqInfo->lResult == 0)
    {
        //
        // Make sure there's still a call there to unpark
        //

        if (gaParkedCalls[dwParkIndex] == NULL)
        {
            pAsyncReqInfo->lResult = LINEERR_OPERATIONFAILED;
        }
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.CallListCritSec);

        if (gaParkedCalls[dwParkIndex] != NULL  &&
            IsValidDrvCall (pCall, &dwCallInstNow)  &&
            dwCallInstNow == dwCallInstThen)
        {
            //
            // Copy all the data from the parked call to the new call,
            // then free the parked call
            //

            PDRVCALL    pParkedCall = gaParkedCalls[dwParkIndex];


            gaParkedCalls[dwParkIndex] = NULL;

            CopyMemory(
                &pCall->dwMediaMode,
                &pParkedCall->dwMediaMode,
                14 * sizeof (DWORD) + sizeof (LINEDIALPARAMS)
                );

            if ((pCall->pDestCall = pParkedCall->pDestCall))
            {
                pCall->pDestCall->pDestCall = pCall;
                pCall->bConnectedToDestCall =
                    pParkedCall->bConnectedToDestCall;
            }

            CopyMemory(
                &pCall->dwGatherDigitsEndToEndID,
                &pParkedCall->dwGatherDigitsEndToEndID,
                5 * sizeof (DWORD)
                );


            //
            // Reset call state to 0 so SetCallState will do the indication
            //

            {
                DWORD   dwCallState = pCall->dwCallState;


                pCall->dwCallState = 0;

                SetCallState(
                    pCall,
                    dwCallInstThen,
                    0xffffffff, // BUGBUG specify valid call states
                    dwCallState,
                    0,
                    TRUE
                    );
            }

            pParkedCall->dwKey = INVAL_KEY;
            DrvFree (pParkedCall);
        }
        else
        {
            SetCallState(
                pCall,
                dwCallInstThen,
                0xffffffff, // BUGBUG specify valid call states
                LINECALLSTATE_IDLE,
                0,
                TRUE
                );
        }

        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
    }
    else
    {
        FreeCall (pCall, dwCallInstThen);
    }
}


LONG
TSPIAPI
TSPI_lineUnpark(
    DRV_REQUESTID   dwRequestID,
    HDRVLINE        hdLine,
    DWORD           dwAddressID,
    HTAPICALL       htCall,
    LPHDRVCALL      lphdCall,
    LPCWSTR         lpszDestAddress
    )
{
    static char szFuncName[] = "lineUnpark";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdLine,             hdLine          },
        { "dwAddressID",        dwAddressID     },
        { "htCall",             htCall          },
        { "lphdCall",           lphdCall        },
        { "lpszDestAddress",    lpszDestAddress }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params,
        NULL
    };


    if (Prolog (&info))
    {
        //
        // See if the park addr is valid, & if there's actually a
        // call parked there now
        //

        char   *pszDestAddress, *p, c;
        DWORD   length, dwParkIndex;


        //
        // Convert dest addr from unicode to ascii
        //

        length = (lstrlenW (lpszDestAddress) + 1) * sizeof (WCHAR);

        if (!(pszDestAddress = DrvAlloc (length)))
        {
            info.lResult = LINEERR_NOMEM;
            return (Epilog);
        }

        WideCharToMultiByte(
            CP_ACP,
            0,
            lpszDestAddress,
            -1,
            pszDestAddress,
            length,
            NULL,
            NULL
            );

        p = pszDestAddress;


        //
        // See if destination address is in the format of "9999#<addr id>"
        //

        if (*p++ != '9'  ||
            *p++ != '9'  ||
            *p++ != '9'  ||
            *p++ != '9'  ||
            *p++ != '#'  ||
            *p < '0'     ||
            *p > '9')
        {
            info.lResult = LINEERR_INVALADDRESS;
            goto TSPI_lineUnpark_freeDestAddress;
        }

        for (dwParkIndex = 0; (c = *p); p++)
        {
            if (c >= '0' && c <= '9')
            {
                dwParkIndex *= 10;
                dwParkIndex += ((DWORD)(c - '0'));
            }
            else
            {
                break;
            }
        }

        if (c != '\0'  ||  dwParkIndex >= MAX_NUM_PARKED_CALLS)
        {
            info.lResult = LINEERR_INVALADDRESS;
            goto TSPI_lineUnpark_freeDestAddress;
        }

        if (gaParkedCalls[dwParkIndex] != NULL)
        {
            PDRVCALL        pCall;
            LINECALLPARAMS  callParams;


            ZeroMemory (&callParams, sizeof (LINECALLPARAMS));

            callParams.dwTotalSize = sizeof (LINECALLPARAMS);

            callParams.dwAddressID = dwAddressID;
            callParams.dwAddressMode = LINEADDRESSMODE_ADDRESSID;

            if ((info.lResult = AllocCall(
                    (PDRVLINE) hdLine,
                    htCall,
                    &callParams,
                    &pCall

                    )) == 0)
            {
                info.pAsyncReqInfo->dwParam1 = (DWORD) pCall;
                info.pAsyncReqInfo->dwParam2 = pCall->dwCallInstance;
                info.pAsyncReqInfo->dwParam3 = dwParkIndex;

                *lphdCall = (HDRVCALL) pCall;

                info.pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
                    TSPI_lineUnpark_postProcess;
            }
        }
        else
        {
            info.lResult = LINEERR_OPERATIONFAILED;
        }

TSPI_lineUnpark_freeDestAddress:

        DrvFree (pszDestAddress);
    }

    return (Epilog (&info));
}



//
// -------------------------- TSPI_phoneXxx funcs -----------------------------
//

LONG
TSPIAPI
TSPI_phoneClose(
    HDRVPHONE   hdPhone
    )
{
    static char szFuncName[] = "phoneClose";
    FUNC_PARAM params[] =
    {
        { szhdPhone,    hdPhone }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    //
    // This is more of a "command" than a request, in that TAPI.DLL is
    // going to consider the phone closed whether we like it or not.
    // Therefore we want to free up the phone even if the user chooses
    // to return an error.
    //

    Prolog (&info);

    pPhone->htPhone = (HTAPIPHONE) NULL;

    WriteEventBuffer (pPhone->dwDeviceID,  WIDGETTYPE_PHONE, 0, 0, 0, 0);

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneDevSpecific(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    LPVOID          lpParams,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "phoneDevSpecific";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdPhone,        hdPhone     },
        { "lpParams",       lpParams    },
        { szdwSize,         dwSize      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params
    };


    if (Prolog (&info))
    {
        PESPDEVSPECIFICINFO pInfo = (PESPDEVSPECIFICINFO) lpParams;


        if (dwSize >= sizeof (ESPDEVSPECIFICINFO)  &&
            pInfo->dwKey == ESPDEVSPECIFIC_KEY)
        {
            switch (pInfo->dwType)
            {
            case ESP_DEVSPEC_MSG:

                switch (pInfo->u.EspMsg.dwMsg)
                {
                case PHONE_BUTTON:
                case PHONE_CLOSE:
                case PHONE_DEVSPECIFIC:
                case PHONE_STATE:

                    SendPhoneEvent(
                        (PDRVPHONE) hdPhone,
                        pInfo->u.EspMsg.dwMsg,
                        pInfo->u.EspMsg.dwParam1,
                        pInfo->u.EspMsg.dwParam2,
                        pInfo->u.EspMsg.dwParam3
                        );

                    break;

                case PHONE_CREATE: // BUGBUG

                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_phoneDevSpecific: no support " \
                            "for indicating PHONE_CREATE yet"
                        );

                    info.lResult = PHONEERR_OPERATIONFAILED;
                    break;

                default:

                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_phoneDevSpecific: unrecognized " \
                            "ESPDEVSPECIFICINFO.u.EspMsg.dwMsg (=x%x)",
                        pInfo->u.EspMsg.dwMsg
                        );

                    info.lResult = PHONEERR_OPERATIONFAILED;
                    break;
                }

                break;

            case ESP_DEVSPEC_RESULT:
            {
                DWORD   dwResult = pInfo->u.EspResult.lResult;


                if (dwResult != 0  &&
                    (dwResult < LINEERR_ALLOCATED ||
                    dwResult > PHONEERR_REINIT ||
                    (dwResult > LINEERR_DIALVOICEDETECT &&
                    dwResult < PHONEERR_ALLOCATED)))
                {
                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_phoneDevSpecific: invalid request" \
                            "result value (x%x)",
                        dwResult
                        );

                    info.lResult = PHONEERR_OPERATIONFAILED;
                }
                else if (pInfo->u.EspResult.dwCompletionType >
                            ESP_RESULT_CALLCOMPLPROCASYNC)
                {
                    ShowStr(
                        TRUE,
                        "ERROR: TSPI_phoneDevSpecific: invalid request" \
                            "completion type (x%x)",
                        pInfo->u.EspResult.dwCompletionType
                        );

                    info.lResult = PHONEERR_OPERATIONFAILED;
                }
                else
                {
                    glNextRequestResult = (LONG) dwResult;
                    gdwNextRequestCompletionType =
                        pInfo->u.EspResult.dwCompletionType;
                    gdwDevSpecificRequestID = dwRequestID;
                }

                break;
            }
            default:

                ShowStr(
                    TRUE,
                    "ERROR: TSPI_phoneDevSpecific: unrecognized " \
                        "ESPDEVSPECIFICINFO.dwType (=x%x)",
                    pInfo->dwType
                    );

                info.lResult = PHONEERR_OPERATIONFAILED;
                break;
            }
        }
        else
        {
            info.pAsyncReqInfo->dwParam1 = lpParams;
            info.pAsyncReqInfo->dwParam2 = dwSize;

            info.pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
                TSPI_lineDevSpecific_postProcess;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetButtonInfo(
    HDRVPHONE           hdPhone,
    DWORD               dwButtonLampID,
    LPPHONEBUTTONINFO   lpButtonInfo
    )
{
    static char szFuncName[] = "phoneGetButtonInfo";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "dwButtonLampID", dwButtonLampID  },
        { "lpButtonInfo",   lpButtonInfo    }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 3, params };
    PDRVPHONE   pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        if (dwButtonLampID == 0)
        {
            if (pPhone->pButtonInfo)
            {
                if (pPhone->pButtonInfo->dwUsedSize <=
                        lpButtonInfo->dwTotalSize)
                {
                    CopyMemory(
                        (LPBYTE) &lpButtonInfo->dwNeededSize,
                        (LPBYTE) &pPhone->pButtonInfo->dwNeededSize,
                        pPhone->pButtonInfo->dwUsedSize - sizeof (DWORD)
                        );
                }
                else
                {
                    lpButtonInfo->dwNeededSize =
                        pPhone->pButtonInfo->dwUsedSize;
                }
            }
        }
        else
        {
            info.lResult = PHONEERR_INVALBUTTONLAMPID;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetData(
    HDRVPHONE   hdPhone,
    DWORD       dwDataID,
    LPVOID      lpData,
    DWORD       dwSize
    )
{
    static char szFuncName[] = "phoneGetData";
    FUNC_PARAM params[] =
    {
        { szhdPhone,    hdPhone     },
        { "dwDataID",   dwDataID    },
        { "lpData",     lpData      },
        { szdwSize,     dwSize      }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        if (dwDataID != 0)
        {
            info.lResult = PHONEERR_INVALDATAID;
        }
        else if (pPhone->pData)
        {
            CopyMemory(
                lpData,
                pPhone->pData,
                (dwSize > pPhone->dwDataSize ? pPhone->dwDataSize : dwSize)
                );
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetDevCaps(
    DWORD       dwDeviceID,
    DWORD       dwTSPIVersion,
    DWORD       dwExtVersion,
    LPPHONECAPS lpPhoneCaps
    )
{
    static char szFuncName[] = "phoneGetDevCaps";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "dwExtVersion",   dwExtVersion    },
        { "lpPhoneCaps",    lpPhoneCaps     }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 4, params };
    WCHAR       buf[32];
    DWORD       dwDummy;
    PDRVPHONE   pPhone = GetPhoneFromID (dwDeviceID);


    if (Prolog (&info))
    {
        //lpPhoneCaps->dwNeededSize
        //lpPhoneCaps->dwUsedSize

        InsertVarDataString(
            lpPhoneCaps,
            &lpPhoneCaps->dwProviderInfoSize,
            gszProviderInfo
            );

        //lpPhoneCaps->dwPhoneInfoSize
        //lpPhoneCaps->dwPhoneInfoOffset

        lpPhoneCaps->dwPermanentPhoneID =
            (gESPGlobals.dwPermanentProviderID << 16) |
                (dwDeviceID - gESPGlobals.dwPhoneDeviceIDBase);

        wsprintfW (buf, L"ESP Phone %d", dwDeviceID);

        InsertVarDataString(
            lpPhoneCaps,
            &lpPhoneCaps->dwPhoneNameSize,
            buf
            );

        lpPhoneCaps->dwStringFormat = STRINGFORMAT_ASCII;
        //lpPhoneCaps->dwPhoneStates
        lpPhoneCaps->dwHookSwitchDevs = AllHookSwitchDevs;
        lpPhoneCaps->dwHandsetHookSwitchModes =
        lpPhoneCaps->dwSpeakerHookSwitchModes =
        lpPhoneCaps->dwHeadsetHookSwitchModes = AllHookSwitchModes;
        lpPhoneCaps->dwVolumeFlags = AllHookSwitchDevs;
        lpPhoneCaps->dwGainFlags = AllHookSwitchDevs;
        lpPhoneCaps->dwDisplayNumRows = 1;
        lpPhoneCaps->dwDisplayNumColumns = PHONE_DISPLAY_SIZE_IN_CHARS;
        lpPhoneCaps->dwNumRingModes = 0xffffffff;

        lpPhoneCaps->dwNumButtonLamps = 1;

        dwDummy = PHONEBUTTONMODE_FEATURE;

        InsertVarData(
            lpPhoneCaps,
            &lpPhoneCaps->dwButtonModesSize,
            (LPVOID) &dwDummy,
            sizeof (DWORD)
            );

        dwDummy = PHONEBUTTONFUNCTION_UNKNOWN;

        InsertVarData(
            lpPhoneCaps,
            &lpPhoneCaps->dwButtonFunctionsSize,
            (LPVOID) &dwDummy,
            sizeof (DWORD)
            );

        dwDummy = PHONELAMPMODE_OFF;

        InsertVarData(
            lpPhoneCaps,
            &lpPhoneCaps->dwLampModesSize,
            (LPVOID) &dwDummy,
            sizeof (DWORD)
            );


        lpPhoneCaps->dwNumSetData = 1;

        dwDummy = MAX_VAR_DATA_SIZE;

        InsertVarData(
            lpPhoneCaps,
            &lpPhoneCaps->dwSetDataSize,
            (LPVOID) &dwDummy,
            sizeof (DWORD)
            );

        lpPhoneCaps->dwNumGetData = 1;

        InsertVarData(
            lpPhoneCaps,
            &lpPhoneCaps->dwGetDataSize,
            (LPVOID) &dwDummy,
            sizeof (DWORD)
            );

        //lpPhoneCaps->dwDevSpecificSize
        //lpPhoneCaps->dwDevSpecificOffset

        if (gESPGlobals.dwSPIVersion >= 0x00020000)
        {
            //lpPhoneCaps->dwDeviceClassesSize
            //lpPhoneCaps->dwDeviceClassesOffset
            lpPhoneCaps->dwPhoneFeatures = AllPhoneFeatures;
            lpPhoneCaps->dwSettableHandsetHookSwitchModes =
            lpPhoneCaps->dwSettableSpeakerHookSwitchModes =
            lpPhoneCaps->dwSettableHeadsetHookSwitchModes = AllHookSwitchModes;
            //lpPhoneCaps->dwMonitoredHandsetHookSwitchModes
            //lpPhoneCaps->dwMonitoredSpeakerHookSwitchModes
            //lpPhoneCaps->dwMonitoredHeadsetHookSwitchModes
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetDisplay(
    HDRVPHONE   hdPhone,
    LPVARSTRING lpDisplay
    )
{
    static char szFuncName[] = "phoneGetDisplay";
    FUNC_PARAM params[] =
    {
        { szhdPhone,    hdPhone     },
        { "lpDisplay",  lpDisplay   }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 2, params };
    PDRVPHONE   pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        static DWORD    dwNeededSize = sizeof(VARSTRING) +
                            PHONE_DISPLAY_SIZE_IN_BYTES;


        if (lpDisplay->dwTotalSize >= dwNeededSize)
        {
            if (pPhone->pDisplay)
            {
                CopyMemory(
                    lpDisplay + 1,
                    pPhone->pDisplay,
                    PHONE_DISPLAY_SIZE_IN_BYTES
                    );
            }
            else
            {
                ZeroMemory (lpDisplay + 1, PHONE_DISPLAY_SIZE_IN_BYTES);
            }

            lpDisplay->dwUsedSize     = dwNeededSize;
            lpDisplay->dwStringFormat = STRINGFORMAT_ASCII;
            lpDisplay->dwStringSize   = PHONE_DISPLAY_SIZE_IN_BYTES;
            lpDisplay->dwStringOffset = sizeof (VARSTRING);
        }

        lpDisplay->dwNeededSize = dwNeededSize;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetExtensionID(
    DWORD               dwDeviceID,
    DWORD               dwTSPIVersion,
    LPPHONEEXTENSIONID  lpExtensionID
    )
{
    static char szFuncName[] = "phoneGetExtensionID";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "lpExtensionID",  lpExtensionID   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        // BUGBUG TSPI_phoneGetExtensionID:
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetGain(
    HDRVPHONE   hdPhone,
    DWORD       dwHookSwitchDev,
    LPDWORD     lpdwGain
    )
{
    static char szFuncName[] = "phoneGetGain";
    FUNC_PARAM params[] =
    {
        { szhdPhone,            hdPhone         },
        { "dwHookSwitchDev",    dwHookSwitchDev },
        { "lpdwGain",           lpdwGain        }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        switch (dwHookSwitchDev)
        {
        case PHONEHOOKSWITCHDEV_HANDSET:

            *lpdwGain = pPhone->dwHandsetGain;
            break;

        case PHONEHOOKSWITCHDEV_SPEAKER:

            *lpdwGain = pPhone->dwSpeakerGain;
            break;

        case PHONEHOOKSWITCHDEV_HEADSET:

            *lpdwGain = pPhone->dwHeadsetGain;
            break;

        }
    }

    return (Epilog (&info));
}



LONG
TSPIAPI
TSPI_phoneGetHookSwitch(
    HDRVPHONE   hdPhone,
    LPDWORD     lpdwHookSwitchDevs
    )
{
    static char szFuncName[] = "phoneGetHookSwitch";
    FUNC_PARAM params[] =
    {
        { szhdPhone,            hdPhone             },
        { "lpdwHookSwitchDevs", lpdwHookSwitchDevs  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        *lpdwHookSwitchDevs = 0;

        if (!(pPhone->dwHandsetHookSwitchMode & PHONEHOOKSWITCHMODE_ONHOOK))
        {
            *lpdwHookSwitchDevs |= PHONEHOOKSWITCHDEV_HANDSET;
        }

        if (!(pPhone->dwSpeakerHookSwitchMode & PHONEHOOKSWITCHMODE_ONHOOK))
        {
            *lpdwHookSwitchDevs |= PHONEHOOKSWITCHDEV_SPEAKER;
        }

        if (!(pPhone->dwHeadsetHookSwitchMode & PHONEHOOKSWITCHMODE_ONHOOK))
        {
            *lpdwHookSwitchDevs |= PHONEHOOKSWITCHDEV_HEADSET;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetIcon(
    DWORD   dwDeviceID,
    LPCWSTR lpszDeviceClass,
    LPHICON lphIcon
    )
{
    static char szFuncName[] = "phoneGetIcon";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "lpszDeviceClass",    lpszDeviceClass },
        { "lphIcon",            lphIcon         }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        if (lpszDeviceClass  &&
            lstrcmpiW (lpszDeviceClass, L"tapi/InvalidDeviceClass") == 0)
        {
            info.lResult = PHONEERR_INVALDEVICECLASS;
        }
        else
        {
            *lphIcon = gESPGlobals.hIconPhone;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetID(
    HDRVPHONE   hdPhone,
    LPVARSTRING lpDeviceID,
    LPCWSTR     lpszDeviceClass,
    HANDLE      hTargetProcess
    )
{
    static char szFuncName[] = "phoneGetID";
    FUNC_PARAM params[] =
    {
        { szhdPhone,            hdPhone         },
        { "lpDeviceID",         lpDeviceID      },
        { "lpszDeviceClass",    lpszDeviceClass }
        ,{ "hTargetProcess",     hTargetProcess }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;
    DWORD    i, dwDeviceID, dwNeededSize = sizeof(VARSTRING) + sizeof(DWORD);


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }

    for (i = 0; aszDeviceClasses[i]; i++)
    {
        if (lstrcmpiW (lpszDeviceClass, aszDeviceClasses[i]) == 0)
        {
            break;
        }
    }

    if (!aszDeviceClasses[i])
    {
        info.lResult = PHONEERR_NODEVICE;
        return (Epilog (&info));
    }

    if (lpDeviceID->dwTotalSize < dwNeededSize)
    {
        lpDeviceID->dwNeededSize = dwNeededSize;
        lpDeviceID->dwUsedSize = 3 * sizeof(DWORD);

        return (Epilog (&info));
    }

    if (i == 1)
    {
        dwDeviceID = pPhone->dwDeviceID;
    }
    else
    {
/*  BUGBUG TSPI_phoneGetID:    if (gbShowLineGetIDDlg)
        {
            char szDlgTitle[64];
            EVENT_PARAM params[] =
            {
                { "dwDeviceID", PT_DWORD, gdwDefLineGetIDID, 0 }
            };
            EVENT_PARAM_HEADER paramsHeader =
                { 1, szDlgTitle, 0, params };
            HWND hwnd;


            if (strlen (lpszDeviceClass) > 20)
            {
                ((char far *)lpszDeviceClass)[19] = 0;
            }

            wsprintf(
                szDlgTitle,
                "TSPI_phoneGetID: select ID for class '%s'",
                lpszDeviceClass
                );

            hwnd = CreateDialogParam(
                ghInstance,
                (LPCSTR)MAKEINTRESOURCE(IDD_DIALOG3),
                (HWND) NULL,
                (DLGPROC) CallDlgProc,
                (LPARAM) &paramsHeader
                );

            MsgLoopInTAPIClientContext (hwnd);

            dwDeviceID = params[0].dwValue;
        }
        else
        {
*/
            dwDeviceID = 0;
//        }
    }

    lpDeviceID->dwNeededSize   =
    lpDeviceID->dwUsedSize     = dwNeededSize;
    lpDeviceID->dwStringFormat = STRINGFORMAT_BINARY;
    lpDeviceID->dwStringSize   = sizeof(DWORD);
    lpDeviceID->dwStringOffset = sizeof(VARSTRING);

    *((LPDWORD)(lpDeviceID + 1)) = dwDeviceID;

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetLamp(
    HDRVPHONE   hdPhone,
    DWORD       dwButtonLampID,
    LPDWORD     lpdwLampMode
    )
{
    static char szFuncName[] = "phoneGetLamp";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "dwButtonLampID", dwButtonLampID  },
        { "lpdwLampMode",   lpdwLampMode    }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };


    if (Prolog (&info))
    {
        *lpdwLampMode = ((PDRVPHONE) hdPhone)->dwLampMode;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetRing(
    HDRVPHONE   hdPhone,
    LPDWORD     lpdwRingMode,
    LPDWORD     lpdwVolume
    )
{
    static char szFuncName[] = "phoneGetRing";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "lpdwRingMode",   lpdwRingMode    },
        { "lpdwVolume",     lpdwVolume      }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        *lpdwRingMode = pPhone->dwRingMode;
        *lpdwVolume   = pPhone->dwRingVolume;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetStatus(
    HDRVPHONE       hdPhone,
    LPPHONESTATUS   lpPhoneStatus
    )
{
    static char szFuncName[] = "phoneGetStatus";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "lpPhoneStatus",  lpPhoneStatus   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        //lpPhoneStatus->dwStatusFlags;
        lpPhoneStatus->dwRingMode      = pPhone->dwRingMode;
        lpPhoneStatus->dwRingVolume    = pPhone->dwRingVolume;
        lpPhoneStatus->dwHandsetHookSwitchMode =
            pPhone->dwHandsetHookSwitchMode;
        lpPhoneStatus->dwHandsetVolume = pPhone->dwHandsetVolume;
        lpPhoneStatus->dwHandsetGain   = pPhone->dwHandsetGain;
        lpPhoneStatus->dwSpeakerHookSwitchMode =
            pPhone->dwSpeakerHookSwitchMode;
        lpPhoneStatus->dwSpeakerVolume = pPhone->dwSpeakerVolume;
        lpPhoneStatus->dwSpeakerGain   = pPhone->dwSpeakerGain;
        lpPhoneStatus->dwHeadsetHookSwitchMode =
            pPhone->dwHeadsetHookSwitchMode;
        lpPhoneStatus->dwHeadsetVolume = pPhone->dwHeadsetVolume;
        lpPhoneStatus->dwHeadsetGain   = pPhone->dwHeadsetGain;

        // BUGBUG TSPI_phoneGetStatus: copy 0's to display buf if !pDisplay

        InsertVarData(
            lpPhoneStatus,
            &lpPhoneStatus->dwDisplaySize,
            (LPVOID) pPhone->pDisplay,
            (pPhone->pDisplay ? PHONE_DISPLAY_SIZE_IN_BYTES : 0)
            );

        InsertVarData(
            lpPhoneStatus,
            &lpPhoneStatus->dwLampModesSize,
            (LPVOID) &pPhone->dwLampMode,
            sizeof (DWORD)
            );

        //lpPhoneStatus->dwDevSpecificSize;
        //lpPhoneStatus->dwDevSpecificOffset;

        if (gESPGlobals.dwSPIVersion >= 0x00020000)
        {
            //lpPhoneStatus->
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneGetVolume(
    HDRVPHONE   hdPhone,
    DWORD       dwHookSwitchDev,
    LPDWORD     lpdwVolume
    )
{
    static char szFuncName[] = "phoneGetVolume";
    FUNC_PARAM params[] =
    {
        { szhdPhone,            hdPhone         },
        { "dwHookSwitchDev",    dwHookSwitchDev,    aHookSwitchDevs },
        { "lpdwVolume",         lpdwVolume      }
    };
    FUNC_INFO info = { szFuncName, SYNC, 3, params };
    PDRVPHONE pPhone = (PDRVPHONE) hdPhone;


    if (Prolog (&info))
    {
        switch (dwHookSwitchDev)
        {
        case PHONEHOOKSWITCHDEV_HANDSET:

            *lpdwVolume = pPhone->dwHandsetVolume;
            break;

        case PHONEHOOKSWITCHDEV_SPEAKER:

            *lpdwVolume = pPhone->dwSpeakerVolume;
            break;

        case PHONEHOOKSWITCHDEV_HEADSET:

            *lpdwVolume = pPhone->dwHeadsetVolume;
            break;

        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneNegotiateExtVersion(
    DWORD   dwDeviceID,
    DWORD   dwTSPIVersion,
    DWORD   dwLowVersion,
    DWORD   dwHighVersion,
    LPDWORD lpdwExtVersion
    )
{
    static char szFuncName[] = "phoneNegotiateExtVersion";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "dwLowVersion",   dwLowVersion    },
        { "dwHighVersion",  dwHighVersion   },
        { "lpdwExtVersion", lpdwExtVersion  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };


    if (Prolog (&info))
    {
        if (dwLowVersion == 0 ||
            dwHighVersion == 0xffffffff ||
            dwLowVersion > dwHighVersion)
        {
            info.lResult = PHONEERR_INCOMPATIBLEEXTVERSION;
        }
        else
        {
            *lpdwExtVersion = dwHighVersion;
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneNegotiateTSPIVersion(
    DWORD   dwDeviceID,
    DWORD   dwLowVersion,
    DWORD   dwHighVersion,
    LPDWORD lpdwTSPIVersion
    )
{
    static char szFuncName[] = "phoneNegotiateTSPIVersion";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,         dwDeviceID      },
        { "dwLowVersion",       dwLowVersion    },
        { "dwHighVersion",      dwHighVersion   },
        { "lpdwTSPIVersion",    lpdwTSPIVersion }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };


    if (Prolog (&info))
    {
        *lpdwTSPIVersion = gESPGlobals.dwSPIVersion;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneOpen(
    DWORD       dwDeviceID,
    HTAPIPHONE  htPhone,
    LPHDRVPHONE lphdPhone,
    DWORD       dwTSPIVersion,
    PHONEEVENT  lpfnEventProc
    )
{
    static char szFuncName[] = "phoneOpen";
    FUNC_PARAM params[] =
    {
        { szdwDeviceID,     dwDeviceID      },
        { "htPhone",        htPhone         },
        { "lphdPhone",      lphdPhone       },
        { "dwTSPIVersion",  dwTSPIVersion   },
        { "lpfnEventProc",  lpfnEventProc   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 5, params };


    if (Prolog (&info))
    {
        PDRVPHONE pPhone;


        if (!(pPhone = GetPhoneFromID (dwDeviceID)))
        {
            // BUGBUG
        }

        pPhone->htPhone = htPhone;

        *lphdPhone = (HDRVPHONE) pPhone;

        WriteEventBuffer(
            pPhone->dwDeviceID,
            WIDGETTYPE_PHONE,
            (DWORD) pPhone,
            (DWORD) htPhone,
            0,
            0
            );
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneSelectExtVersion(
    HDRVPHONE   hdPhone,
    DWORD       dwExtVersion
    )
{
    static char szFuncName[] = "phoneSelectExtVersion";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "dwExtVersion",   dwExtVersion    }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetButtonInfo_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    PDRVPHONE           pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;
    LPPHONEBUTTONINFO   pButtonInfo = (LPPHONEBUTTONINFO)
                            pAsyncReqInfo->dwParam2, pToFree;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.PhoneCritSec);

        pToFree = pPhone->pButtonInfo;
        pPhone->pButtonInfo = pButtonInfo;

        LeaveCriticalSection (&gESPGlobals.PhoneCritSec);

        DrvFree (pToFree);

        // no msg to send for this one?
    }
    else
    {
        DrvFree (pButtonInfo);
    }
}


LONG
TSPIAPI
TSPI_phoneSetButtonInfo(
    DRV_REQUESTID       dwRequestID,
    HDRVPHONE           hdPhone,
    DWORD               dwButtonLampID,
    LPPHONEBUTTONINFO   const lpButtonInfo
    )
{
    static char szFuncName[] = "phoneSetButtonInfo";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdPhone,        hdPhone         },
        { "dwButtonLampID", dwButtonLampID  },
        { "lpButtonInfo",   lpButtonInfo    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetButtonInfo_postProcess
    };


    if (Prolog (&info))
    {
        if (dwButtonLampID == 0)
        {
            DWORD               dwNeededSize;
            LPPHONEBUTTONINFO   pMyButtonInfo;


            info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;

            dwNeededSize = sizeof (PHONEBUTTONINFO) +
                lpButtonInfo->dwButtonTextSize +
                lpButtonInfo->dwDevSpecificSize +
                16;                             // 64-bit align var fields

            if ((pMyButtonInfo = (LPPHONEBUTTONINFO) DrvAlloc (dwNeededSize)))
            {
                info.pAsyncReqInfo->dwParam2 = (DWORD) pMyButtonInfo;

                CopyMemory(
                    pMyButtonInfo,
                    lpButtonInfo,
                    (gESPGlobals.dwSPIVersion > 0x00010003 ?
                        sizeof (PHONEBUTTONINFO) : 9 * sizeof (DWORD))
                    );

                pMyButtonInfo->dwTotalSize  = dwNeededSize;
                pMyButtonInfo->dwNeededSize =
                pMyButtonInfo->dwUsedSize   = sizeof (PHONEBUTTONINFO);

                InsertVarData(
                    pMyButtonInfo,
                    &pMyButtonInfo->dwButtonTextSize,
                    ((LPBYTE) lpButtonInfo) +
                        lpButtonInfo->dwButtonTextOffset,
                    lpButtonInfo->dwButtonTextSize
                    );

                InsertVarData(
                    pMyButtonInfo,
                    &pMyButtonInfo->dwDevSpecificSize,
                    ((LPBYTE) lpButtonInfo) +
                        lpButtonInfo->dwDevSpecificOffset,
                    lpButtonInfo->dwDevSpecificSize
                    );
            }
            else
            {
                info.lResult = PHONEERR_NOMEM;
            }
        }
        else
        {
            info.lResult = PHONEERR_INVALBUTTONLAMPID;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetData_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwNewDataSize = pAsyncReqInfo->dwParam3;
    LPVOID      pNewData = (LPVOID) pAsyncReqInfo->dwParam2, pToFree;
    PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.PhoneCritSec);

        pToFree            = pPhone->pData;
        pPhone->pData      = pNewData;
        pPhone->dwDataSize = dwNewDataSize;

        LeaveCriticalSection (&gESPGlobals.PhoneCritSec);

        DrvFree (pToFree);

        // no msg to send for this one?
    }
    else
    {
        DrvFree (pNewData);
    }
}


LONG
TSPIAPI
TSPI_phoneSetData(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwDataID,
    LPVOID          const lpData,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "phoneSetData";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdPhone,        hdPhone         },
        { "dwDataID",       dwDataID        },
        { "lpData",         lpData          },
        { szdwSize,         dwSize          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        5,
        params,
        TSPI_phoneSetData_postProcess
    };


    if (Prolog (&info))
    {
        if (dwDataID != 0)
        {
            info.lResult = PHONEERR_INVALDATAID;
        }
        else
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;

            if (dwSize)
            {
                info.pAsyncReqInfo->dwParam2 = (DWORD) DrvAlloc (dwSize);

                if (info.pAsyncReqInfo->dwParam2 == 0)
                {
                    info.lResult = PHONEERR_NOMEM;
                    return (Epilog (&info));
                }

                CopyMemory(
                    (LPVOID) info.pAsyncReqInfo->dwParam2,
                    lpData,
                    dwSize
                    );
            }
            else
            {
                info.pAsyncReqInfo->dwParam2 = 0;
            }

            info.pAsyncReqInfo->dwParam3 = (DWORD) dwSize;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetDisplay_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwColumn = pAsyncReqInfo->dwParam2,
                dwSize =  pAsyncReqInfo->dwParam4;
    WCHAR      *pDisplay = (LPVOID) pAsyncReqInfo->dwParam3;
    PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


    if (pAsyncReqInfo->lResult == 0)
    {
        EnterCriticalSection (&gESPGlobals.PhoneCritSec);

        if (pPhone->pDisplay ||
            (pPhone->pDisplay = DrvAlloc (PHONE_DISPLAY_SIZE_IN_BYTES)))
        {
            CopyMemory(
                pPhone->pDisplay + dwColumn,
                pDisplay,
                dwSize
                );

            SendPhoneEvent (pPhone, PHONE_STATE, PHONESTATE_DISPLAY, 0, 0);
        }
        else
        {
            pAsyncReqInfo->lResult = PHONEERR_NOMEM;
        }

        LeaveCriticalSection (&gESPGlobals.PhoneCritSec);
    }

    DoCompletion (pAsyncReqInfo, bAsync);

    DrvFree (pDisplay);
}


LONG
TSPIAPI
TSPI_phoneSetDisplay(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwRow,
    DWORD           dwColumn,
    LPCWSTR         lpsDisplay,
    DWORD           dwSize
    )
{
    static char szFuncName[] = "phoneSetDisplay";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdPhone,        hdPhone     },
        { "dwRow",          dwRow       },
        { "dwColumn",       dwColumn    },
        { "lpsDisplay",     lpsDisplay  },
        { szdwSize,         dwSize      }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        6,
        params,
        TSPI_phoneSetDisplay_postProcess
    };


    if (Prolog (&info))
    {
        if (dwRow == 0 &&
            dwColumn < PHONE_DISPLAY_SIZE_IN_CHARS &&
            dwSize <= PHONE_DISPLAY_SIZE_IN_BYTES &&
            (dwColumn * sizeof (WCHAR) + dwSize - sizeof (WCHAR)) <
                PHONE_DISPLAY_SIZE_IN_BYTES)
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
            info.pAsyncReqInfo->dwParam2 = (DWORD) dwColumn;

            if (dwSize)
            {
                info.pAsyncReqInfo->dwParam3 = (DWORD) DrvAlloc (dwSize);

                if (info.pAsyncReqInfo->dwParam3 == 0)
                {
                    info.lResult = PHONEERR_NOMEM;
                    return (Epilog (&info));
                }

                CopyMemory(
                    (LPVOID) info.pAsyncReqInfo->dwParam3,
                    lpsDisplay,
                    dwSize
                    );
            }
            else
            {
                info.pAsyncReqInfo->dwParam3 = 0;
            }

            info.pAsyncReqInfo->dwParam4 = (DWORD) dwSize;

        }
        else
        {
            info.lResult = PHONEERR_INVALPARAM;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetGain_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD       dwHookSwitchDev = pAsyncReqInfo->dwParam2,
                    dwGain = pAsyncReqInfo->dwParam3,
                   *pdwXxxGain, dwPhoneState;
        PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


        switch (dwHookSwitchDev)
        {
        case PHONEHOOKSWITCHDEV_HANDSET:

            pdwXxxGain = &pPhone->dwHandsetGain;
            dwPhoneState = PHONESTATE_HANDSETGAIN;
            break;

        case PHONEHOOKSWITCHDEV_SPEAKER:

            pdwXxxGain = &pPhone->dwSpeakerGain;
            dwPhoneState = PHONESTATE_SPEAKERGAIN;
            break;

        default: // case PHONEHOOKSWITCHDEV_HEADSET:

            pdwXxxGain = &pPhone->dwHeadsetGain;
            dwPhoneState = PHONESTATE_HEADSETGAIN;
            break;
        }

        if (*pdwXxxGain != dwGain)
        {
            *pdwXxxGain = dwGain;
            SendPhoneEvent (pPhone, PHONE_STATE, dwPhoneState, 0, 0);
        }
    }
}


LONG
TSPIAPI
TSPI_phoneSetGain(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwHookSwitchDev,
    DWORD           dwGain
    )
{
    static char szFuncName[] = "phoneSetGain";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdPhone,            hdPhone         },
        { "dwHookSwitchDev",    dwHookSwitchDev,    aHookSwitchDevs },
        { "dwGain",             dwGain          }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetGain_postProcess
    };


    if (Prolog (&info))
    {
        info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
        info.pAsyncReqInfo->dwParam2 = (DWORD) dwHookSwitchDev;
        info.pAsyncReqInfo->dwParam3 = (DWORD) dwGain;
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetHookSwitch_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD       dwHookSwitchDevs = pAsyncReqInfo->dwParam2,
                    dwHookSwitchMode = pAsyncReqInfo->dwParam3,
                    dwPhoneStates = 0;
        PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


        if (dwHookSwitchDevs & PHONEHOOKSWITCHDEV_HANDSET &&
            pPhone->dwHandsetHookSwitchMode != dwHookSwitchMode)
        {
            pPhone->dwHandsetHookSwitchMode = dwHookSwitchMode;
            dwPhoneStates |= PHONESTATE_HANDSETHOOKSWITCH;
        }

        if (dwHookSwitchDevs & PHONEHOOKSWITCHDEV_SPEAKER &&
            pPhone->dwSpeakerHookSwitchMode != dwHookSwitchMode)
        {
            pPhone->dwSpeakerHookSwitchMode = dwHookSwitchMode;
            dwPhoneStates |= PHONESTATE_SPEAKERHOOKSWITCH;
        }

        if (dwHookSwitchDevs & PHONEHOOKSWITCHDEV_HEADSET &&
            pPhone->dwHeadsetHookSwitchMode != dwHookSwitchMode)
        {
            pPhone->dwHeadsetHookSwitchMode = dwHookSwitchMode;
            dwPhoneStates |= PHONESTATE_HEADSETHOOKSWITCH;
        }

        if (dwPhoneStates)
        {
            SendPhoneEvent(
                pPhone,
                PHONE_STATE,
                dwPhoneStates,
                dwHookSwitchMode,
                0
                );
        }
    }
}


LONG
TSPIAPI
TSPI_phoneSetHookSwitch(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwHookSwitchDevs,
    DWORD           dwHookSwitchMode
    )
{
    static char szFuncName[] = "phoneSetHookSwitch";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID         },
        { szhdPhone,            hdPhone             },
        { "dwHookSwitchDevs",   dwHookSwitchDevs,   aHookSwitchDevs },
        { "dwHookSwitchMode",   dwHookSwitchMode,   aHookSwitchModes    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetHookSwitch_postProcess
    };


    if (Prolog (&info))
    {
        info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
        info.pAsyncReqInfo->dwParam2 = (DWORD) dwHookSwitchDevs;
        info.pAsyncReqInfo->dwParam3 = (DWORD) dwHookSwitchMode;
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetLamp_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD       dwLampMode = pAsyncReqInfo->dwParam2;
        PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


        if (pPhone->dwLampMode != dwLampMode)
        {
            pPhone->dwLampMode = dwLampMode;
            SendPhoneEvent (pPhone, PHONE_STATE, PHONESTATE_LAMP, 0, 0);
        }
    }
}


LONG
TSPIAPI
TSPI_phoneSetLamp(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwButtonLampID,
    DWORD           dwLampMode
    )
{
    static char szFuncName[] = "phoneSetLamp";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID     },
        { szhdPhone,        hdPhone         },
        { "dwButtonLampID", dwButtonLampID  },
        { "dwLampMode",     dwLampMode, aLampModes   }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetLamp_postProcess
    };


    if (Prolog (&info))
    {
        if (dwButtonLampID == 0)
        {
            info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
            info.pAsyncReqInfo->dwParam2 = (DWORD) dwLampMode;
        }
        else
        {
            info.lResult = PHONEERR_INVALBUTTONLAMPID;
        }
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetRing_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD       dwRingMode = pAsyncReqInfo->dwParam2,
                    dwRingVolume = pAsyncReqInfo->dwParam3,
                    dwPhoneStates = 0;
        PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


        if (pPhone->dwRingMode != dwRingMode)
        {
            pPhone->dwRingMode = dwRingMode;
            dwPhoneStates |= PHONESTATE_RINGMODE;
        }

        if (pPhone->dwRingVolume != dwRingVolume)
        {
            pPhone->dwRingVolume = dwRingVolume;
            dwPhoneStates |= PHONESTATE_RINGVOLUME;
        }

        if (dwPhoneStates)
        {
            SendPhoneEvent(
                pPhone,
                PHONE_STATE,
                dwPhoneStates,
                0,
                0
                );
        }
    }
}


LONG
TSPIAPI
TSPI_phoneSetRing(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwRingMode,
    DWORD           dwVolume
    )
{
    static char szFuncName[] = "phoneSetRing";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,    dwRequestID },
        { szhdPhone,        hdPhone     },
        { "dwRingMode",     dwRingMode  },
        { "dwVolume",       dwVolume    }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetRing_postProcess
    };


    if (Prolog (&info))
    {
        info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
        info.pAsyncReqInfo->dwParam2 = (DWORD) dwRingMode;
        info.pAsyncReqInfo->dwParam3 = (DWORD) dwVolume;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_phoneSetStatusMessages(
    HDRVPHONE   hdPhone,
    DWORD       dwPhoneStates,
    DWORD       dwButtonModes,
    DWORD       dwButtonStates
    )
{
    static char szFuncName[] = "phoneSetStatusMessages";
    FUNC_PARAM params[] =
    {
        { szhdPhone,        hdPhone         },
        { "dwPhoneStates",  dwPhoneStates,  aPhoneStates    },
        { "dwButtonModes",  dwButtonModes,  aButtonModes    },
        { "dwButtonStates", dwButtonStates, aButtonStates   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };


    if (Prolog (&info))
    {
    }

    return (Epilog (&info));
}


void
FAR
PASCAL
TSPI_phoneSetVolume_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        DWORD       dwHookSwitchDev = pAsyncReqInfo->dwParam2,
                    dwVolume = pAsyncReqInfo->dwParam3,
                   *pdwXxxVolume, dwPhoneState;
        PDRVPHONE   pPhone = (PDRVPHONE) pAsyncReqInfo->dwParam1;


        switch (dwHookSwitchDev)
        {
        case PHONEHOOKSWITCHDEV_HANDSET:

            pdwXxxVolume = &pPhone->dwHandsetVolume;
            dwPhoneState = PHONESTATE_HANDSETVOLUME;
            break;

        case PHONEHOOKSWITCHDEV_SPEAKER:

            pdwXxxVolume = &pPhone->dwSpeakerVolume;
            dwPhoneState = PHONESTATE_SPEAKERVOLUME;
            break;

        default: // case PHONEHOOKSWITCHDEV_HEADSET:

            pdwXxxVolume = &pPhone->dwHeadsetVolume;
            dwPhoneState = PHONESTATE_HEADSETVOLUME;
            break;
        }

        if (*pdwXxxVolume != dwVolume)
        {
            *pdwXxxVolume = dwVolume;
            SendPhoneEvent (pPhone, PHONE_STATE, dwPhoneState, 0, 0);
        }
    }
}


LONG
TSPIAPI
TSPI_phoneSetVolume(
    DRV_REQUESTID   dwRequestID,
    HDRVPHONE       hdPhone,
    DWORD           dwHookSwitchDev,
    DWORD           dwVolume
    )
{
    static char szFuncName[] = "phoneSetVolume";
    FUNC_PARAM params[] =
    {
        { szdwRequestID,        dwRequestID     },
        { szhdPhone,            hdPhone         },
        { "dwHookSwitchDev",    dwHookSwitchDev }, // BUGBUG lookup
        { "dwVolume",           dwVolume        }
    };
    FUNC_INFO info =
    {
        szFuncName,
        ASYNC,
        4,
        params,
        TSPI_phoneSetVolume_postProcess
    };


    if (Prolog (&info))
    {
        info.pAsyncReqInfo->dwParam1 = (DWORD) hdPhone;
        info.pAsyncReqInfo->dwParam2 = (DWORD) dwHookSwitchDev;
        info.pAsyncReqInfo->dwParam3 = (DWORD) dwVolume;
    }

    return (Epilog (&info));
}



//
// ------------------------- TSPI_providerXxx funcs ---------------------------
//

LONG
TSPIAPI
TSPI_providerConfig(
    HWND                hwndOwner,
    DWORD               dwPermanentProviderID
    )
{
    //
    // 32-bit TAPI never actually calls this function (the corresponding
    // TUISPI_ func has taken it's place), but the Telephony control
    // panel applet does look to see if this function is exported to
    // determine whether or not the provider is configurable
    //

    return 0;
}


LONG
TSPIAPI
TSPI_providerCreateLineDevice(
    DWORD   dwTempID,
    DWORD   dwDeviceID
    )
{
    static char szFuncName[] = "providerCreateLineDevice";
    FUNC_PARAM params[] =
    {
        { "dwTempID",   dwTempID    },
        { szdwDeviceID, dwDeviceID  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
        // BUGBUG TSPI_providerCreateLineDevice: alloc new line
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerCreatePhoneDevice(
    DWORD   dwTempID,
    DWORD   dwDeviceID
    )
{
    static char szFuncName[] = "providerCreatePhoneDevice";
    FUNC_PARAM params[] =
    {
        { "dwTempID",   dwTempID    },
        { szdwDeviceID, dwDeviceID  }
    };
    FUNC_INFO info = { szFuncName, SYNC, 2, params };


    if (Prolog (&info))
    {
        // BUGBUG TSPI_providerCreatePhoneDevice: alloc new phone
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerEnumDevices(
    DWORD       dwPermanentProviderID,
    LPDWORD     lpdwNumLines,
    LPDWORD     lpdwNumPhones,
    HPROVIDER   hProvider,
    LINEEVENT   lpfnLineCreateProc,
    PHONEEVENT  lpfnPhoneCreateProc
    )
{
    static char szFuncName[] = "providerEnumDevices";
    FUNC_PARAM params[] =
    {
        { szdwPermanentProviderID,  dwPermanentProviderID   },
        { "lpdwNumLines",           lpdwNumLines            },
        { "lpdwNumPhones",          lpdwNumPhones           },
        { "hProvider",              hProvider               },
        { "lpfnLineCreateProc",     lpfnLineCreateProc      },
        { "lpfnPhoneCreateProc",    lpfnPhoneCreateProc     }
    };
    FUNC_INFO info = { szFuncName, SYNC, 6, params };


    if (Prolog (&info))
    {
        *lpdwNumLines  = gESPGlobals.dwNumLines;
        *lpdwNumPhones = gESPGlobals.dwNumPhones;

        gESPGlobals.pfnLineEvent  = lpfnLineCreateProc;
        gESPGlobals.pfnPhoneEvent = lpfnPhoneCreateProc;

        gESPGlobals.hProvider = hProvider;
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerFreeDialogInstance(
    HDRVDIALOGINSTANCE  hdDlgInst
    )
{
    static char szFuncName[] = "providerFreeDialogInstance";
    FUNC_PARAM params[] =
    {
        { "hdDlgInst",  hdDlgInst   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };


    Prolog (&info);

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerGenericDialogData(
    DWORD   dwObjectID,
    DWORD   dwObjectType,
    LPVOID  lpParams,
    DWORD   dwSize
    )
{
    static char szFuncName[] = "providerGenericDialogData";
    FUNC_PARAM params[] =
    {
        { "dwObjectID",     dwObjectID      },
        { "dwObjectType",   dwObjectType    },
        { "lpszParams",      lpParams       },
        { "dwSize",         dwSize          }
    };
    FUNC_INFO info = { szFuncName, SYNC, 4, params };


    Prolog (&info);

    lstrcpyA (lpParams, "espDlgData");

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerInit(
    DWORD               dwTSPIVersion,
    DWORD               dwPermanentProviderID,
    DWORD               dwLineDeviceIDBase,
    DWORD               dwPhoneDeviceIDBase,
    DWORD               dwNumLines,
    DWORD               dwNumPhones,
    ASYNC_COMPLETION    lpfnCompletionProc,
    LPDWORD             lpdwTSPIOptions
    )
{
    static char szFuncName[] = "providerInit";
    FUNC_PARAM params[] =
    {
        { "dwTSPIVersion",          dwTSPIVersion           },
        { szdwPermanentProviderID,  dwPermanentProviderID   },
        { "dwLineDeviceIDBase",     dwLineDeviceIDBase      },
        { "dwPhoneDeviceIDBase",    dwPhoneDeviceIDBase     },
        { "dwNumLines",             dwNumLines              },
        { "dwNumPhones",            dwNumPhones             },
        { "lpfnCompletionProc",     lpfnCompletionProc      }
    };
    FUNC_INFO info = { szFuncName, SYNC, 7, params };
    DWORD       i, dwNumTotalEntries;
    PDRVLINE    pLine;
    PDRVPHONE   pPhone;


    if (!Prolog (&info))
    {
        return (Epilog (&info));
    }


    //
    //
    //

    // BUGBUG zero out he approp gESPGlobals stuff

    gESPGlobals.bProviderShutdown = FALSE;

    ZeroMemory (gaParkedCalls, MAX_NUM_PARKED_CALLS * sizeof (PDRVCALL));


    //
    // Alloc a queue for storing async requests for async completion,
    // and start a thread to service that queue
    //

//    if (gbDisableUI == FALSE) IF THIS IS UNCOMMENTED MUST MUNGE ERROR CLEANUP
    {
        gESPGlobals.dwNumTotalQueueEntries = DEF_NUM_ASYNC_REQUESTS_IN_QUEUE;

        if (!(gESPGlobals.pAsyncRequestQueue = DrvAlloc(
                gESPGlobals.dwNumTotalQueueEntries * sizeof (DWORD)

                )))
        {
            goto TSPI_providerInit_error0;
        }

        gESPGlobals.pAsyncRequestQueueIn =
        gESPGlobals.pAsyncRequestQueueOut = gESPGlobals.pAsyncRequestQueue;

        if (!(gESPGlobals.hAsyncEventsPendingEvent = CreateEvent(
                (LPSECURITY_ATTRIBUTES) NULL,
                TRUE,   // manual reset
                FALSE,  // non-signaled
                NULL    // unnamed
                )))
        {
            goto TSPI_providerInit_error1;
        }

        if (!(gESPGlobals.hAsyncEventQueueServiceThread = CreateThread(
                (LPSECURITY_ATTRIBUTES) NULL,
                0,      // def stack size
                (LPTHREAD_START_ROUTINE) AsyncEventQueueServiceThread,
                NULL,   // thread param
                0,      // creation flags
                &i      // &dwThreadID
                )))
        {
            goto TSPI_providerInit_error2;
        }
    }


    //
    // Init sundry globals
    //

    gESPGlobals.dwPermanentProviderID = dwPermanentProviderID;
    gESPGlobals.dwLineDeviceIDBase    = dwLineDeviceIDBase;
    gESPGlobals.dwPhoneDeviceIDBase   = dwPhoneDeviceIDBase;
    gESPGlobals.dwInitialNumLines     = dwNumLines;
    gESPGlobals.dwInitialNumPhones    = dwNumPhones;
    gESPGlobals.pfnCompletion         = lpfnCompletionProc;

    gESPGlobals.hIconLine = LoadIcon(
        ghInstance,
        (LPCSTR)MAKEINTRESOURCE(PHONE_ICON) // the id's are reversed
        );

    gESPGlobals.hIconPhone = LoadIcon(
        ghInstance,
        (LPCSTR)MAKEINTRESOURCE(LINE_ICON)
        );


    //
    // Init the line lookup table & crit sec for accessing call lists
    //

    dwNumTotalEntries = dwNumLines + DEF_NUM_EXTRA_LOOKUP_ENTRIES - 1;

    if (!(gESPGlobals.pLines = DrvAlloc(
            sizeof (DRVLINETABLE) + sizeof (DRVLINE) * dwNumTotalEntries
            )))
    {
        goto TSPI_providerInit_error3;
    }

    gESPGlobals.pLines->dwNumTotalEntries = dwNumTotalEntries;
    gESPGlobals.pLines->dwNumUsedEntries  = dwNumLines;

    for (
        i = dwLineDeviceIDBase, pLine = gESPGlobals.pLines->aLines;
        i < (dwLineDeviceIDBase + dwNumLines);
        i++, pLine++
        )
    {
        pLine->dwDeviceID = i;

        if (!(pLine->aAddrs = DrvAlloc(
                sizeof (DRVADDRESS) * gESPGlobals.dwNumAddressesPerLine
                )))
        {
            goto TSPI_providerInit_error4;
        }
    }


    //
    // Init the phone lookup table
    //

    dwNumTotalEntries = dwNumPhones + DEF_NUM_EXTRA_LOOKUP_ENTRIES - 1;

    if (!(gESPGlobals.pPhones = DrvAlloc(
            sizeof (DRVPHONETABLE) + dwNumTotalEntries * sizeof(DRVPHONE)
            )))
    {
        goto TSPI_providerInit_error4;
    }

    gESPGlobals.pPhones->dwNumTotalEntries = dwNumTotalEntries;
    gESPGlobals.pPhones->dwNumUsedEntries  = dwNumPhones;

    for (
        i = dwPhoneDeviceIDBase, pPhone = gESPGlobals.pPhones->aPhones;
        i < (dwPhoneDeviceIDBase + dwNumPhones);
        i++, pPhone++
        )
    {
        pPhone->dwDeviceID = i;
    }

    if (gbDisableUI == FALSE)
    {
        WriteEventBuffer(
            0,
            WIDGETTYPE_STARTUP,
            dwNumLines,
            dwNumPhones,
            dwLineDeviceIDBase,
            dwPhoneDeviceIDBase
            );
    }

    goto TSPI_providerInit_return;


TSPI_providerInit_error4:

    for (
        i = dwLineDeviceIDBase, pLine = gESPGlobals.pLines->aLines;
        i < (dwLineDeviceIDBase + dwNumLines);
        i++, pLine++
        )
    {
        if (pLine->aAddrs)
        {
            DrvFree (pLine->aAddrs);
        }
    }

    DrvFree (gESPGlobals.pLines);

TSPI_providerInit_error3:

    gESPGlobals.bProviderShutdown = TRUE;

    while (WaitForSingleObject(
            gESPGlobals.hAsyncEventQueueServiceThread,
            0
            ) != WAIT_OBJECT_0)
    {
        SetEvent (gESPGlobals.hAsyncEventsPendingEvent);
        Sleep (0);
    }

    CloseHandle (gESPGlobals.hAsyncEventQueueServiceThread);

TSPI_providerInit_error2:

    CloseHandle (gESPGlobals.hAsyncEventsPendingEvent);

TSPI_providerInit_error1:

    DrvFree (gESPGlobals.pAsyncRequestQueue);

TSPI_providerInit_error0:

    info.lResult = LINEERR_NOMEM;

TSPI_providerInit_return:

    return (Epilog (&info)); // BUGBUG TSPI_providerInit: return 0 by default
}


LONG
TSPIAPI
TSPI_providerInstall(
    HWND                hwndOwner,
    DWORD               dwPermanentProviderID
    )
{
    //
    // 32-bit TAPI never actually calls this function (the corresponding
    // TUISPI_ func has taken it's place), but the Telephony control
    // panel applet does look to see if this function is exported to
    // determine whether or not the provider is installable
    //

    return 0;
}


LONG
TSPIAPI
TSPI_providerRemove(
    HWND                hwndOwner,
    DWORD               dwPermanentProviderID
    )
{
    //
    // 32-bit TAPI never actually calls this function (the corresponding
    // TUISPI_ func has taken it's place), but the Telephony control
    // panel applet does look to see if this function is exported to
    // determine whether or not the provider is removeable
    //

    return 0;
}


LONG
TSPIAPI
TSPI_providerShutdown(
    DWORD   dwTSPIVersion,
    DWORD   dwPermanentProviderID
    )
{
    static char szFuncName[] = "providerShutdown";
    FUNC_PARAM params[] =
    {
        { "dwTSPIVersion",  dwTSPIVersion },
        { szdwPermanentProviderID,  dwPermanentProviderID   }
    };
    FUNC_INFO   info = { szFuncName, SYNC, 2, params };
    LONG        lResult;
    DWORD       i;


    Prolog (&info);

    DestroyIcon (gESPGlobals.hIconLine);
    DestroyIcon (gESPGlobals.hIconPhone);


    //
    //
    //

//    if (gbDisableUI == FALSE)
//    {
        gESPGlobals.bProviderShutdown = TRUE;

        while (WaitForSingleObject(
                gESPGlobals.hAsyncEventQueueServiceThread,
                0
                ) != WAIT_OBJECT_0)
        {
            SetEvent (gESPGlobals.hAsyncEventsPendingEvent);
            Sleep (0);
        }

        CloseHandle (gESPGlobals.hAsyncEventQueueServiceThread);
        CloseHandle (gESPGlobals.hAsyncEventsPendingEvent);

        DrvFree (gESPGlobals.pAsyncRequestQueue);
//    }


    //
    // Free the device tables & call list crit sec
    //

    {
        PDRVLINETABLE   pTable = gESPGlobals.pLines;


        while (pTable)
        {
            PDRVLINETABLE   pNextTable = pTable->pNext;


            for (i = 0; i < pTable->dwNumUsedEntries; i++)
            {
                DrvFree (pTable->aLines[i].aAddrs);
            }

            DrvFree (pTable);
            pTable = pNextTable;
        }
    }

    {
        PDRVPHONETABLE  pTable = gESPGlobals.pPhones;


        while (pTable)
        {
            PDRVPHONE       pPhone = pTable->aPhones;
            PDRVPHONETABLE  pNextTable = pTable->pNext;


            for (i = 0; i < pTable->dwNumUsedEntries; i++, pPhone++)
            {
                if (pPhone->pData)
                {
                    DrvFree (pPhone->pData);
                }

                if (pPhone->pButtonInfo)
                {
                    DrvFree (pPhone->pButtonInfo);
                }

                if (pPhone->pDisplay)
                {
                    DrvFree (pPhone->pDisplay);
                }
            }

            DrvFree (pTable);
            pTable = pNextTable;
        }
    }


    //
    //
    //

    if (ghPBXThread)
    {
        ESPStopPBXThread (0);
    }


    //
    // Clean up any parked calls
    //

    for (i = 0; i < MAX_NUM_PARKED_CALLS; i++)
    {
        if (gaParkedCalls[i])
        {
            if (gaParkedCalls[i]->pSendingFlowspec)
            {
                DrvFree (gaParkedCalls[i]->pSendingFlowspec);
            }

            if (gaParkedCalls[i]->pReceivingFlowspec)
            {
                DrvFree (gaParkedCalls[i]->pReceivingFlowspec);
            }

            if (gaParkedCalls[i]->pCallData)
            {
                DrvFree (gaParkedCalls[i]->pCallData);
            }

            DrvFree (gaParkedCalls[i]);
        }
    }

    return (Epilog (&info));
}


LONG
TSPIAPI
TSPI_providerUIIdentify(
    LPWSTR   lpszUIDLLName
    )
{
    static char szFuncName[] = "providerUIIdentify";
    FUNC_PARAM params[] =
    {
        { "pszUIDLLName",   lpszUIDLLName   }
    };
    FUNC_INFO info = { szFuncName, SYNC, 1, params };


    Prolog (&info);

    wcscpy (lpszUIDLLName, szESPUIDLL);

    return (Epilog (&info));
}

#pragma warning (default:4047)

//
// ------------------------ Private support routines --------------------------
//


BOOL
PASCAL
IsValidDrvCall(
    PDRVCALL    pCall,
    LPDWORD     pdwCallInstance
    )
{
    try
    {
        if (pdwCallInstance)
        {
            *pdwCallInstance = pCall->dwCallInstance;
        }

        if (pCall->dwKey != DRVCALL_KEY)
        {
            return FALSE;
        }
    }
    except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
            EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    {
        return FALSE;
    }

    return TRUE;
}


VOID
ShowStr(
    BOOL    bAlertApp,
    char   *format,
    ...
    )
{
    char    buf[256];
    DWORD   dwTotalSize, dwMoveSize, dwMoveSizeWrapped = 0;
    va_list ap;


    if (gbDisableUI == TRUE)
    {
        return;
    }

    va_start(ap, format);

    dwTotalSize = wvsprintf (buf, format, ap);
    buf[dwTotalSize++] = '\r';
    buf[dwTotalSize++] = '\n';

    dwMoveSize = dwTotalSize;

    EnterCriticalSection (&gESPGlobals.DebugBufferCritSec);


    //
    // Check to see if there's enough room in the the buffer for the new
    // data, alloc more if not
    //

    if (dwMoveSize > (gESPGlobals.dwDebugBufferTotalSize -
            gESPGlobals.dwDebugBufferUsedSize))
    {
        char   *pNewDebugBuffer;
        DWORD   dwMoveSize2, dwMoveSizeWrapped2;


        if (!(pNewDebugBuffer = DrvAlloc(
                2 * gESPGlobals.dwDebugBufferTotalSize
                )))
        {
            LeaveCriticalSection (&gESPGlobals.DebugBufferCritSec);
            return;
        }

        if (gESPGlobals.pDebugBufferIn > gESPGlobals.pDebugBufferOut)
        {
            dwMoveSize2 = (DWORD) (gESPGlobals.pDebugBufferIn -
                gESPGlobals.pDebugBufferOut);

            dwMoveSizeWrapped2 = 0;
        }
        else
        {
            dwMoveSize2 = (DWORD) ((gESPGlobals.pDebugBuffer +
                gESPGlobals.dwDebugBufferTotalSize) -
                gESPGlobals.pDebugBufferOut);

            dwMoveSizeWrapped2 = (DWORD) (gESPGlobals.pDebugBufferIn -
                gESPGlobals.pDebugBuffer);
        }

        CopyMemory(
            pNewDebugBuffer,
            gESPGlobals.pDebugBufferOut,
            dwMoveSize2
            );

        if (dwMoveSizeWrapped2)
        {
            CopyMemory(
                pNewDebugBuffer + dwMoveSize2,
                gESPGlobals.pDebugBuffer,
                dwMoveSizeWrapped2
                );
        }

        DrvFree (gESPGlobals.pDebugBuffer);

        gESPGlobals.pDebugBufferIn = pNewDebugBuffer + dwMoveSize2 +
            dwMoveSizeWrapped2;

        gESPGlobals.pDebugBuffer =
        gESPGlobals.pDebugBufferOut = pNewDebugBuffer;

        gESPGlobals.dwDebugBufferTotalSize *= 2;
    }

    if (gESPGlobals.pDebugBufferIn >= gESPGlobals.pDebugBufferOut)
    {
        DWORD dwFreeSize = gESPGlobals.dwDebugBufferTotalSize -
                (gESPGlobals.pDebugBufferIn - gESPGlobals.pDebugBuffer);


        if (dwMoveSize > dwFreeSize)
        {
            dwMoveSizeWrapped = dwMoveSize - dwFreeSize;

            dwMoveSize = dwFreeSize;
        }
    }

    CopyMemory (gESPGlobals.pDebugBufferIn, buf, dwMoveSize);

    if (dwMoveSizeWrapped != 0)
    {
        CopyMemory(
            gESPGlobals.pDebugBuffer,
            buf + dwMoveSize,
            dwMoveSizeWrapped
            );

        gESPGlobals.pDebugBufferIn = gESPGlobals.pDebugBuffer +
            dwMoveSizeWrapped;
    }
    else
    {
        gESPGlobals.pDebugBufferIn += dwMoveSize;
    }

    gESPGlobals.dwDebugBufferUsedSize += dwTotalSize;

    LeaveCriticalSection (&gESPGlobals.DebugBufferCritSec);

    if (bAlertApp)
    {
        SetEvent (ghDebugOutputEvent);
    }

    va_end(ap);
}


char far *
GetFlags(
    DWORD    dwFlags,
    PLOOKUP  pLookup
    )
{
    int i;
    static char buf[256];
    char far *p = (char far *) NULL;


    if (gbDisableUI == TRUE)
    {
        return NULL;
    }

    buf[0] = 0;

    for (i = 0; (dwFlags && (pLookup[i].dwVal != 0xffffffff)); i++)
    {
        if (dwFlags & pLookup[i].dwVal)
        {
            lstrcatA (buf, pLookup[i].lpszVal);
            lstrcatA (buf, " ");

            dwFlags = dwFlags & (~pLookup[i].dwVal);
        }
    }

    if (buf[0])
    {
        if ((p = (char far *) DrvAlloc (lstrlenA (buf) + 1)))
        {
            lstrcpyA (p, buf);
        }
    }

    return p;
}


void
ShowLineEvent(
    HTAPILINE   htLine,
    HTAPICALL   htCall,
    DWORD       dwMsg,
    DWORD       dwParam1,
    DWORD       dwParam2,
    DWORD       dwParam3
    )
{
    if (gESPGlobals.dwDebugOptions & SHOW_EVENT_NOTIFICATIONS)
    {
        static DWORD adwLineMsgs[] =
        {
            LINE_ADDRESSSTATE,
            LINE_CALLINFO,
            LINE_CALLSTATE,
            LINE_CLOSE,
            LINE_DEVSPECIFIC,
            LINE_DEVSPECIFICFEATURE,
            LINE_GATHERDIGITS,
            LINE_GENERATE,
            LINE_LINEDEVSTATE,
            LINE_MONITORDIGITS,
            LINE_MONITORMEDIA,
            LINE_MONITORTONE,

            LINE_CREATE,

            LINE_NEWCALL,
            LINE_CALLDEVSPECIFIC,
            LINE_CALLDEVSPECIFICFEATURE,

            LINE_REMOVE,

            0xffffffff
        };

        static char *aszLineMsgs[] =
        {
            "LINE_ADDRESSSTATE",
            "LINE_CALLINFO",
            "LINE_CALLSTATE",
            "LINE_CLOSE",
            "LINE_DEVSPECIFIC",
            "LINE_DEVSPECIFICFEATURE",
            "LINE_GATHERDIGITS",
            "LINE_GENERATE",
            "LINE_LINEDEVSTATE",
            "LINE_MONITORDIGITS",
            "LINE_MONITORMEDIA",
            "LINE_MONITORTONE",

            "LINE_CREATE",

            "LINE_NEWCALL",
            "LINE_CALLDEVSPECIFIC",
            "LINE_CALLDEVSPECIFICFEATURE",

            "LINE_REMOVE"
        };

        int       i;
        char far *lpszParam1 = (char far *) NULL;
        char far *lpszParam2 = (char far *) NULL;
        char far *lpszParam3 = (char far *) NULL;


        for (i = 0; adwLineMsgs[i] != 0xffffffff; i++)
        {
            if (dwMsg == adwLineMsgs[i])
            {
                ShowStr(
                    FALSE,
                    "%ssent %s : htLine=x%x, htCall=x%x",
                    szCallUp,
                    aszLineMsgs[i],
                    htLine,
                    htCall
                    );

                break;
            }
        }

        if (adwLineMsgs[i] == 0xffffffff)
        {
            ShowStr(
                FALSE,
                "%ssent <unknown msg id, x%x> : htLine=x%x, htCall=x%x",
                szCallUp,
                dwMsg,
                htLine,
                htCall
                );
        }

        switch (dwMsg)
        {
        case LINE_ADDRESSSTATE:

            lpszParam2 = GetFlags (dwParam2, aAddressStates);
            break;

        case LINE_CALLINFO:

            lpszParam1 = GetFlags (dwParam1, aCallInfoStates);
            break;

        case LINE_CALLSTATE:

            lpszParam1 = GetFlags (dwParam1, aCallStates);
            break;

        case LINE_LINEDEVSTATE:

            lpszParam1 = GetFlags (dwParam1, aLineStates);
            break;

        } // switch

        ShowStr(
            FALSE,
            "%s%sdwParam1=x%x, %s",
            szCallUp,
            szTab,
            dwParam1,
            (lpszParam1 ? lpszParam1 : "")
            );

        ShowStr(
            FALSE,
            "%s%sdwParam2=x%x, %s",
            szCallUp,
            szTab,
            dwParam2,
            (lpszParam2 ? lpszParam2 : "")
            );

        ShowStr(
            TRUE,
            "%s%sdwParam3=x%x, %s",
            szCallUp,
            szTab,
            dwParam3,
            (lpszParam3 ? lpszParam3 : "")
            );

        if (lpszParam1)
        {
            DrvFree (lpszParam1);
        }

        if (lpszParam2)
        {
            DrvFree (lpszParam2);
        }

        if (lpszParam3)
        {
            DrvFree (lpszParam3);
        }
    }
}


void
ShowPhoneEvent(
    HTAPIPHONE  htPhone,
    DWORD       dwMsg,
    DWORD       dwParam1,
    DWORD       dwParam2,
    DWORD       dwParam3
    )
{
    if (gESPGlobals.dwDebugOptions & SHOW_EVENT_NOTIFICATIONS)
    {
        static DWORD adwPhoneMsgs[] =
        {
            PHONE_BUTTON,
            PHONE_CLOSE,
            PHONE_DEVSPECIFIC,
            PHONE_STATE,
            PHONE_CREATE,
            PHONE_REMOVE,
            0xffffffff
        };

        static char *aszPhoneMsgs[] =
        {
            "PHONE_BUTTON",
            "PHONE_CLOSE",
            "PHONE_DEVSPECIFIC",
            "PHONE_STATE",
            "PHONE_CREATE",
            "PHONE_REMOVE"
        };
        char far *lpszParam1 = (char far *) NULL;
        char far *lpszParam2 = (char far *) NULL;
        char far *lpszParam3 = (char far *) NULL;
        DWORD   i;


        for (i = 0; adwPhoneMsgs[i] != 0xffffffff; i++)
        {
            if (dwMsg == adwPhoneMsgs[i])
            {
                ShowStr(
                    FALSE,
                    "%ssent %s : htPhone=x%x",
                    szCallUp,
                    aszPhoneMsgs[i],
                    htPhone
                    );

                break;
            }
        }

        if (adwPhoneMsgs[i] == 0xffffffff)
        {
            ShowStr(
                FALSE,
                "%ssent <unknown msg id, x%x> : htPhone=x%x",
                szCallUp,
                dwMsg,
                htPhone
                );
        }

        switch (dwMsg)
        {
        case PHONE_BUTTON:

            lpszParam2 = GetFlags (dwParam2, aButtonModes);
            lpszParam3 = GetFlags (dwParam3, aButtonStates);
            break;

        case PHONE_STATE:

            lpszParam1 = GetFlags (dwParam1, aPhoneStates);
            break;

        } // switch

        ShowStr(
            FALSE,
            "%s%sdwParam1=x%x, %s",
            szCallUp,
            szTab,
            dwParam1,
            (lpszParam1 ? lpszParam1 : "")
            );

        ShowStr(
            FALSE,
            "%s%sdwParam2=x%x, %s",
            szCallUp,
            szTab,
            dwParam2,
            (lpszParam2 ? lpszParam2 : "")
            );

        ShowStr(
            TRUE,
            "%s%sdwParam3=x%x, %s",
            szCallUp,
            szTab,
            dwParam3,
            (lpszParam3 ? lpszParam3 : "")
            );

        if (lpszParam1)
        {
            DrvFree (lpszParam1);
        }

        if (lpszParam2)
        {
            DrvFree (lpszParam2);
        }

        if (lpszParam3)
        {
            DrvFree (lpszParam3);
        }
    }
}


BOOL
Prolog(
    PFUNC_INFO pInfo
    )
{
    BOOL  bLineFunc = (pInfo->pszFuncName[1] != 'h');
    DWORD i, j;


    if (gESPGlobals.dwDebugOptions & SHOW_FUNC_ENTRY)
    {
        ShowStr (FALSE, "TSPI_%s: enter", pInfo->pszFuncName);
    }

    if (gESPGlobals.dwDebugOptions & SHOW_PARAMETERS)
    {
        for (i = 0; i < pInfo->dwNumParams; i++)
        {
            if (pInfo->aParams[i].dwVal &&
                pInfo->aParams[i].lpszVal[3] == 'z') // "lpszXxx"
            {
                ShowStr(
                    FALSE,
                    "%s%s=x%x, '%ws'",
                    szTab,
                    pInfo->aParams[i].lpszVal,
                    pInfo->aParams[i].dwVal,
                    pInfo->aParams[i].dwVal
                    );
            }
            else if (pInfo->aParams[i].pLookup)
            {
                char buf[90];
                PLOOKUP pLookup = pInfo->aParams[i].pLookup;


                wsprintf(
                    buf,
                    "%s%s=x%x, ",
                    szTab,
                    pInfo->aParams[i].lpszVal,
                    pInfo->aParams[i].dwVal
                    );

                for (j = 0; pLookup[j].dwVal != 0xffffffff; j++)
                {
                    if (pInfo->aParams[i].dwVal & pLookup[j].dwVal)
                    {
                        lstrcatA (buf, pLookup[j].lpszVal);
                        lstrcatA (buf, " ");

                        if (lstrlenA (buf) > 60)
                        {
                            ShowStr (FALSE, buf);
                            wsprintfA (buf, "%s%s", szTab, szTab);
                        }
                    }
                }

                ShowStr (FALSE, buf);
            }
            else
            {
                ShowStr(
                    FALSE,
                    "%s%s=x%x",
                    szTab,
                    pInfo->aParams[i].lpszVal,
                    pInfo->aParams[i].dwVal
                    );
            }
        }
    }

    if (gESPGlobals.dwDebugOptions & (SHOW_PARAMETERS | SHOW_FUNC_ENTRY))
    {
        SetEvent (ghDebugOutputEvent);
    }


    //
    //
    //

    if (gdwDevSpecificRequestID  &&
         glNextRequestResult != 0  &&
         (pInfo->bAsync == FALSE  ||
             gdwNextRequestCompletionType == ESP_RESULT_RETURNRESULT)
         )
    {
        gdwDevSpecificRequestID = 0;
        pInfo->lResult = glNextRequestResult;
        return FALSE;
    }


    if (gESPGlobals.dwDebugOptions & MANUAL_RESULTS)
    {
        char szDlgTitle[64];
        EVENT_PARAM params[] =
        {
            { "lResult", PT_ORDINAL, 0, (bLineFunc ? aLineErrs : aPhoneErrs) }
        };
        EVENT_PARAM_HEADER paramsHeader =
            { 1, szDlgTitle, 0, params };
        HWND hwnd;


        wsprintf (szDlgTitle, "TSPI_%s request result", pInfo->pszFuncName);

        DialogBoxParam(
            ghInstance,
            (LPCSTR)MAKEINTRESOURCE(IDD_DIALOG1),
            (HWND) NULL,
            (DLGPROC) ValuesDlgProc,
            (LPARAM) &paramsHeader
            );


        //
        // If user selected to synchronously return an error we'll save
        // the error & return FALSE to indicate to caller that it should
        // return immediately.
        //

        if (params[0].dwValue)
        {
            pInfo->lResult = (LONG) params[0].dwValue;

            return FALSE;
        }
    }


    if (pInfo->bAsync)
    {
        //
        // Alloc & init an async request info structure
        //

        PASYNC_REQUEST_INFO pAsyncReqInfo = (PASYNC_REQUEST_INFO)
            DrvAlloc (sizeof(ASYNC_REQUEST_INFO));


        if ((pInfo->pAsyncReqInfo = pAsyncReqInfo))
        {
            pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
                pInfo->pfnPostProcessProc;
            pAsyncReqInfo->dwRequestID        = pInfo->aParams[0].dwVal;

            pAsyncReqInfo->pszFuncName = pInfo->pszFuncName;
        }
        else
        {
            pInfo->lResult = (bLineFunc ?
                LINEERR_OPERATIONFAILED : PHONEERR_OPERATIONFAILED);

            return FALSE;
        }
    }

    return TRUE;
}


LONG
Epilog(
    PFUNC_INFO pInfo
    )
{
    if (pInfo->bAsync)
    {
        PASYNC_REQUEST_INFO pAsyncReqInfo = pInfo->pAsyncReqInfo;


        if (pInfo->lResult == 0)
        {
            //
            //
            //

            if (gdwDevSpecificRequestID  &&
                pInfo->aParams[0].dwVal != gdwDevSpecificRequestID)
            {
                gdwDevSpecificRequestID = 0;

                if (glNextRequestResult != 0)
                {
                    pAsyncReqInfo->lResult = glNextRequestResult;
                }

                if (gdwNextRequestCompletionType ==
                        ESP_RESULT_CALLCOMPLPROCASYNC)
                {
                    goto complete_event_async;
                }
                else
                {
                    goto complete_event_sync;
                }
            }

            switch (gESPGlobals.dwCompletionMode)
            {
            case COMPLETE_ASYNC_EVENTS_SYNCHRONOUSLY:

                //
                // We're completing this async request synchronously, so call
                // the post processing proc (if there is one) or call the
                // completion routine directly
                //

complete_event_sync:

                if (pInfo->pAsyncReqInfo->pfnPostProcessProc)
                {
                    (*((POSTPROCESSPROC) pAsyncReqInfo->pfnPostProcessProc))(
                        pInfo->pAsyncReqInfo,
                        SYNC
                        );
                }
                else
                {
                    DoCompletion (pAsyncReqInfo, SYNC);
                }

                DrvFree (pAsyncReqInfo);

                break;

            case COMPLETE_ASYNC_EVENTS_ASYNCHRONOUSLY:

                //
                // Safely add the async request to the queue (careful to
                // reset pDataIn when we reach the end of the buffer)
                //

complete_event_async:

                EnterCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);

                if (gESPGlobals.dwNumUsedQueueEntries ==
                        gESPGlobals.dwNumTotalQueueEntries)
                {
                    //
                    // We've max'd out our ring buf, so try to grow it
                    //

                    DWORD  *pNewAsyncRequestQueue, dwMoveSize;


                    if (!(pNewAsyncRequestQueue = DrvAlloc(
                            2 * gESPGlobals.dwNumTotalQueueEntries *
                            sizeof (DWORD)
                            )))
                    {
                        LeaveCriticalSection(
                            &gESPGlobals.AsyncEventQueueCritSec
                            );

                        goto complete_event_sync;
                    }

                    dwMoveSize = ((gESPGlobals.pAsyncRequestQueue +
                        gESPGlobals.dwNumTotalQueueEntries) -
                        gESPGlobals.pAsyncRequestQueueOut) * sizeof (DWORD);

                    CopyMemory(
                        pNewAsyncRequestQueue,
                        gESPGlobals.pAsyncRequestQueueOut,
                        dwMoveSize
                        );

                    CopyMemory(
                        ((LPBYTE) pNewAsyncRequestQueue) + dwMoveSize,
                        gESPGlobals.pAsyncRequestQueue,
                        (gESPGlobals.pAsyncRequestQueueOut -
                            gESPGlobals.pAsyncRequestQueue) * sizeof (DWORD)
                        );

                    DrvFree (gESPGlobals.pAsyncRequestQueue);

                    gESPGlobals.pAsyncRequestQueue    =
                    gESPGlobals.pAsyncRequestQueueOut = pNewAsyncRequestQueue;

                    gESPGlobals.pAsyncRequestQueueIn = pNewAsyncRequestQueue +
                        gESPGlobals.dwNumTotalQueueEntries;

                    gESPGlobals.dwNumTotalQueueEntries *= 2;
                }

                *(gESPGlobals.pAsyncRequestQueueIn) = (DWORD) pAsyncReqInfo;

                gESPGlobals.pAsyncRequestQueueIn++;

                if (gESPGlobals.pAsyncRequestQueueIn ==
                        (gESPGlobals.pAsyncRequestQueue +
                            gESPGlobals.dwNumTotalQueueEntries))
                {
                    gESPGlobals.pAsyncRequestQueueIn =
                        gESPGlobals.pAsyncRequestQueue;
                }

                gESPGlobals.dwNumUsedQueueEntries++;

                if (gESPGlobals.dwNumUsedQueueEntries == 1)
                {
                    SetEvent (gESPGlobals.hAsyncEventsPendingEvent);
                }

                LeaveCriticalSection (&gESPGlobals.AsyncEventQueueCritSec);

                break;

            case COMPLETE_ASYNC_EVENTS_SYNC_AND_ASYNC:
            {
                //
                // Decide whether to complete this request sync or async,
                // then jump to the right place
                //

                static i = 0;


                if (i++ % 2)
                {
                    goto complete_event_sync;
                }
                else
                {
                    goto complete_event_async;
                }

                break;
            }
            case COMPLETE_ASYNC_EVENTS_MANUALLY:

                WriteEventBuffer(
                    (DWORD) pAsyncReqInfo->dwRequestID,
                    WIDGETTYPE_ASYNCREQUEST,
                    (DWORD) pAsyncReqInfo,
                    0,
                    0,
                    0
                    );

                break;
            }


            //
            // Finally, return the request ID
            //

            pInfo->lResult = pInfo->aParams[0].dwVal;
        }
        else if (pAsyncReqInfo)
        {
            DrvFree (pAsyncReqInfo);
        }
    }

    if (gESPGlobals.dwDebugOptions & SHOW_FUNC_EXIT)
    {
        ShowStr(
            TRUE,
            "TSPI_%s: exit, returning x%x",
            pInfo->pszFuncName,
            pInfo->lResult
            );
    }

    return (pInfo->lResult);
}


void
PASCAL
SendLineEvent(
    PDRVLINE    pLine,
    PDRVCALL    pCall,
    DWORD       dwMsg,
    DWORD       dwParam1,
    DWORD       dwParam2,
    DWORD       dwParam3
    )
{
    //
    //
    //

    (*(gESPGlobals.pfnLineEvent))(
        pLine->htLine,
        (pCall ? pCall->htCall : (HTAPICALL) NULL),
        dwMsg,
        dwParam1,
        dwParam2,
        dwParam3
        );

    if (dwMsg == LINE_CALLSTATE)
    {
        //PostUpdateWidgetListMsg();
    }

    ShowLineEvent(
        pLine->htLine,
        (pCall ? pCall->htCall : (HTAPICALL) NULL),
        dwMsg,
        dwParam1,
        dwParam2,
        dwParam3
        );
}


void
PASCAL
SendPhoneEvent(
    PDRVPHONE   pPhone,
    DWORD       dwMsg,
    DWORD       dwParam1,
    DWORD       dwParam2,
    DWORD       dwParam3
    )
{
    //
    //
    //

    (*(gESPGlobals.pfnPhoneEvent))(
        pPhone->htPhone,
        dwMsg,
        dwParam1,
        dwParam2,
        dwParam3
        );

    ShowPhoneEvent(
        pPhone->htPhone,
        dwMsg,
        dwParam1,
        dwParam2,
        dwParam3
        );
}


void
PASCAL
DoCompletion(
    PASYNC_REQUEST_INFO pAsyncRequestInfo,
    BOOL                bAsync
    )
{
    (*(gESPGlobals.pfnCompletion))(
        pAsyncRequestInfo->dwRequestID,
        pAsyncRequestInfo->lResult
        );

    if (gESPGlobals.dwDebugOptions & SHOW_COMPLETION_NOTIFICATIONS)
    {
        ShowStr(
            TRUE,
            "%sTSPI_%s: calling compl proc (%ssync), dwReqID=x%x, lResult = x%x",
            szCallUp,
            pAsyncRequestInfo->pszFuncName,
            (bAsync ? "a" : ""),
            pAsyncRequestInfo->dwRequestID,
            pAsyncRequestInfo->lResult
            );
    }
}


LONG
PASCAL
SetCallState(
    PDRVCALL pCall,
    DWORD    dwExpectedCallInstance,
    DWORD    dwValidCurrentStates,
    DWORD    dwNewCallState,
    DWORD    dwNewCallStateMode,
    BOOL     bSendStateMsgToExe
    )
{
    LONG    lResult = 0;
    DWORD   dwActualCallInstance;


    EnterCriticalSection (&gESPGlobals.CallListCritSec);

    if (!IsValidDrvCall (pCall, &dwActualCallInstance)  ||
        dwActualCallInstance != dwExpectedCallInstance)
    {
        LeaveCriticalSection (&gESPGlobals.CallListCritSec);
        return LINEERR_INVALCALLHANDLE;
    }

    if (lResult == 0)
    {
        //
        // Check to see that the call is in a valid (expected) state.
        // If dwValidCurrentStates == 0xffffffff then we want to chg the
        // state regardless of the current state.
        //

        if ((dwValidCurrentStates != 0xffffffff) &&
            !(dwValidCurrentStates & pCall->dwCallState))
        {
            LeaveCriticalSection (&gESPGlobals.CallListCritSec);
            return LINEERR_INVALCALLSTATE;
        }


        //
        // Only chg the call state if it differs from the existing one
        //

        if (dwNewCallState != pCall->dwCallState)
        {
            pCall->dwCallState     = dwNewCallState;
            pCall->dwCallStateMode = dwNewCallStateMode;

            SendLineEvent(
                pCall->pLine,
                pCall,
                LINE_CALLSTATE,
                dwNewCallState,
                dwNewCallStateMode,
                pCall->dwMediaMode
                );

            if (bSendStateMsgToExe)
            {
                WriteEventBuffer(
                    ((PDRVLINE) pCall->pLine)->dwDeviceID,
                    WIDGETTYPE_CALL,
                    (DWORD) pCall,
                    (DWORD) pCall->htCall,
                    dwNewCallState,
                    pCall->dwAddressID
                    );
            }


            //
            // If this call has an associated call/endpoint then pass
            // connected (1st time only) or disconnected msgs to that
            // call so they know if we've answered or hung up
            //

            if (pCall->pDestCall)
            {
                if (dwNewCallState == LINECALLSTATE_CONNECTED)
                {
                    if (!pCall->bConnectedToDestCall)
                    {
                        if (IsValidDrvCall (pCall->pDestCall, NULL))
                        {
                            if (SetCallState(
                                    pCall->pDestCall,
                                    pCall->pDestCall->dwCallInstance,
                                    0xffffffff,
                                    LINECALLSTATE_CONNECTED,
                                    0,
                                    TRUE

                                    ) == 0)
                            {
                                // NOTE: use 0x55 to aid in debugging
                                //       wild writes

                                pCall->bConnectedToDestCall =
                                pCall->pDestCall->bConnectedToDestCall = 0x55;
                            }
                        }
                        else
                        {
                            pCall->pDestCall = NULL;
                        }
                    }
                }
                else if (dwNewCallState == LINECALLSTATE_IDLE ||
                         dwNewCallState == LINECALLSTATE_DISCONNECTED)
                {
                    pCall->pDestCall->pDestCall = NULL;

                    SetCallState(
                        pCall->pDestCall,
                        pCall->pDestCall->dwCallInstance,
                        0xffffffff,
                        LINECALLSTATE_DISCONNECTED,
                        (dwNewCallState == LINECALLSTATE_DISCONNECTED ?
                            dwNewCallStateMode : LINEDISCONNECTMODE_NORMAL),
                        TRUE
                        );

                    pCall->pDestCall = NULL;
                }
            }
        }
    }

    LeaveCriticalSection (&gESPGlobals.CallListCritSec);

    return lResult;
}


void
PASCAL
WriteEventBuffer(
    DWORD   dwParam1,
    DWORD   dwParam2,
    DWORD   dwParam3,
    DWORD   dwParam4,
    DWORD   dwParam5,
    DWORD   dwParam6
    )
{
    WIDGETEVENT event ={dwParam1,dwParam2,dwParam3,dwParam4,dwParam5,dwParam6};


    if (gbDisableUI == TRUE)
    {
        return;
    }

    EnterCriticalSection (&gESPGlobals.EventBufferCritSec);

    if ((gESPGlobals.dwEventBufferUsedSize + sizeof (WIDGETEVENT)) >
            gESPGlobals.dwEventBufferTotalSize)
    {
        //
        // We've max'd out our ring buf, so try to grow it
        //

        char   *pNewEventBuffer;
        DWORD   dwMoveSize;


        if (!(pNewEventBuffer = DrvAlloc (
                2 * gESPGlobals.dwEventBufferTotalSize
                )))
        {
            LeaveCriticalSection (&gESPGlobals.EventBufferCritSec);

            // log some sort of error

            return;
        }

        dwMoveSize = (gESPGlobals.pEventBuffer +
            gESPGlobals.dwEventBufferTotalSize) - gESPGlobals.pEventBufferOut;

        CopyMemory (pNewEventBuffer, gESPGlobals.pEventBufferOut, dwMoveSize);

        CopyMemory(
            pNewEventBuffer + dwMoveSize,
            gESPGlobals.pEventBuffer,
            gESPGlobals.pEventBufferOut - gESPGlobals.pEventBuffer
            );

        DrvFree (gESPGlobals.pEventBuffer);

        gESPGlobals.pEventBuffer    =
        gESPGlobals.pEventBufferOut = pNewEventBuffer;

        gESPGlobals.pEventBufferIn = pNewEventBuffer +
            gESPGlobals.dwEventBufferTotalSize;

        gESPGlobals.dwEventBufferTotalSize *= 2;
    }

    CopyMemory (gESPGlobals.pEventBufferIn, &event, sizeof (WIDGETEVENT));

    gESPGlobals.dwEventBufferUsedSize += sizeof (WIDGETEVENT);

    if ((gESPGlobals.pEventBufferIn +=  sizeof (WIDGETEVENT)) >=
            (gESPGlobals.pEventBuffer + gESPGlobals.dwEventBufferTotalSize))
    {
         gESPGlobals.pEventBufferIn = gESPGlobals.pEventBuffer;
    }

    SetEvent (ghWidgetEventsEvent);

    LeaveCriticalSection (&gESPGlobals.EventBufferCritSec);
}


LPVOID
DrvAlloc(
    size_t numBytes
    )
{
    LPVOID p = (LPVOID) LocalAlloc (LPTR, numBytes);


    if (!p)
    {
        ShowStr (TRUE, "Error: DrvAlloc (x%x) failed", (DWORD) numBytes);
    }

    return p;
}


void
DrvFree(
    LPVOID  p
    )
{
    if (p)
    {
#if DBG
        //
        // Fill the buf to free with 0xa5's to facilitate debugging
        //

        FillMemory (p, LocalSize (LocalHandle (p)), 0xa5);
#endif
        LocalFree (p);
    }
}


PDRVADDRESS
PASCAL
FindFreeAddress(
    PDRVLINE    pLine
    )
{
    DWORD       i;
    PDRVADDRESS pAddr = pLine->aAddrs;


    for (i = 0; i < gESPGlobals.dwNumAddressesPerLine; i++)
    {
        if (pAddr->dwNumCalls < gESPGlobals.dwNumCallsPerAddress)
        {
            return pAddr;
        }

        pAddr++;
    }

    return NULL;
}


LONG
PASCAL
AllocCall(
    PDRVLINE            pLine,
    HTAPICALL           htCall,
    LPLINECALLPARAMS    pCallParams,
    PDRVCALL           *ppCall
    )
{
    LONG        lResult = 0;
    DWORD       i;
    PDRVCALL    pCall;
    PDRVADDRESS pAddr;


    if (!(pCall = (PDRVCALL) DrvAlloc (sizeof (DRVCALL))))
    {
        return LINEERR_NOMEM;
    }

    pCall->pLine = pLine;
    pCall->htCall = htCall;


    EnterCriticalSection (&gESPGlobals.CallListCritSec);

    if (!pCallParams)
    {
        if (!(pAddr = FindFreeAddress (pLine)))
        {
            lResult = LINEERR_CALLUNAVAIL;
            goto AllocCall_cleanup;
        }

        pCall->dwMediaMode  = LINEMEDIAMODE_INTERACTIVEVOICE;
        pCall->dwBearerMode = LINEBEARERMODE_VOICE;
    }
    else
    {

AllocCall_findTheAddr:

        if (pCallParams->dwAddressMode == LINEADDRESSMODE_ADDRESSID)
        {
            if (pCallParams->dwAddressID >= gESPGlobals.dwNumAddressesPerLine)
            {
                lResult = LINEERR_INVALADDRESSID;
                goto AllocCall_cleanup;
            }

            if (pCallParams->dwAddressID == 0)
            {
                //
                // App doesn't care which addr to make call on
                //

                if (!(pAddr = FindFreeAddress (pLine)))
                {
                    lResult = LINEERR_CALLUNAVAIL;
                    goto AllocCall_cleanup;
                }
            }
            else
            {
                //
                // App wants call on a particular addr
                //

                pAddr = pLine->aAddrs + pCallParams->dwAddressID;

                if (pAddr->dwNumCalls > gESPGlobals.dwNumCallsPerAddress)
                {
                    lResult = LINEERR_CALLUNAVAIL;
                    goto AllocCall_cleanup;
                }
            }
        }
        else // (pCallParams->dwAddressMode == LINEADDRESSMODE_DIALABLEADDR)
        {

// BUGBUG AllocCall: handle dialable addr

            pCallParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
            pCallParams->dwAddressID = 0;
            goto AllocCall_findTheAddr;
        }

        pCall->dwMediaMode  = pCallParams->dwMediaMode;
        pCall->dwBearerMode = pCallParams->dwBearerMode;
    }


    //
    // Call was successfully "made", so do all the common stuff like
    // adding it to the addr's list, setting the attributes, etc...
    //

    if ((pCall->pNext = pAddr->pCalls))
    {
        pCall->pNext->pPrev = pCall;
    }

    pAddr->pCalls = pCall;

    pAddr->dwNumCalls++;

    pCall->dwKey          = DRVCALL_KEY;
    pCall->pLine          = pLine;
    pCall->dwAddressID    = pAddr - pLine->aAddrs;
    pCall->dwCallInstance = gdwCallInstance++;
    pCall->dwCallID       = gdwCallID++;


AllocCall_cleanup:

    LeaveCriticalSection (&gESPGlobals.CallListCritSec);

    if (lResult == 0)
    {
        *ppCall = pCall;
    }
    else
    {
        DrvFree (pCall);
    }

    return lResult;
}


void
PASCAL
FreeCall(
    PDRVCALL    pCall,
    DWORD       dwExpectedCallInstance
    )
{
    DWORD   dwActualCallInstance;


    EnterCriticalSection (&gESPGlobals.CallListCritSec);

    if (IsValidDrvCall (pCall, &dwActualCallInstance)  &&
        dwActualCallInstance == dwExpectedCallInstance)
    {
        PDRVADDRESS pAddr;


        pCall->dwKey = INVAL_KEY;

        pAddr = ((PDRVLINE) pCall->pLine)->aAddrs + pCall->dwAddressID;
        if (pCall->pNext)
        {
            pCall->pNext->pPrev = pCall->pPrev;
        }

        if (pCall->pPrev)
        {
            pCall->pPrev->pNext = pCall->pNext;
        }
        else
        {
            pAddr->pCalls = pCall->pNext;
        }

        if (pCall->pDestCall)
        {
            pCall->pDestCall->pDestCall = NULL;
        }

        pAddr->dwNumCalls--;
    }
    else
    {
        pCall = NULL;
    }

    LeaveCriticalSection (&gESPGlobals.CallListCritSec);

    if (pCall)
    {
        if (pCall->pSendingFlowspec)
        {
            DrvFree (pCall->pSendingFlowspec);
        }

        if (pCall->pReceivingFlowspec)
        {
            DrvFree (pCall->pReceivingFlowspec);
        }

        if (pCall->pCallData)
        {
            DrvFree (pCall->pCallData);
        }

        if (pCall->dwGatherDigitsEndToEndID)
        {
            (gESPGlobals.pfnLineEvent)(
                ((PDRVLINE) pCall->pLine)->htLine,
                pCall->htCall,
                LINE_GATHERDIGITS,
                LINEGATHERTERM_CANCEL,
                pCall->dwGatherDigitsEndToEndID,
                0
                );
        }

        if (pCall->dwGenerateDigitsEndToEndID)
        {
            (gESPGlobals.pfnLineEvent)(
                ((PDRVLINE) pCall->pLine)->htLine,
                pCall->htCall,
                LINE_GENERATE,
                LINEGENERATETERM_CANCEL,
                pCall->dwGenerateDigitsEndToEndID,
                0
                );
        }

        if (pCall->dwGenerateToneEndToEndID)
        {
            (gESPGlobals.pfnLineEvent)(
                ((PDRVLINE) pCall->pLine)->htLine,
                pCall->htCall,
                LINE_GENERATE,
                LINEGENERATETERM_CANCEL,
                pCall->dwGenerateToneEndToEndID,
                0
                );
        }

        if (pCall->dwMonitorToneListID)
        {
            (gESPGlobals.pfnLineEvent)(
                ((PDRVLINE) pCall->pLine)->htLine,
                pCall->htCall,
                LINE_MONITORTONE,
                0,
                pCall->dwMonitorToneListID,
                0
                );
        }

        DrvFree (pCall);
    }
}


PDRVLINE
PASCAL
GetLineFromID(
    DWORD   dwDeviceID
    )
{
    PDRVLINE    pLine;


    if (dwDeviceID < gESPGlobals.dwLineDeviceIDBase)
    {
        pLine = (PDRVLINE) NULL;
    }
    else if (dwDeviceID < (gESPGlobals.dwLineDeviceIDBase +
                gESPGlobals.dwInitialNumLines))
    {
        pLine = gESPGlobals.pLines->aLines +
            (dwDeviceID - gESPGlobals.dwLineDeviceIDBase);
    }
    else
    {
        // BUGBUG GetLineFromID: find a line created on the fly
        pLine = (PDRVLINE) NULL;
    }

    return pLine;
}


PDRVPHONE
PASCAL
GetPhoneFromID(
    DWORD   dwDeviceID
    )
{
    PDRVPHONE   pPhone;


    if (dwDeviceID < (gESPGlobals.dwPhoneDeviceIDBase +
            gESPGlobals.dwInitialNumPhones))
    {
        pPhone = gESPGlobals.pPhones->aPhones +
            (dwDeviceID - gESPGlobals.dwPhoneDeviceIDBase);
    }
    else
    {
        // BUGBUG GetPhoneFromID: find a phone created on the fly
        pPhone = (PDRVPHONE) NULL;
    }

    return pPhone;
}


long
ESPAttach(
    long    lProcessID,
    long   *phShutdownEvent,
    long   *phDebugOutputEvent,
    long   *phWidgetEventsEvent
    )
{
    HANDLE hMyProcess, hEspExeProcess;


    hMyProcess = GetCurrentProcess();

    hEspExeProcess = OpenProcess(
        PROCESS_DUP_HANDLE,
        TRUE,
        (DWORD) lProcessID
        );

    if (!DuplicateHandle(
            hMyProcess,
            ghDebugOutputEvent,
            hEspExeProcess,
            (HANDLE *) phDebugOutputEvent,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
            ) ||

        !DuplicateHandle(
            hMyProcess,
            ghShutdownEvent,
            hEspExeProcess,
            (HANDLE *) phShutdownEvent,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
            ) ||

        !DuplicateHandle(
            hMyProcess,
            ghWidgetEventsEvent,
            hEspExeProcess,
            (HANDLE *) phWidgetEventsEvent,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
            ))
    {
        DBGOUT((1, "ESPAttach: DupHandle failed, err=%d", GetLastError()));
        return -1;
    }

    return 0;
}


void
ESPSetOptions(
    long    lDebugOptions,
    long    lCompletionMode
    )
{
    gESPGlobals.dwDebugOptions   = (DWORD) lDebugOptions;
    gESPGlobals.dwCompletionMode = (DWORD) lCompletionMode;

    if (!gbInteractWithDesktop)
    {
        gESPGlobals.dwDebugOptions &= ~MANUAL_RESULTS;
    }
}


void
ESPCompleteRequest(
    long    lAsyncReqInfo,
    long    lResult
    )
{
    PASYNC_REQUEST_INFO pAsyncReqInfo = (PASYNC_REQUEST_INFO) lAsyncReqInfo;


    pAsyncReqInfo->lResult = lResult;

    if (pAsyncReqInfo->pfnPostProcessProc)
    {
        (*((POSTPROCESSPROC) pAsyncReqInfo->pfnPostProcessProc))(
            pAsyncReqInfo,
            ASYNC
            );
    }
    else
    {
        DoCompletion (pAsyncReqInfo, ASYNC);
    }

    DrvFree (pAsyncReqInfo);
}


long
ESPEvent(
    long    htDevice,
    long    htCall,
    long    dwMsg,
    long    dwParam1,
    long    dwParam2,
    long    dwParam3
    )
{
    switch (dwMsg)
    {
    case LINE_CALLSTATE:
    {
        DWORD   dwCallInst;

// BUGBUG when state == conf or prevState == conf need to add/rem conf list

        try
        {
            dwCallInst = ((PDRVCALL) htCall)->dwCallInstance;
        }
        except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
                EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
        {
            break;
        }

        SetCallState(
            (PDRVCALL) htCall,
            dwCallInst,
            0xffffffff,
            dwParam1,
            dwParam2,
            TRUE
            );

        break;
    }
    case LINE_ADDRESSSTATE:
    case LINE_CALLDEVSPECIFIC:
    case LINE_CALLDEVSPECIFICFEATURE:
    case LINE_CALLINFO:
    case LINE_CLOSE:
    case LINE_DEVSPECIFIC:
    case LINE_DEVSPECIFICFEATURE:
    case LINE_GATHERDIGITS:
    case LINE_GENERATE:
    case LINE_LINEDEVSTATE:
    case LINE_MONITORDIGITS:
    case LINE_MONITORMEDIA:
    case LINE_MONITORTONE:
    case LINE_REMOVE:

        (*gESPGlobals.pfnLineEvent)(
            (HTAPILINE) htDevice,
            (HTAPICALL) htCall,
            dwMsg,
            dwParam1,
            dwParam2,
            dwParam3
            );

        ShowLineEvent(
            (HTAPILINE) htDevice,
            (HTAPICALL) htCall,
            dwMsg,
            dwParam1,
            dwParam2,
            dwParam3
            );

        break;

    case LINE_CREATEDIALOGINSTANCE:
    {
        TUISPICREATEDIALOGINSTANCEPARAMS createDlgParams;


        ZeroMemory(
            &createDlgParams,
            sizeof (TUISPICREATEDIALOGINSTANCEPARAMS)
            );

        createDlgParams.dwRequestID   = dwParam1;
        createDlgParams.hdDlgInst     = (HDRVDIALOGINSTANCE) 0x55;
        createDlgParams.lpszUIDLLName = szESPUIDLL;
        createDlgParams.lpParams      = "genDlg params";
        createDlgParams.dwSize        = 14;

        (*gESPGlobals.pfnLineEvent)(
            (HTAPILINE) gESPGlobals.hProvider,
            (HTAPICALL) NULL,
            (DWORD) LINE_CREATEDIALOGINSTANCE,
            (DWORD) &createDlgParams,
            0,
            0
            );

        ShowLineEvent(
            (HTAPILINE) gESPGlobals.hProvider,
            NULL,
            LINE_CREATEDIALOGINSTANCE,
            (DWORD) &createDlgParams,
            0,
            0
            );

            break;
    }
    case LINE_SENDDIALOGINSTANCEDATA:
    {
        char data[] = "dlgInstData";


        (*gESPGlobals.pfnLineEvent)(
            (HTAPILINE) dwParam1,
            (HTAPICALL) NULL,
            (DWORD) LINE_SENDDIALOGINSTANCEDATA,
            (DWORD) data,
            12,
            0
            );

        ShowLineEvent(
            (HTAPILINE) dwParam1,
            NULL,
            LINE_CREATEDIALOGINSTANCE,
            (DWORD) data,
            12,
            0
            );

            break;
    }
    case PHONE_BUTTON:
    case PHONE_CLOSE:
    case PHONE_DEVSPECIFIC:
    case PHONE_STATE:
    case PHONE_REMOVE:

        (*gESPGlobals.pfnPhoneEvent)(
            (HTAPIPHONE) htDevice,
            dwMsg,
            dwParam1,
            dwParam2,
            dwParam3
            );

        ShowPhoneEvent(
            (HTAPIPHONE) htDevice,
            dwMsg,
            dwParam1,
            dwParam2,
            dwParam3
            );

        break;
    }

    return 0;
}


void
ESPGetDebugOutput(
    unsigned char  *pBuffer,
    long           *plSize
    )
{
    DWORD   dwTotalSize, dwMoveSize, dwMoveSizeWrapped;


    EnterCriticalSection (&gESPGlobals.DebugBufferCritSec);

    dwMoveSize =
    dwTotalSize = ((DWORD) *plSize < gESPGlobals.dwDebugBufferUsedSize ?
        (DWORD)*plSize : gESPGlobals.dwDebugBufferUsedSize);

    if ((DWORD) (gESPGlobals.pDebugBuffer + gESPGlobals.dwDebugBufferTotalSize
            - gESPGlobals.pDebugBufferOut) > dwTotalSize)
    {
        dwMoveSizeWrapped = 0;
    }
    else
    {
        dwMoveSize = (gESPGlobals.pDebugBuffer +
            gESPGlobals.dwDebugBufferTotalSize - gESPGlobals.pDebugBufferOut);

        dwMoveSizeWrapped = dwTotalSize - dwMoveSize;
    }

    CopyMemory(
        pBuffer,
        gESPGlobals.pDebugBufferOut,
        dwMoveSize
        );

    if (dwMoveSizeWrapped)
    {
        CopyMemory(
            pBuffer + dwMoveSize,
            gESPGlobals.pDebugBuffer,
            dwMoveSizeWrapped
            );

        gESPGlobals.pDebugBufferOut = gESPGlobals.pDebugBuffer +
            dwMoveSizeWrapped;
    }
    else
    {
        gESPGlobals.pDebugBufferOut += dwTotalSize;
    }

    gESPGlobals.dwDebugBufferUsedSize -= dwTotalSize;

    LeaveCriticalSection (&gESPGlobals.DebugBufferCritSec);

    ResetEvent (ghDebugOutputEvent);

    *plSize = (long) dwTotalSize;
}


void
ESPGetWidgetEvents(
    unsigned char  *pBuffer,
    long           *plSize
    )
{
    DWORD   dwTotalSize, dwMoveSize, dwMoveSizeWrapped;


    EnterCriticalSection (&gESPGlobals.EventBufferCritSec);

    dwMoveSize =
    dwTotalSize = ((DWORD) *plSize < gESPGlobals.dwEventBufferUsedSize ?
        (DWORD)*plSize : gESPGlobals.dwEventBufferUsedSize);

    if ((DWORD) (gESPGlobals.pEventBuffer + gESPGlobals.dwEventBufferTotalSize
            - gESPGlobals.pEventBufferOut) > dwTotalSize)
    {
        dwMoveSizeWrapped = 0;
    }
    else
    {
        dwMoveSize = (gESPGlobals.pEventBuffer +
            gESPGlobals.dwEventBufferTotalSize - gESPGlobals.pEventBufferOut);

        dwMoveSizeWrapped = dwTotalSize - dwMoveSize;
    }

    CopyMemory(
        pBuffer,
        gESPGlobals.pEventBufferOut,
        dwMoveSize
        );

    if (dwMoveSizeWrapped)
    {
        CopyMemory(
            pBuffer + dwMoveSize,
            gESPGlobals.pEventBuffer,
            dwMoveSizeWrapped
            );

        gESPGlobals.pEventBufferOut = gESPGlobals.pEventBuffer +
            dwMoveSizeWrapped;
    }
    else
    {
        gESPGlobals.pEventBufferOut += dwTotalSize;
    }

    gESPGlobals.dwEventBufferUsedSize -= dwTotalSize;

    LeaveCriticalSection (&gESPGlobals.EventBufferCritSec);

    ResetEvent (ghWidgetEventsEvent);

    *plSize = (long) dwTotalSize;
}


long
ESPStartPBXThread(
    unsigned char  *pBuffer,
    long            lSize
    )
{
    long   *pPBXSettings;
    DWORD   dwThreadID;

    if ((pPBXSettings = DrvAlloc (lSize)))
    {
        CopyMemory (pPBXSettings, pBuffer, lSize);

        gbExitPBXThread = FALSE;

        if ((ghPBXThread = CreateThread(
                (LPSECURITY_ATTRIBUTES) NULL,
                0,
                (LPTHREAD_START_ROUTINE) PBXThread,
                (LPVOID) pPBXSettings,
                0,
                &dwThreadID
                )))
        {
            return 0;
        }

        DrvFree (pPBXSettings);
    }

    return -1;
}


long
ESPStopPBXThread(
    long    lDummy
    )
{
    gbExitPBXThread = TRUE;

    WaitForSingleObject (ghPBXThread, INFINITE);

    CloseHandle (ghPBXThread);

    ghPBXThread = NULL;

    return 0;
}


void
__RPC_FAR *
__RPC_API
midl_user_allocate(
    size_t len
    )
{


    return (DrvAlloc (len));
}


void
__RPC_API
midl_user_free(
    void __RPC_FAR * ptr
    )
{
    DrvFree (ptr);
}


#if DBG
VOID
DbgPrt(
    IN DWORD  dwDbgLevel,
    IN PUCHAR lpszFormat,
    IN ...
    )
/*++

Routine Description:

    Formats the incoming debug message & calls DbgPrint

Arguments:

    DbgLevel   - level of message verboseness

    DbgMessage - printf-style format string, followed by appropriate
                 list of arguments

Return Value:


--*/
{
    if (dwDbgLevel <= gdwDebugLevel)
    {
        char    buf[128] = "ESP32: ";
        va_list ap;


        va_start(ap, lpszFormat);
        wvsprintf (&buf[7], lpszFormat, ap);
        lstrcatA (buf, "\n");
        OutputDebugStringA (buf);
        va_end(ap);
    }
}
#endif

BOOL
ScanForDWORD(
   char far *pBuf,
   LPDWORD  lpdw
   )
{
    char  c;
    BOOL  bValid = FALSE;
    DWORD d = 0;

    while ((c = *pBuf))
    {
        if ((c >= '0') && (c <= '9'))
        {
            c -= '0';
        }
        else if ((c >= 'a') && (c <= 'f'))
        {
            c -= ('a' - 10);
        }
        else if ((c >= 'A') && (c <= 'F'))
        {
            c -= ('A' - 10);
        }
        else
        {
            break;
        }

        bValid = TRUE;

        d *= 16;

        d += (DWORD) c;

        pBuf++;
    }

    if (bValid)
    {
        *lpdw = d;
    }

    return bValid;
}


BOOL
CALLBACK
ValuesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    DWORD   i;

    static  HWND                hwndCombo, hwndList1, hwndList2;
    static  LONG                lLastSel;
    static  char                szComboText[MAX_STRING_PARAM_SIZE];
    static  PEVENT_PARAM_HEADER pParamsHeader;


    switch (msg)
    {
    case WM_INITDIALOG:
    {
        hwndList1 = GetDlgItem (hwnd, IDC_LIST1);
        hwndList2 = GetDlgItem (hwnd, IDC_LIST2);
        hwndCombo = GetDlgItem (hwnd, IDC_COMBO1);

        lLastSel = -1;
        pParamsHeader = (PEVENT_PARAM_HEADER) lParam;


        //
        // Limit the max text length for the combobox's edit field
        // (NOTE: A combobox ctrl actually has two child windows: a
        // edit ctrl & a listbox.  We need to get the hwnd of the
        // child edit ctrl & send it the LIMITTEXT msg.)
        //

        {
            HWND hwndChild = GetWindow (hwndCombo, GW_CHILD);


            while (hwndChild)
            {
                char buf[8];


                GetClassName (hwndChild, buf, 7);

                if (_stricmp (buf, "edit") == 0)
                {
                    break;
                }

                hwndChild = GetWindow (hwndChild, GW_HWNDNEXT);
            }

            SendMessage(
                hwndChild,
                EM_LIMITTEXT,
                (WPARAM) MAX_STRING_PARAM_SIZE - 1,
                0
                );
        }


        //
        // Misc other init
        //

        SetWindowText (hwnd, pParamsHeader->pszDlgTitle);

        for (i = 0; i < pParamsHeader->dwNumParams; i++)
        {
            SendMessage(
                hwndList1,
                LB_INSERTSTRING,
                (WPARAM) -1,
                (LPARAM) pParamsHeader->aParams[i].szName
                );
        }

        break;
    }
    case WM_COMMAND:
    {
        switch (LOWORD(wParam))
        {
        case IDOK:

            if (lLastSel != -1)
            {
                char buf[MAX_STRING_PARAM_SIZE];


                //
                // Save val of currently selected param
                //

                i = GetDlgItemText(
                    hwnd,
                    IDC_COMBO1,
                    buf,
                    MAX_STRING_PARAM_SIZE - 1
                    );

                switch (pParamsHeader->aParams[lLastSel].dwType)
                {
                case PT_STRING:
                {
                    LONG lComboSel;


                    lComboSel = SendMessage (hwndCombo, CB_GETCURSEL, 0, 0);

                    if (lComboSel == 0) // "NULL string (dwXxxSize = 0)"
                    {
                        pParamsHeader->aParams[lLastSel].dwValue = (DWORD) 0;
                    }
                    else // "Valid string"
                    {
                        strncpy(
                            pParamsHeader->aParams[lLastSel].buf,
                            buf,
                            MAX_STRING_PARAM_SIZE - 1
                            );

                        pParamsHeader->aParams[lLastSel].buf[MAX_STRING_PARAM_SIZE-1] = 0;

                        pParamsHeader->aParams[lLastSel].dwValue = (DWORD)
                            pParamsHeader->aParams[lLastSel].buf;
                    }

                    break;
                }
                case PT_DWORD:
                case PT_FLAGS:
                case PT_ORDINAL:
                {
                    if (!ScanForDWORD(
                            buf,
                            &pParamsHeader->aParams[lLastSel].dwValue
                            ))
                    {
                        //
                        // Default to 0
                        //

                        pParamsHeader->aParams[lLastSel].dwValue = 0;
                    }

                    break;
                }
                } // switch
            }

            // Drop thru to IDCANCEL cleanup code

        case IDCANCEL:

            EndDialog (hwnd, (int)LOWORD(wParam));
            break;

        case IDC_LIST1:

            if (HIWORD(wParam) == LBN_SELCHANGE)
            {
                char    buf[MAX_STRING_PARAM_SIZE] = "";
                LPCSTR  lpstr = buf;
                LONG    lSel = SendMessage (hwndList1, LB_GETCURSEL, 0, 0);


                if (lLastSel != -1)
                {
                    //
                    // Save the old param value
                    //

                    i = GetWindowText(
                        hwndCombo,
                        buf,
                        MAX_STRING_PARAM_SIZE - 1
                        );

                    switch (pParamsHeader->aParams[lLastSel].dwType)
                    {
                    case PT_STRING:
                    {
                        LONG lComboSel;


                        lComboSel = SendMessage (hwndCombo, CB_GETCURSEL, 0,0);

                        if (lComboSel == 0) // "NULL string (dwXxxSize = 0)"
                        {
                            pParamsHeader->aParams[lLastSel].dwValue =
                                (DWORD)0;
                        }
                        else // "Valid string" or no sel
                        {
                            strncpy(
                                pParamsHeader->aParams[lLastSel].buf,
                                buf,
                                MAX_STRING_PARAM_SIZE - 1
                                );

                            pParamsHeader->aParams[lLastSel].buf[MAX_STRING_PARAM_SIZE - 1] = 0;

                            pParamsHeader->aParams[lLastSel].dwValue = (DWORD)
                                pParamsHeader->aParams[lLastSel].buf;
                        }

                        break;
                    }
                    case PT_DWORD:
                    case PT_FLAGS:
                    case PT_ORDINAL:
                    {
                        if (!ScanForDWORD(
                                buf,
                                &pParamsHeader->aParams[lLastSel].dwValue
                                ))
                        {
                            //
                            // Default to 0
                            //

                            pParamsHeader->aParams[lLastSel].dwValue = 0;
                        }

                        break;
                    }
                    } // switch
                }


                SendMessage (hwndList2, LB_RESETCONTENT, 0, 0);
                SendMessage (hwndCombo, CB_RESETCONTENT, 0, 0);

                switch (pParamsHeader->aParams[lSel].dwType)
                {
                case PT_STRING:
                {
                    char * aszOptions[] =
                    {
                        "NUL (dwXxxSize=0)",
                        "Valid string"
                    };


                    for (i = 0; i < 2; i++)
                    {
                        SendMessage(
                            hwndCombo,
                            CB_INSERTSTRING,
                            (WPARAM) -1,
                            (LPARAM) aszOptions[i]
                            );
                    }

                    if (pParamsHeader->aParams[lSel].dwValue == 0)
                    {
                        i = 0;
                        buf[0] = 0;
                    }
                    else
                    {
                        i = 1;
                        lpstr = (LPCSTR) pParamsHeader->aParams[lSel].dwValue;
                    }

                    SendMessage (hwndCombo, CB_SETCURSEL, (WPARAM) i, 0);

                    break;
                }
                case PT_DWORD:
                {
                    SendMessage(
                        hwndCombo,
                        CB_INSERTSTRING,
                        (WPARAM) -1,
                        (LPARAM) (char far *) "0000000"
                        );

                    if (pParamsHeader->aParams[lSel].dwDefValue)
                    {
                        //
                        // Add the default val string to the combo
                        //

                        wsprintf(
                            buf,
                            "%08lx",
                            pParamsHeader->aParams[lSel].dwDefValue
                            );

                        SendMessage(
                            hwndCombo,
                            CB_INSERTSTRING,
                            (WPARAM) -1,
                            (LPARAM) buf
                            );
                    }

                    SendMessage(
                        hwndCombo,
                        CB_INSERTSTRING,
                        (WPARAM) -1,
                        (LPARAM) (char far *) "ffffffff"
                        );

                    wsprintf(
                        buf,
                        "%08lx",
                        pParamsHeader->aParams[lSel].dwValue
                        );

                    break;
                }
                case PT_ORDINAL:
                {
                    //
                    // Stick the bit flag strings in the list box
                    //

                    PLOOKUP pLookup = (PLOOKUP)
                        pParamsHeader->aParams[lSel].pLookup;

                    for (i = 0; pLookup[i].dwVal != 0xffffffff; i++)
                    {
                        SendMessage(
                            hwndList2,
                            LB_INSERTSTRING,
                            (WPARAM) -1,
                            (LPARAM) pLookup[i].lpszVal
                            );

                        if (pParamsHeader->aParams[lSel].dwValue ==
                            pLookup[i].dwVal)
                        {
                            SendMessage(
                                hwndList2,
                                LB_SETSEL,
                                (WPARAM) TRUE,
                                (LPARAM) MAKELPARAM((WORD)i,0)
                                );
                        }
                    }

                    SendMessage(
                        hwndCombo,
                        CB_INSERTSTRING,
                        (WPARAM) -1,
                        (LPARAM) (char far *) "select none"
                        );

                    wsprintf(
                        buf,
                        "%08lx",
                        pParamsHeader->aParams[lSel].dwValue
                        );

                    break;
                }
                case PT_FLAGS:
                {
                    //
                    // Stick the bit flag strings in the list box
                    //

                    HWND hwndList2 = GetDlgItem (hwnd, IDC_LIST2);
                    PLOOKUP pLookup = (PLOOKUP)
                        pParamsHeader->aParams[lSel].pLookup;

                    for (i = 0; pLookup[i].dwVal != 0xffffffff; i++)
                    {
                        SendMessage(
                            hwndList2,
                            LB_INSERTSTRING,
                            (WPARAM) -1,
                            (LPARAM) pLookup[i].lpszVal
                            );

                        if (pParamsHeader->aParams[lSel].dwValue &
                            pLookup[i].dwVal)
                        {
                            SendMessage(
                                hwndList2,
                                LB_SETSEL,
                                (WPARAM) TRUE,
                                (LPARAM) MAKELPARAM((WORD)i,0)
                                );
                        }
                    }

                    SendMessage(
                        hwndCombo,
                        CB_INSERTSTRING,
                        (WPARAM) -1,
                        (LPARAM) (char far *) "select none"
                        );

                    SendMessage(
                        hwndCombo,
                        CB_INSERTSTRING,
                        (WPARAM) -1,
                        (LPARAM) (char far *) "select all"
                        );

                    wsprintf(
                        buf,
                        "%08lx",
                        pParamsHeader->aParams[lSel].dwValue
                        );

                    break;
                }
                } //switch

                SetWindowText (hwndCombo, lpstr);

                lLastSel = lSel;
            }
            break;

        case IDC_LIST2:

            if (HIWORD(wParam) == LBN_SELCHANGE)
            {
                //
                // BUGBUG in the PT_ORDINAL case we should compare the
                // currently selected item(s) against the previous DWORD
                // val and figure out which item we need to deselect,
                // if any, in order to maintain a mutex of values
                //

                PLOOKUP pLookup = (PLOOKUP)
                    pParamsHeader->aParams[lLastSel].pLookup;
                char buf[16];
                DWORD dwValue = 0;
                int far *ai;
                LONG i, lSelCount =
                    SendMessage (hwndList2, LB_GETSELCOUNT, 0, 0);


                ai = (int far *) DrvAlloc ((size_t)lSelCount * sizeof(int));

                SendMessage(
                    hwndList2,
                    LB_GETSELITEMS,
                    (WPARAM) lSelCount,
                    (LPARAM) ai
                    );

                if (pParamsHeader->aParams[lLastSel].dwType == PT_FLAGS)
                {
                    for (i = 0; i < lSelCount; i++)
                    {
                        dwValue |= pLookup[ai[i]].dwVal;
                    }
                }
                else // if (.dwType == PT_ORDINAL)
                {
                    if (lSelCount == 1)
                    {
                        dwValue = pLookup[ai[0]].dwVal;
                    }
                    else if (lSelCount == 2)
                    {
                        //
                        // Figure out which item we need to de-select, since
                        // we're doing ordinals & only want 1 item selected
                        // at a time
                        //

                        GetWindowText (hwndCombo, buf, 16);

                        if (ScanForDWORD (buf, &dwValue))
                        {
                            if (pLookup[ai[0]].dwVal == dwValue)
                            {
                                SendMessage(
                                    hwndList2,
                                    LB_SETSEL,
                                    0,
                                    (LPARAM) ai[0]
                                    );

                                dwValue = pLookup[ai[1]].dwVal;
                            }
                            else
                            {
                                SendMessage(
                                    hwndList2,
                                    LB_SETSEL,
                                    0,
                                    (LPARAM) ai[1]
                                    );

                                dwValue = pLookup[ai[0]].dwVal;
                            }
                        }
                        else
                        {
                            // BUGBUG de-select items???

                            dwValue = 0;
                        }
                    }
                    else if (lSelCount > 2)
                    {
                        //
                        // Determine previous selection & de-select all the
                        // latest selections
                        //

                        GetDlgItemText (hwnd, IDC_COMBO1, buf, 16);

                        if (ScanForDWORD (buf, &dwValue))
                        {
                            for (i = 0; i < lSelCount; i++)
                            {
                                if (pLookup[ai[i]].dwVal != dwValue)
                                {
                                    SendMessage(
                                        hwndList2,
                                        LB_SETSEL,
                                        0,
                                        (LPARAM) ai[i]
                                        );
                                }
                            }
                        }
                        else
                        {
                            // BUGBUG de-select items???

                            dwValue = 0;
                        }
                    }
                }

                DrvFree (ai);
                wsprintf (buf, "%08lx", dwValue);
                SetWindowText (hwndCombo, buf);
            }
            break;

        case IDC_COMBO1:

            switch (HIWORD(wParam))
            {
            case CBN_SELCHANGE:
            {
                LONG    lSel =  SendMessage (hwndCombo, CB_GETCURSEL, 0, 0);


                switch (pParamsHeader->aParams[lLastSel].dwType)
                {
                case PT_ORDINAL:

                    //
                    // The only option here is "select none"
                    //

                    strcpy (szComboText, "00000000");
                    PostMessage (hwnd, WM_USER+55, 0, 0);
                    break;

                case PT_FLAGS:
                {
                    BOOL bSelect = (lSel ? TRUE : FALSE);

                    SendMessage(
                        hwndList2,
                        LB_SETSEL,
                        (WPARAM) bSelect,
                        (LPARAM) -1
                        );

                    if (bSelect)
                    {
                        PLOOKUP pLookup = (PLOOKUP)
                            pParamsHeader->aParams[lLastSel].pLookup;
                        DWORD dwValue = 0;
                        int far *ai;
                        LONG i, lSelCount =
                            SendMessage (hwndList2, LB_GETSELCOUNT, 0, 0);


                        ai = (int far *) DrvAlloc(
                            (size_t)lSelCount * sizeof(int)
                            );

                        SendMessage(
                            hwndList2,
                            LB_GETSELITEMS,
                            (WPARAM) lSelCount,
                            (LPARAM) ai
                            );

                        for (i = 0; i < lSelCount; i++)
                        {
                            dwValue |= pLookup[ai[i]].dwVal;
                        }

                        DrvFree (ai);
                        wsprintf (szComboText, "%08lx", dwValue);

                    }
                    else
                    {
                        strcpy (szComboText, "00000000");
                    }

                    PostMessage (hwnd, WM_USER+55, 0, 0);

                    break;
                }
                case PT_STRING:

                    if (lSel == 1)
                    {
                        strncpy(
                            szComboText,
                            pParamsHeader->aParams[lLastSel].buf,
                            MAX_STRING_PARAM_SIZE
                            );

                        szComboText[MAX_STRING_PARAM_SIZE-1] = 0;
                    }
                    else
                    {
                        szComboText[0] = 0;
                    }

                    PostMessage (hwnd, WM_USER+55, 0, 0);

                    break;

                case PT_DWORD:

                    break;

                } // switch
                break;
            }
            case CBN_EDITCHANGE:
            {
                //
                // If user entered text in the edit field then copy the
                // text to our buffer
                //

                if (pParamsHeader->aParams[lLastSel].dwType == PT_STRING)
                {
                    char buf[MAX_STRING_PARAM_SIZE];


                    GetWindowText (hwndCombo, buf, MAX_STRING_PARAM_SIZE);

                    strncpy(
                        pParamsHeader->aParams[lLastSel].buf,
                        buf,
                        MAX_STRING_PARAM_SIZE
                        );

                    pParamsHeader->aParams[lLastSel].buf
                        [MAX_STRING_PARAM_SIZE-1] = 0;
                }
                break;
            }
            } // switch

        } // switch

        break;
    }
    case WM_USER+55:

        SetWindowText (hwndCombo, szComboText);
        break;

    case WM_CTLCOLORSTATIC:

        SetBkColor ((HDC) wParam, RGB (192,192,192));
        return (BOOL) GetStockObject (LTGRAY_BRUSH);

    case WM_PAINT:
    {
        PAINTSTRUCT ps;

        BeginPaint (hwnd, &ps);
        FillRect (ps.hdc, &ps.rcPaint, GetStockObject (LTGRAY_BRUSH));
        EndPaint (hwnd, &ps);

        break;
    }
    }

    return FALSE;
}


BOOL
PASCAL
IsValidESPAddress(
    LPCWSTR     lpszDestAddress,
    PDRVLINE   *ppLine,
    LPDWORD     pdwAddressID
    )
{
    char   *pszDestAddress, *p, c;
    BOOL    bResult = FALSE;
    DWORD   length, dwDestLineID, dwAddressID;


    if (!lpszDestAddress)
    {
        return FALSE;
    }


    //
    // Convert destination address from unicode to ascii
    //

    length = (lstrlenW (lpszDestAddress) + 1) * sizeof (WCHAR);

    if (!(pszDestAddress = DrvAlloc (length)))
    {
        return FALSE;
    }

    WideCharToMultiByte(
        CP_ACP,
        0,
        lpszDestAddress,
        -1,
        pszDestAddress,
        length,
        NULL,
        NULL
        );

    p = pszDestAddress;


    //
    // See if destination address is in the format of either
    // "<esp line id>" or "<esp line id>#<esp address id>"
    //

    if (*p < '0'  ||  *p > '9')
    {
        goto ISESPAddress_freeAddr;
    }

    for (dwDestLineID = 0; (c = *p); p++)
    {
        if (c >= '0' && c <= '9')
        {
            dwDestLineID *= 10;
            dwDestLineID += ((DWORD)(c - '0'));
        }
        else
        {
            break;
        }
    }

    if (c != '\0'  &&  c != '#')
    {
        goto ISESPAddress_freeAddr;
    }

    if (dwDestLineID < gESPGlobals.dwLineDeviceIDBase ||
        dwDestLineID >= (gESPGlobals.dwNumLines +
            gESPGlobals.dwLineDeviceIDBase))
    {
        goto ISESPAddress_freeAddr;
    }

    if (c == '\0')
    {
        *pdwAddressID = 0;
        goto ISESPAddress_success;
    }

    p++;

    if (*p < '0'  ||  *p > '9')
    {
        goto ISESPAddress_freeAddr;
    }

    for (dwAddressID = 0; (c = *p); p++)
    {
        if (c >= '0' && c <= '9')
        {
            dwAddressID *= 10;
            dwAddressID += ((DWORD)(c - '0'));
        }
        else
        {
            break;
        }
    }

    if (c != '\0'  ||  dwAddressID >= gESPGlobals.dwNumAddressesPerLine)
    {
        goto ISESPAddress_freeAddr;
    }

    *pdwAddressID = dwAddressID;

ISESPAddress_success:

    *ppLine = GetLineFromID (dwDestLineID);
    bResult = TRUE;

ISESPAddress_freeAddr:

    DrvFree (pszDestAddress);
    return bResult;
}


LONG
PASCAL
CreateIncomingCall(
    LPCWSTR             lpszDestAddress,
    LPLINECALLPARAMS    lpCallParams,
    PDRVCALL            pOutgoingCall,
    BOOL               *pbValidESPAddress,
    PDRVLINE           *ppIncomingLine,
    PDRVCALL           *ppIncomingCall
    )
{
    LONG                lResult;
    DWORD               dwIncomingAddressID;
    PDRVCALL            pIncomingCall;
    PDRVLINE            pIncomingLine;
    LINECALLPARAMS      callParams;


    *pbValidESPAddress = FALSE;
    *ppIncomingLine = NULL;
    *ppIncomingCall = NULL;

    if (!IsValidESPAddress(
            lpszDestAddress,
            &pIncomingLine,
            &dwIncomingAddressID
            ))
    {
        return LINEERR_INVALADDRESS;
    }

    *pbValidESPAddress = TRUE;

    if (pIncomingLine->htLine == NULL ||
        (pOutgoingCall &&
        !(pIncomingLine->dwMediaModes & pOutgoingCall->dwMediaMode)))
    {
        return LINEERR_INVALMEDIAMODE;
    }

    if (!lpCallParams)
    {
        lpCallParams = &callParams;

        ZeroMemory (&callParams, sizeof (LINECALLPARAMS));

        callParams.dwTotalSize = sizeof (LINECALLPARAMS);
        callParams.dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
        callParams.dwBearerMode = LINEBEARERMODE_VOICE;
    }

    lpCallParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
    lpCallParams->dwAddressID = dwIncomingAddressID;

    if ((lResult = AllocCall(
            pIncomingLine,
            0,
            lpCallParams,
            &pIncomingCall

            )) == 0)
    {
        if (pOutgoingCall)
        {
            pOutgoingCall->pDestCall = pIncomingCall;
            pIncomingCall->pDestCall = pOutgoingCall;
        }

        *ppIncomingLine = pIncomingLine;
        *ppIncomingCall = pIncomingCall;
    }
    else
    {
        ShowStr(
             TRUE,
             "lineMakeCall couldn't create incoming call on" \
                 "line/addr id %d/%d, exceeded max calls per line/addr",
             pIncomingLine->dwDeviceID,
             dwIncomingAddressID
             );
    }

    return lResult;
}


void
FAR
PASCAL
TransferCall_postProcess(
    PASYNC_REQUEST_INFO pAsyncReqInfo,
    BOOL                bAsync
    )
{
    DWORD       dwCallInstThen = pAsyncReqInfo->dwParam2,
                dwValidCurrentCallStates = pAsyncReqInfo->dwParam5,
                dwNewCallState = pAsyncReqInfo->dwParam6,
                dwCallInstNow;
    PDRVCALL    pCall = (PDRVCALL) pAsyncReqInfo->dwParam1,
                pDestCall = (PDRVCALL) pAsyncReqInfo->dwParam4;
    PDRVLINE    pDestLine = (PDRVLINE) pAsyncReqInfo->dwParam3;


    DoCompletion (pAsyncReqInfo, bAsync);

    if (pAsyncReqInfo->lResult == 0)
    {
        if ((pAsyncReqInfo->lResult = SetCallState(
                pCall,
                dwCallInstThen,
                dwValidCurrentCallStates,
                LINECALLSTATE_IDLE,
                0,
                TRUE

                )) != 0)
        {
            goto TSPI_lineBlindTransfer_postProcess_freeDestCall;
        }

        if (pDestCall)
        {
            EnterCriticalSection (&gESPGlobals.CallListCritSec);

            if (IsValidDrvCall (pCall, &dwCallInstNow) &&
                dwCallInstNow == dwCallInstThen)
            {
                SendLineEvent(
                    pDestLine,
                    NULL,
                    LINE_NEWCALL,
                    (DWORD) pDestCall,
                    (DWORD) &pDestCall->htCall,
                    0
                    );

                if (pDestCall->htCall)
                {
                    SetCallState(
                        pDestCall,
                        pDestCall->dwCallInstance,
                        0xffffffff,
                        dwNewCallState,
                        0,
                        TRUE
                        );
                }
                else
                {
                    FreeCall (pDestCall, pDestCall->dwCallInstance);
                }
            }
            else
            {
                FreeCall (pDestCall, pDestCall->dwCallInstance);
            }

            LeaveCriticalSection (&gESPGlobals.CallListCritSec);
        }

    }
    else
    {

TSPI_lineBlindTransfer_postProcess_freeDestCall:

        if (pDestCall)
        {
            FreeCall (pDestCall, pDestCall->dwCallInstance);
        }
    }
}




LONG
PASCAL
TransferCall(
    PFUNC_INFO  pInfo,
    PDRVCALL    pCall,
    DWORD       dwValidCurrentCallStates,
    DWORD       dwNewCallState, // initial call state of new incoming call
    LPCWSTR     lpszDestAddress
    )
{
    BOOL        bValidESPAddress;
    LONG        lResult = 0;
    PDRVLINE    pDestLine;
    PDRVCALL    pDestCall;


    EnterCriticalSection (&gESPGlobals.CallListCritSec);

    if (IsValidDrvCall (pCall, NULL) == FALSE)
    {
        lResult = LINEERR_INVALCALLHANDLE;
    }
    else if ((pCall->dwCallState & dwValidCurrentCallStates) == 0)
    {
        lResult = LINEERR_INVALCALLSTATE;
    }
    else
    {
        PDRVCALL        pDestCallOrig = pCall->pDestCall;
        LINECALLPARAMS  callParams;


        if (IsValidDrvCall (pDestCallOrig, NULL) == FALSE)
        {
            pDestCallOrig = NULL;
        }

        ZeroMemory (&callParams, sizeof (LINECALLPARAMS));

        callParams.dwTotalSize  = sizeof (LINECALLPARAMS);
        callParams.dwMediaMode  = pCall->dwMediaMode;
        callParams.dwBearerMode = pCall->dwBearerMode;
        callParams.dwMinRate    = pCall->dwMinRate;
        callParams.dwMaxRate    = pCall->dwMaxRate;

        if (CreateIncomingCall(
                lpszDestAddress,
                &callParams,
                pDestCallOrig,
                &bValidESPAddress,
                &pDestLine,
                &pDestCall

                ) == 0)
        {
            pCall->pDestCall = NULL;
            pDestCall->dwCallID = pCall->dwCallID;

            if (pCall->dwCallDataSize  &&
                (pDestCall->pCallData = DrvAlloc (pCall->dwCallDataSize)))
            {
                CopyMemory(
                    pDestCall->pCallData,
                    pCall->pCallData,
                    (pDestCall->dwCallDataSize = pCall->dwCallDataSize)
                    );
            }
        }

        pInfo->pAsyncReqInfo->pfnPostProcessProc = (FARPROC)
            TransferCall_postProcess;

        pInfo->pAsyncReqInfo->dwParam1 = (DWORD) pCall;
        pInfo->pAsyncReqInfo->dwParam2 = (DWORD) pCall->dwCallInstance;
        pInfo->pAsyncReqInfo->dwParam3 = (DWORD) pDestLine;
        pInfo->pAsyncReqInfo->dwParam4 = (DWORD) pDestCall;
        pInfo->pAsyncReqInfo->dwParam5 = (DWORD) dwValidCurrentCallStates;
        pInfo->pAsyncReqInfo->dwParam6 = (DWORD) dwNewCallState;
    }

    LeaveCriticalSection (&gESPGlobals.CallListCritSec);

    return lResult;
}