/*++ BUILD Version: 0000    // Increment this if a change has global effects

Copyright (c) 1995-1998  Microsoft Corporation

Module Name:

    line.c

Abstract:

    Src module for tapi server line funcs

Author:

    Dan Knudson (DanKn)    01-Apr-1995

Revision History:

--*/


#include "windows.h"
#include "assert.h"
#include "prsht.h"
#include "stdlib.h"
#include "tapip.h" // private\inc\tapip.h
#include "tspi.h"
#include "utils.h"
#include "client.h"
#include "loc_comn.h"
#include "server.h"
#include "line.h"
#include "resource.h"
#include "tapihndl.h"
#include "tregupr2.h"
#include <tchar.h>
#include "private.h"
#include <MMSYSTEM.H>
#include <mmddk.h>

// PERF
#include "tapiperf.h"

#define ARRAYSIZE(x)    (sizeof(x)/sizeof(x[0]))

// PERF
extern PERFBLOCK           PerfBlock;

LONG
RtlOpenCurrentUser(
    IN ULONG DesiredAccess,
    OUT PHANDLE CurrentUserKey
    );

//NTSYSAPI
//NTSTATUS
//NTAPI
LONG
NtClose(
    IN HANDLE Handle
    );



LPLINECOUNTRYLIST   gpCountryList = NULL;
LPDWORD             gpCountryGroups = NULL;

extern TAPIGLOBALS TapiGlobals;
extern CRITICAL_SECTION gSafeMutexCritSec,
                        gPriorityListCritSec;


extern HANDLE ghHandleTable;

extern TCHAR gszProviderID[];
extern TCHAR gszNumProviders[];
extern TCHAR gszNextProviderID[];
extern TCHAR gszProviderFilename[];
extern TCHAR gszRegKeyTelephony[];

const TCHAR gszLocation[]  = TEXT("Location");
const TCHAR gszLocations[] = TEXT("Locations");
const TCHAR gszAreaCodeRules[]  = TEXT("AreaCodeRules");

extern TCHAR gszRegKeyProviders[];

extern PTPROVIDER pRemoteSP;

extern DWORD gdwNumSPEventHandlerThreads;

extern HINSTANCE ghInstance;

extern BOOL gbQueueSPEvents;

const TCHAR gszID[]                 = TEXT("ID");
const TCHAR gszCountry[]            = TEXT("Country");
const TCHAR gszFlags[]              = TEXT("Flags");
const TCHAR gszNoPrefixAreaCodes[]  = TEXT("NoPrefAC");

const TCHAR gszNumEntries[]         = TEXT("NumEntries");
const TCHAR gszCurrentID[]          = TEXT("CurrentID");
const TCHAR gszNextID[]             = TEXT("NextID");
const TCHAR gszLocationListVersion[]= TEXT("LocationListVersion");

const TCHAR gszCallingCard[]        = TEXT("CallingCard");

const TCHAR gszSameAreaRuleW[]       = TEXT("SameAreaRule");
const TCHAR gszLongDistanceRuleW[]   = TEXT("LongDistanceRule");
const TCHAR gszInternationalRuleW[]  = TEXT("InternationalRule");
const TCHAR gszCountryGroupW[]       = TEXT("CountryGroup");
const TCHAR gszNameW[]               = TEXT("Name");
const TCHAR gszNameResW[]            = TEXT("NameResourceId");
const TCHAR gszAreaCodeW[]           = TEXT("AreaCode");
const TCHAR gszLongDistanceCarrierCodeW[] = TEXT("LongDistanceCarrierCode");
const TCHAR gszInternationalCarrierCodeW[] = TEXT("InternationalCarrierCode");
const TCHAR gszOutsideAccessW[]      = TEXT("OutsideAccess");
const TCHAR gszLongDistanceAccessW[] = TEXT("LongDistanceAccess");
const TCHAR gszDisableCallWaitingW[] = TEXT("DisableCallWaiting");
const TCHAR gszTollListW[]           = TEXT("TollList");
const TCHAR gszCountryListVersionW[] = TEXT("CountryListVersion");

const TCHAR gszAreaCodeToCallW[]     = TEXT("AreaCodeToCall");
const TCHAR gszNumberToDialW[]       = TEXT("NumberToDial"); 
const TCHAR gszPrefixesW[]           = TEXT("Prefixes"); 

//
// IMPORTANT NOTE: this value should be incremented any time there is a
//                 change to country.rc
//

#define TAPI_CURRENT_COUNTRY_LIST_VERSION  0x00000110

#define IS_LRESULT_NOTERROR( foo )                            \
            ( ! ( 0x80000000 & foo )  &&                                        \
            (  (foo <= LINEERR_LASTERRORVALUE)  ||                                     \
                (foo > 0x90000000 && foo <= PHONEERR_LASTERRORVALUE) ) )

extern UINT             guiAlignmentFaultEnabled;
extern BOOL             gbWinNT;
extern BOOL             gbNTServer;
extern BOOL             gbServerInited;
extern HANDLE           ghTapisrvHeap;



//
// The following are used for the call hub implementation.
//
// One call hub hash table is used for each service provider.
// When a outgoing call or is successfully made or an incoming
// call shows up, TAPI will retrieve the call ID for that
// call and stick it in the hash table using the algorithim :
//
//   hashEntryIndex = callID % numHashTableEntries
//
// In the case of collision (hashEntry already in use by a
// different callID) then the DoCallHubHashing() function will
// try to create a dynamic entry to hang off the "static" entry.
//
// We will allow a total number (per table) of dynamic entries
// specified by the GetMaxDynamicHashTableEntries() macro below.
//
// We will allow a total number (per table entry) of dynamic
// entries specified by the symbolic MAX_DYNAMIC_HASH_ENTRIES_PER_SLOT
// constant below.
//
// TapiPrimes is an array of increasing prime numbers used
// for hash table sizes. We will not grow a hash table to more
// the 261983 (static) entries, this being a seemingly
// reasonable limitation at this point.  If we try to hash
// a call and are unable to (out of memory, max table size, etc)
// then we will reset our internal copy of the call ID for
// the call to 0, meaning it's not hashed at all.
//

const DWORD TapiPrimes[] =
{
    31, 61, 127, 257, 521, 1031, 2053, 4099, 8191,
    16381, 32749, 65537, 131071, 261983, 0
};

#define GetMaxDynamicHashTableEntries(TotalNumEntries) \
            (TotalNumEntries/10)

#define MAX_DYNAMIC_HASH_ENTRIES_PER_SLOT 3


#if DBG
extern DWORD   gdwDebugLevel;
extern BOOL    gfBreakOnSeriousProblems;

char *
PASCAL
MapResultCodeToText(
    LONG    lResult,
    char   *pszResult
    );
#endif

void
PASCAL
DestroytCall(
    PTCALL  ptCall
    );

void
PASCAL
DestroytCallClient(
    PTCALLCLIENT    ptCallClient
    );

void
PASCAL
DestroytLineClient(
    HLINE   hLine
    );

void
LDevSpecific_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    );

void
LMakeCall_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    );

void
CALLBACK
CompletionProcSP(
    DWORD   dwRequestID,
    LONG    lResult
    );

LONG
PASCAL
GetPhoneAppListFromClient(
    PTCLIENT        ptClient,
    PTPOINTERLIST  *ppList
    );

LONG
InitializeClient(
    PTCLIENT    ptClient
    );

BOOL
GetLinePermanentIdFromDeviceID(
    PTCLIENT            ptClient,
    DWORD               dwDeviceID,
    LPTAPIPERMANENTID   pID
    );

void
CALLBACK
LineEventProcSP(
    HTAPILINE   htLine,
    HTAPICALL   htCall,
    DWORD       dwMsg,
    ULONG_PTR   dwParam1,
    ULONG_PTR   dwParam2,
    ULONG_PTR   dwParam3
    );

PTPHONELOOKUPENTRY
GetPhoneLookupEntry(
    DWORD   dwDeviceID
    );

LONG
GetPermLineIDAndInsertInTable(
    PTPROVIDER  ptProvider,
    DWORD       dwDeviceID,
    DWORD       dwSPIVersion
    );

LONG
AppendNewDeviceInfo (
    BOOL                        bLine,
    DWORD                       dwDeviceID
    );

LONG
RemoveDeviceInfoEntry (
    BOOL                        bLine,
    DWORD                       dwDeviceID
    );

BOOL
IsAPIVersionInRange(
    DWORD   dwAPIVersion,
    DWORD   dwSPIVersion
    )
{
    if (dwAPIVersion <= dwSPIVersion)
    {
        switch (dwAPIVersion)
        {
        case TAPI_VERSION_CURRENT:
        case TAPI_VERSION3_0:
        case TAPI_VERSION2_2:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_0:
        case TAPI_VERSION1_4:
        case TAPI_VERSION1_0:

            return TRUE;

        default:

            break;
        }
    }

    return FALSE;
}


LONG
GetLineVersions(
    PTLINECLIENT    ptLineClient,
    LPDWORD         lpdwAPIVersion,
    LPDWORD         lpdwSPIVersion
    )
{
    *lpdwAPIVersion = ptLineClient->dwAPIVersion;
    *lpdwSPIVersion = ptLineClient->ptLine->dwSPIVersion;

    if (ptLineClient->dwKey != TLINECLIENT_KEY)
    {
        return LINEERR_INVALLINEHANDLE;
    }

    return 0;
}


BOOL
InitTapiStruct(
    LPVOID  pTapiStruct,
    DWORD   dwTotalSize,
    DWORD   dwFixedSize,
    BOOL    bZeroInit
    )
{
    //
    // Verify there's space enough for fixed data
    //

    if (dwTotalSize < dwFixedSize)
    {
        return FALSE;
    }


    //
    // Init the dwTotalSize as specified, then init the dwUsedSize and
    // dwNeededSize fields as the fixed size of the structure (saves the
    // SP some work if it's not planning on adding any of it's own
    // varible-length data to the structure)
    //

    *((LPDWORD) pTapiStruct)       = dwTotalSize;

    *(((LPDWORD) pTapiStruct) + 1) =
    *(((LPDWORD) pTapiStruct) + 2) = dwFixedSize;


    //
    // Now zero out the rest of the buffer if the caller wants us to
    //

    if (bZeroInit)
    {
        ZeroMemory(
            ((LPDWORD) pTapiStruct) + 3,
            dwTotalSize - 3 * sizeof (DWORD)
            );
    }

    return TRUE;
}


#if DBG
BOOL
IsBadSizeOffset(
    DWORD dwTotalSize,
    DWORD dwFixedSize,
    DWORD dwXxxSize,
    DWORD dwXxxOffset,
    DWORD dwAlignMask,
    char *pszCallingFunc,
    char *pszFieldName
    )

#else
BOOL
IsBadSizeOffset(
    DWORD dwTotalSize,
    DWORD dwFixedSize,
    DWORD dwXxxSize,
    DWORD dwXxxOffset,
    DWORD dwAlignMask
    )

#endif
{
    if (dwXxxSize != 0)
    {
        DWORD   dwSum = dwXxxSize + dwXxxOffset;

        if ((dwAlignMask == 2) || (dwAlignMask == 4) || (dwAlignMask == 8))
        {
            dwAlignMask--;
            if (dwAlignMask & dwXxxOffset)
            {
                return TRUE;
            }
        }

        if (dwXxxOffset < dwFixedSize)
        {
#if DBG
            LOG((TL_INFO,
                "%s: dw%sOffset (=x%x) points at fixed portion (=x%x)" \
                    " of structure",
                pszCallingFunc,
                pszFieldName,
                dwXxxSize,
                dwFixedSize
                ));
#else
            LOG((TL_INFO,
                "Offset (=x%x) points at fixed portion (=x%x)" \
                    " of structure",
                dwXxxSize,
                dwFixedSize
                ));
#endif
            return TRUE;
        }
        else if ((dwSum > dwTotalSize) || (dwSum < dwXxxSize))
        {
#if DBG
            LOG((TL_INFO,
                "%s: sum of dw%sSize/Offset (=x%x/x%x) > dwTotalSize (=x%x)",
                pszCallingFunc,
                pszFieldName,
                dwXxxSize,
                dwXxxOffset,
                dwFixedSize,
                dwTotalSize
                ));
#endif
            return TRUE;
        }
    }

    return FALSE;
}


BOOL
IsBadStringParam(
    DWORD   dwParamsBufferSize,
    LPBYTE  pDataBuf,
    DWORD   dwStringOffset
    )
{
    WCHAR  *p;
    DWORD   dwCharsLeft;


    //
    // Check if offset is not WCHAR-aligned or if offset lies outside buffer
    //

    if ((dwStringOffset & 1)  ||  (dwStringOffset >= dwParamsBufferSize))
    {
        return TRUE;
    }


    //
    // Walk the string & make sure it's NULL-terminated within the buffer
    //

    dwCharsLeft = (dwParamsBufferSize - dwStringOffset) / sizeof (WCHAR);

    for(
        p = (WCHAR *) (pDataBuf + dwStringOffset);
        dwCharsLeft  &&  *p;
        dwCharsLeft--, p++
        );

    return (dwCharsLeft ? FALSE : TRUE);
}


BOOL
IsBadStructParam(
    DWORD   dwParamsBufferSize,
    LPBYTE  pDataBuf,
    DWORD   dwXxxOffset
    )
{
    DWORD   dwTotalSize;


    if ((dwXxxOffset & 0x3) ||
        (dwParamsBufferSize < sizeof (DWORD)) ||
        (dwXxxOffset >= (dwParamsBufferSize - sizeof (DWORD))))
    {
        return TRUE;
    }

    dwTotalSize = *((LPDWORD) (pDataBuf + dwXxxOffset));

    if (dwTotalSize > (dwParamsBufferSize - dwXxxOffset))
    {
        return TRUE;
    }

    return FALSE;
}


LONG
PASCAL
ValidateCallParams(
    LPLINECALLPARAMS    pCallParamsApp,
    LPLINECALLPARAMS   *ppCallParamsSP,
    DWORD               dwAPIVersion,
    DWORD               dwSPIVersion,
    DWORD               dwAsciiCallParamsCodePage
    )
{
    //
    // This routine checks the fields in a LINECALLPARAMS struct,
    // looking for invalid bit flags and making sure that the
    // various size/offset pairs only reference data within the
    // variable-data portion of the structure. Also, if the
    // specified SPI version is greater than the API version and
    // the fixed structure size differs between the two versions,
    // a larger buffer is allocated, the var data is relocated,
    // and the sizeof/offset pairs are patched.
    //

    char    szFunc[] = "ValidateCallParams";
    DWORD   dwTotalSize = pCallParamsApp->dwTotalSize, dwFixedSizeApp,
            dwFixedSizeSP, dwAllBearerModes, dwAllMediaModes,
            dwAllCallParamFlags;


    switch (dwAPIVersion)
    {
    case TAPI_VERSION1_0:

        dwFixedSizeApp   = 112; // 24 * sizeof (DWORD) + sizeof(LINEDIALPARAMS)
        dwAllMediaModes  = AllMediaModes1_0;

        //
        // HotFax v1.0 may be hot, but it's not real smart.  It's
        // negotating an API ver == 0x10003, and subsequently tries
        // to sneak in a v1.4 bearer mode (PASSTHROUGH).  We'll
        // allow for it this time...
        //
        //dwAllBearerModes = AllBearerModes1_0;

        dwAllBearerModes = AllBearerModes1_4; // HACK - see above

        dwAllCallParamFlags = AllCallParamFlags1_0;

        break;

    case TAPI_VERSION1_4:

        dwFixedSizeApp   = 112; // 24 * sizeof (DWORD) + sizeof(LINEDIALPARAMS)
        dwAllMediaModes  = AllMediaModes1_4;
        dwAllBearerModes = AllBearerModes1_4;
        dwAllCallParamFlags = AllCallParamFlags1_0;
        break;

    case TAPI_VERSION2_0:
    case TAPI_VERSION2_1:
    case TAPI_VERSION2_2:

        dwFixedSizeApp   = 176; // 40 * sizeof (DWORD) + sizeof(LINEDIALPARAMS)
        dwAllMediaModes  = AllMediaModes2_1;
        dwAllBearerModes = AllBearerModes2_0;
        dwAllCallParamFlags = AllCallParamFlags2_0;
        break;

    case TAPI_VERSION3_0:
    case TAPI_VERSION_CURRENT:

        dwFixedSizeApp   = sizeof (LINECALLPARAMS);
        dwAllMediaModes  = AllMediaModes2_1;
        dwAllBearerModes = AllBearerModes2_0;
        dwAllCallParamFlags = AllCallParamFlags2_0;
        break;

    default:

        return LINEERR_OPERATIONFAILED;
    }


    switch (dwSPIVersion)
    {
    case TAPI_VERSION1_0:
    case TAPI_VERSION1_4:

        dwFixedSizeSP = 112;    // 24 * sizeof (DWORD) + sizeof(LINEDIALPARAMS)
        break;

    case TAPI_VERSION2_0:
    case TAPI_VERSION2_1:
    case TAPI_VERSION2_2:

        dwFixedSizeSP = 176;    // 40 * sizeof (DWORD) + sizeof(LINEDIALPARAMS)
        break;

    case TAPI_VERSION3_0:
    case TAPI_VERSION_CURRENT:

        dwFixedSizeSP = sizeof (LINECALLPARAMS);
        break;

    default:

        return LINEERR_OPERATIONFAILED;
    }


    if (dwTotalSize < dwFixedSizeApp)
    {
        LOG((TL_ERROR,
            "%sbad dwTotalSize, x%x (minimum valid size=x%x)",
            szFunc,
            dwTotalSize,
            dwFixedSizeApp
            ));
        return LINEERR_STRUCTURETOOSMALL;
    }

    if (pCallParamsApp->dwBearerMode)
    {
        //
        // Allow 1.x apps to pass >=1 valid bearer modes to 1.x service
        // providers for backwards compatiblity, but enforce a single
        // valid bearer mode for newer apps and/or service providers
        //

        if ((dwAPIVersion >= TAPI_VERSION2_0) ||
            (dwSPIVersion >= TAPI_VERSION2_0))
        {
            if (!IsOnlyOneBitSetInDWORD (pCallParamsApp->dwBearerMode) ||
                (pCallParamsApp->dwBearerMode & ~dwAllBearerModes))
            {
bad_bearer_mode:

                LOG((TL_ERROR,
                    "%sbad dwBearerMode, x%x",
                    szFunc,
                    pCallParamsApp->dwBearerMode
                    ));

                return LINEERR_INVALBEARERMODE;
            }
        }
        else
        {
            if (pCallParamsApp->dwBearerMode & ~dwAllBearerModes)
            {
                goto bad_bearer_mode;
            }
            else if (!IsOnlyOneBitSetInDWORD (pCallParamsApp->dwBearerMode))
            {
                LOG((TL_ERROR,
                    "%sbad dwBearerMode, x%x, allowed for 1.x apps/SPs",
                    szFunc,
                    pCallParamsApp->dwBearerMode
                    ));
            }
        }
    }
    else
    {
        //
        // For clarity's sake reset 0 bearer mode to VOICE
        //

        pCallParamsApp->dwBearerMode = LINEBEARERMODE_VOICE;
    }



    {
        DWORD dwMediaModeApp = pCallParamsApp->dwMediaMode;


        if ((dwMediaModeApp & (0x00ffffff ^ dwAllMediaModes)) ||

             // single media mode is version is less <= 2.1
             ( (dwAPIVersion <= TAPI_VERSION2_1) &&
               !IsOnlyOneBitSetInDWORD (dwMediaModeApp) &&
               !(dwMediaModeApp & LINEMEDIAMODE_UNKNOWN) ) )
        {
            //
            // For clarity's sake reset 0 media mode to INTERACTIVEVOICE
            //

            if (dwMediaModeApp == 0)
            {
                pCallParamsApp->dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
            }
            else
            {
                LOG((TL_ERROR, "%sbad dwMediaMode, x%x", szFunc, dwMediaModeApp));
                return LINEERR_INVALMEDIAMODE;
            }
        }
    }

    if (pCallParamsApp->dwCallParamFlags & ~dwAllCallParamFlags)
    {
        LOG((TL_ERROR,
            "%sbad dwCallParamFlags, x%x",
            szFunc,
            pCallParamsApp->dwCallParamFlags
            ));
        return LINEERR_INVALCALLPARAMS;
    }

    //
    // Note: an address mode of 0 means "default to any address,
    //       don't select a specific address" (says TNixon)
    //

    if (pCallParamsApp->dwAddressMode == LINEADDRESSMODE_ADDRESSID ||
        pCallParamsApp->dwAddressMode == LINEADDRESSMODE_DIALABLEADDR)
    {
        // do nothing (it's a valid addr mode)
    }
    else if (pCallParamsApp->dwAddressMode == 0)
    {
        //
        // For clarity's sake reset 0 addr mode to ADDRESSID
        //

        pCallParamsApp->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
    }
    else
    {
        LOG((TL_ERROR,
            "%sbad dwAddressMode, x%x",
            szFunc,
            pCallParamsApp->dwAddressMode
            ));

        return LINEERR_INVALADDRESSMODE;
    }

    if (ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwOrigAddressSize,
            pCallParamsApp->dwOrigAddressOffset,
            (!guiAlignmentFaultEnabled) ? 0 : 
                ((pCallParamsApp->dwAddressMode & LINEADDRESSMODE_DIALABLEADDR) ?
                    sizeof(TCHAR) : sizeof(DWORD)),
            szFunc,
            "OrigAddress"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwUserUserInfoSize,
            pCallParamsApp->dwUserUserInfoOffset,
            guiAlignmentFaultEnabled? sizeof(TCHAR) : 0,
            szFunc,
            "UserUserInfo"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwHighLevelCompSize,
            pCallParamsApp->dwHighLevelCompOffset,
            0,
            szFunc,
            "HighLevelComp"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwLowLevelCompSize,
            pCallParamsApp->dwLowLevelCompOffset,
            0,
            szFunc,
            "LowLevelComp"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwDevSpecificSize,
            pCallParamsApp->dwDevSpecificOffset,
            0,
            szFunc,
            "DevSpecificSize"
            ))
    {
        return LINEERR_INVALCALLPARAMS;
    }


    //
    // The following is an attempt to compensate for 1.x tapi apps
    // that borrowed dialer.exe's source code and package their
    // call params incorrectly.  The fix is to zero the offending
    // dwXxxSize/Offset pair of the various information-only fields,
    // so at worst some logging info will be lost, but the app will
    // still be able to make calls.  (Failure to correctly package
    // any of the above var-length fields is considered "fatal" in
    // any case.)
    //

    if (ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwDisplayableAddressSize,
            pCallParamsApp->dwDisplayableAddressOffset,
            guiAlignmentFaultEnabled? sizeof(TCHAR) : 0,
            szFunc,
            "DisplayableAddress"
            ))
    {
        if (dwAPIVersion < TAPI_VERSION2_0)
        {
            pCallParamsApp->dwDisplayableAddressSize   =
            pCallParamsApp->dwDisplayableAddressOffset = 0;
        }
        else
        {
            return LINEERR_INVALCALLPARAMS;
        }
    }

    if (ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwCalledPartySize,
            pCallParamsApp->dwCalledPartyOffset,
            guiAlignmentFaultEnabled? sizeof(TCHAR) : 0,
            szFunc,
            "CalledParty"
            ))
    {
        if (dwAPIVersion < TAPI_VERSION2_0)
        {
            pCallParamsApp->dwCalledPartySize   =
            pCallParamsApp->dwCalledPartyOffset = 0;
        }
        else
        {
            return LINEERR_INVALCALLPARAMS;
        }
    }

    if (ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwCommentSize,
            pCallParamsApp->dwCommentOffset,
            guiAlignmentFaultEnabled? sizeof(TCHAR) : 0,
            szFunc,
            "Comment"
            ))
    {
        if (dwAPIVersion < TAPI_VERSION2_0)
        {
            pCallParamsApp->dwCommentSize   =
            pCallParamsApp->dwCommentOffset = 0;
        }
        else
        {
            return LINEERR_INVALCALLPARAMS;
        }
    }


    if (dwAPIVersion <= TAPI_VERSION1_4)
    {
        goto ValidateCallParams_checkFixedSizes;
    }

    #define AllCallStates                     \
        (LINECALLSTATE_IDLE                 | \
        LINECALLSTATE_OFFERING              | \
        LINECALLSTATE_ACCEPTED              | \
        LINECALLSTATE_DIALTONE              | \
        LINECALLSTATE_DIALING               | \
        LINECALLSTATE_RINGBACK              | \
        LINECALLSTATE_BUSY                  | \
        LINECALLSTATE_SPECIALINFO           | \
        LINECALLSTATE_CONNECTED             | \
        LINECALLSTATE_PROCEEDING            | \
        LINECALLSTATE_ONHOLD                | \
        LINECALLSTATE_CONFERENCED           | \
        LINECALLSTATE_ONHOLDPENDCONF        | \
        LINECALLSTATE_ONHOLDPENDTRANSFER    | \
        LINECALLSTATE_DISCONNECTED          | \
        LINECALLSTATE_UNKNOWN)

    if (pCallParamsApp->dwPredictiveAutoTransferStates & ~AllCallStates)
    {
        LOG((TL_ERROR,
            "%sbad dwPredictiveAutoTransferStates, x%x",
            szFunc,
            pCallParamsApp->dwPredictiveAutoTransferStates
            ));

        return LINEERR_INVALCALLPARAMS;
    }

    if (ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwTargetAddressSize,
            pCallParamsApp->dwTargetAddressOffset,
            guiAlignmentFaultEnabled? sizeof(TCHAR) : 0,
            szFunc,
            "TargetAddress"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwSendingFlowspecSize,
            pCallParamsApp->dwSendingFlowspecOffset,
            guiAlignmentFaultEnabled? sizeof(DWORD) : 0,
            szFunc,
            "SendingFlowspec"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwReceivingFlowspecSize,
            pCallParamsApp->dwReceivingFlowspecOffset,
            guiAlignmentFaultEnabled? sizeof(DWORD) : 0,
            szFunc,
            "ReceivingFlowspec"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwDeviceClassSize,
            pCallParamsApp->dwDeviceClassOffset,
            0,
            szFunc,
            "DeviceClass"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwDeviceConfigSize,
            pCallParamsApp->dwDeviceConfigOffset,
            0,
            szFunc,
            "DeviceConfig"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwCallDataSize,
            pCallParamsApp->dwCallDataOffset,
            0,
            szFunc,
            "CallData"
            ) ||

        ISBADSIZEOFFSET(
            dwTotalSize,
            dwFixedSizeApp,
            pCallParamsApp->dwCallingPartyIDSize,
            pCallParamsApp->dwCallingPartyIDOffset,
            0,
            szFunc,
            "CallingPartyID"
            ))
    {
        return LINEERR_INVALCALLPARAMS;
    }

    if (dwAPIVersion < TAPI_VERSION3_0)
    {
        goto ValidateCallParams_checkFixedSizes;
    }

    if (pCallParamsApp->dwAddressType == 0)
    {
        // pCallParamsApp->dwAddressType = LINEADDRESSTYPE_PHONENUMBER;
    }
    else if ((pCallParamsApp->dwAddressType & ~AllAddressTypes) ||
             !IsOnlyOneBitSetInDWORD (pCallParamsApp->dwAddressType))
    {
        LOG((TL_ERROR,
            "%sbad dwAddressType, x%x",
            szFunc,
            pCallParamsApp->dwAddressType
            ));

        return LINEERR_INVALCALLPARAMS;
    }

ValidateCallParams_checkFixedSizes:

    if (dwAsciiCallParamsCodePage == TAPI_NO_DATA)
    {
        //
        // If here we're getting unicode call params from the app
        //
        // Check to see if the fixed size of the app's call params
        // are smaller than the fixed size of the call params
        // required by the service provider (due to it's negotiated
        // SPI version), and if so alloc a larger buffer to account
        // for this different fixed size & set it up correctly
        //

        if (dwFixedSizeApp < dwFixedSizeSP)
        {
            DWORD               dwFixedSizeDiff =
                                    dwFixedSizeSP - dwFixedSizeApp;
            LPLINECALLPARAMS    pCallParamsSP;


            if (!(pCallParamsSP = ServerAlloc (dwTotalSize + dwFixedSizeDiff)))
            {
                return LINEERR_NOMEM;
            }

            CopyMemory (pCallParamsSP, pCallParamsApp, dwFixedSizeApp);

            pCallParamsSP->dwTotalSize = dwTotalSize + dwFixedSizeDiff;

            CopyMemory(
                ((LPBYTE) pCallParamsSP) + dwFixedSizeSP,
                ((LPBYTE) pCallParamsApp) + dwFixedSizeApp,
                dwTotalSize - dwFixedSizeApp
                );

            if (pCallParamsSP->dwOrigAddressOffset)
            {
                pCallParamsSP->dwOrigAddressOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwDisplayableAddressOffset)
            {
                pCallParamsSP->dwDisplayableAddressOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwCalledPartyOffset)
            {
                pCallParamsSP->dwCalledPartyOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwCommentOffset)
            {
                pCallParamsSP->dwCommentOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwUserUserInfoOffset)
            {
                pCallParamsSP->dwUserUserInfoOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwHighLevelCompOffset)
            {
                pCallParamsSP->dwHighLevelCompOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwLowLevelCompOffset)
            {
                pCallParamsSP->dwLowLevelCompOffset += dwFixedSizeDiff;
            }

            if (pCallParamsSP->dwDevSpecificOffset)
            {
                pCallParamsSP->dwDevSpecificOffset += dwFixedSizeDiff;
            }

            if (dwAPIVersion >= TAPI_VERSION2_0)
            {
                if (pCallParamsSP->dwTargetAddressOffset)
                {
                    pCallParamsSP->dwTargetAddressOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwSendingFlowspecOffset)
                {
                    pCallParamsSP->dwSendingFlowspecOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwReceivingFlowspecOffset)
                {
                    pCallParamsSP->dwReceivingFlowspecOffset +=dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwDeviceClassOffset)
                {
                    pCallParamsSP->dwDeviceClassOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwDeviceConfigOffset)
                {
                    pCallParamsSP->dwDeviceConfigOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwCallDataOffset)
                {
                    pCallParamsSP->dwCallDataOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwCallingPartyIDOffset)
                {
                    pCallParamsSP->dwCallingPartyIDOffset += dwFixedSizeDiff;
                }
            }

            *ppCallParamsSP = pCallParamsSP;
        }
        else
        {
            *ppCallParamsSP = pCallParamsApp;
        }
    }
    else // see if there's ascii var data fields to translate
    {
        //
        // If here we're getting ascii call params form the app
        //
        // We may need to due ascii -> unicode conversions on some
        // of the var fields, as well as account for differences
        // in the fixed sizes of the call params structs as described
        // above
        //

        DWORD   dwAsciiVarDataSize,
                dwFixedSizeDiff = dwFixedSizeSP - dwFixedSizeApp;


        dwAsciiVarDataSize =
            pCallParamsApp->dwOrigAddressSize +
            pCallParamsApp->dwDisplayableAddressSize +
            pCallParamsApp->dwCalledPartySize +
            pCallParamsApp->dwCommentSize;

        if (dwAPIVersion > TAPI_VERSION1_4)
        {
            dwAsciiVarDataSize +=
                pCallParamsApp->dwTargetAddressSize +
                pCallParamsApp->dwDeviceClassSize +
                pCallParamsApp->dwCallingPartyIDSize;
        }

        if (dwFixedSizeDiff != 0  ||  dwAsciiVarDataSize != 0)
        {
            LPLINECALLPARAMS    pCallParamsSP;


            // alloc 3 extra for alignment
            if (!(pCallParamsSP = ServerAlloc(
                    dwTotalSize + dwFixedSizeDiff + 2 * dwAsciiVarDataSize + 3
                    )))
            {
                return LINEERR_NOMEM;
            }

            if (dwFixedSizeDiff)
            {
                CopyMemory (pCallParamsSP, pCallParamsApp, dwFixedSizeApp);

                CopyMemory(
                    ((LPBYTE) pCallParamsSP) + dwFixedSizeSP,
                    ((LPBYTE) pCallParamsApp) + dwFixedSizeApp,
                    dwTotalSize - dwFixedSizeApp
                    );

                if (pCallParamsSP->dwUserUserInfoOffset)
                {
                    pCallParamsSP->dwUserUserInfoOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwHighLevelCompOffset)
                {
                    pCallParamsSP->dwHighLevelCompOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwLowLevelCompOffset)
                {
                    pCallParamsSP->dwLowLevelCompOffset += dwFixedSizeDiff;
                }

                if (pCallParamsSP->dwDevSpecificOffset)
                {
                    pCallParamsSP->dwDevSpecificOffset += dwFixedSizeDiff;
                }

                if (dwAPIVersion >= TAPI_VERSION2_0)
                {
                    if (pCallParamsSP->dwSendingFlowspecOffset)
                    {
                        pCallParamsSP->dwSendingFlowspecOffset +=
                            dwFixedSizeDiff;
                    }

                    if (pCallParamsSP->dwReceivingFlowspecOffset)
                    {
                        pCallParamsSP->dwReceivingFlowspecOffset +=
                            dwFixedSizeDiff;
                    }

                    if (pCallParamsSP->dwDeviceConfigOffset)
                    {
                        pCallParamsSP->dwDeviceConfigOffset +=
                            dwFixedSizeDiff;
                    }

                    if (pCallParamsSP->dwCallDataOffset)
                    {
                        pCallParamsSP->dwCallDataOffset +=
                            dwFixedSizeDiff;
                    }
                }
            }
            else
            {
                CopyMemory (pCallParamsSP, pCallParamsApp, dwTotalSize);
            }

            pCallParamsSP->dwTotalSize = dwTotalSize + dwFixedSizeDiff +
                2*dwAsciiVarDataSize + 3;

            if (dwAsciiVarDataSize)
            {
                LPDWORD alpdwXxxSize[] =
                {
                    &pCallParamsSP->dwOrigAddressSize,
                    &pCallParamsSP->dwDisplayableAddressSize,
                    &pCallParamsSP->dwCalledPartySize,
                    &pCallParamsSP->dwCommentSize,
                    (dwAPIVersion > TAPI_VERSION1_4 ?
                        &pCallParamsSP->dwTargetAddressSize : NULL),
                    (dwAPIVersion > TAPI_VERSION1_4 ?
                        &pCallParamsSP->dwDeviceClassSize : NULL),
                    (dwAPIVersion > TAPI_VERSION1_4 ?
                        &pCallParamsSP->dwCallingPartyIDSize : NULL),
                    NULL
                };

                // align dwXxxOffset
                DWORD   i, dwXxxOffset = (dwTotalSize + dwFixedSizeDiff + 3) &
                                         0xFFFFFFFC;


                for (i = 0; alpdwXxxSize[i]; i++)
                {
                    if (*alpdwXxxSize[i] != 0)
                    {
                        MultiByteToWideChar(
                            (UINT) dwAsciiCallParamsCodePage,
                            MB_PRECOMPOSED,
                            (LPCSTR) (((LPBYTE) pCallParamsApp) +
                                *(alpdwXxxSize[i] + 1)), // dwXxxOffset
                            *alpdwXxxSize[i],
                            (LPWSTR) (((LPBYTE) pCallParamsSP) + dwXxxOffset),
                            *alpdwXxxSize[i] * 2
                            );

                        *(alpdwXxxSize[i] + 1) = dwXxxOffset;
                        *alpdwXxxSize[i] *= 2;
                        dwXxxOffset += *alpdwXxxSize[i];
                    }
                }
            }

            *ppCallParamsSP = pCallParamsSP;
        }
        else
        {
            *ppCallParamsSP = pCallParamsApp;
        }
    }

    return 0; // success
}


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;
        }
    }
}


PTLINELOOKUPENTRY
GetLineLookupEntry(
    DWORD   dwDeviceID
    )
{
    DWORD               dwDeviceIDBase = 0;
    PTLINELOOKUPTABLE   pLookupTable = TapiGlobals.pLineLookup;


    if (dwDeviceID >= TapiGlobals.dwNumLines)
    {
        return ((PTLINELOOKUPENTRY) NULL);
    }

    while (pLookupTable)
    {
        if (dwDeviceID < pLookupTable->dwNumTotalEntries)
        {
            return (pLookupTable->aEntries + dwDeviceID);
        }

        dwDeviceID -= pLookupTable->dwNumTotalEntries;

        pLookupTable = pLookupTable->pNext;
    }

    return ((PTLINELOOKUPENTRY) NULL);
}


BOOL
PASCAL
IsValidLineExtVersion(
    DWORD   dwDeviceID,
    DWORD   dwExtVersion
    )
{
    BOOL                bResult;
    PTLINE              ptLine;
    PTPROVIDER          ptProvider;
    PTLINELOOKUPENTRY   pLookupEntry;


    if (dwExtVersion == 0)
    {
        return TRUE;
    }

    if (!(pLookupEntry = GetLineLookupEntry (dwDeviceID)))
    {
        return FALSE;
    }

    ptLine = pLookupEntry->ptLine;

    if (ptLine)
    {
        try
        {
            if (ptLine->dwExtVersionCount)
            {
                bResult = (dwExtVersion == ptLine->dwExtVersion ?
                    TRUE : FALSE);

                if (ptLine->dwKey == TLINE_KEY)
                {
                    goto IsValidLineExtVersion_return;
                }
            }
        }
        myexcept
        {
            //
            // if here the line was closed, just drop thru to the code below
            //
        }
    }

    ptProvider = pLookupEntry->ptProvider;

    if (ptProvider->apfn[SP_LINENEGOTIATEEXTVERSION])
    {
        LONG    lResult;
        DWORD   dwNegotiatedExtVersion;


        lResult = CallSP5(
            ptProvider->apfn[SP_LINENEGOTIATEEXTVERSION],
            "lineNegotiateExtVersion",
            SP_FUNC_SYNC,
            (DWORD) dwDeviceID,
            (DWORD) pLookupEntry->dwSPIVersion,
            (DWORD) dwExtVersion,
            (DWORD) dwExtVersion,
            (ULONG_PTR) &dwNegotiatedExtVersion
            );

        bResult = ((lResult || !dwNegotiatedExtVersion) ? FALSE : TRUE);
    }
    else
    {
        bResult = FALSE;
    }

IsValidLineExtVersion_return:

    return bResult;
}


PTCALLCLIENT
PASCAL
ReferenceCall(
    HCALL       hCall,
    PTCLIENT    ptClient
    )
{
    PTCALLCLIENT    ptCallClient;


    if ((ptCallClient = ReferenceObject(
            ghHandleTable,
            hCall,
            TCALLCLIENT_KEY
            )))
    {
        if (ptCallClient->ptClient != ptClient)
        {
            DereferenceObject (ghHandleTable, hCall, 1);
            ptCallClient = NULL;
        }
    }

    return ptCallClient;
}


PTCALLHUBCLIENT
PASCAL
IsValidCallHub(
    HCALLHUB    hCallHub,
    PTCLIENT    ptClient
    )
{
    PTCALLHUBCLIENT     ptCallHubClient = NULL;
    if (hCallHub == 0)
    {
        goto ExitHere;
    }

    try
    {
        ptCallHubClient = ReferenceObject (
                            ghHandleTable, 
                            hCallHub, 
                            TCALLHUBCLIENT_KEY);
        if (ptCallHubClient)
        {
            if ((ptCallHubClient->ptClient != ptClient) ||
                 (ptCallHubClient->hCallHub != hCallHub))
            {
                ptCallHubClient = NULL;
            }
            DereferenceObject(ghHandleTable, hCallHub, 1);
        }
    }
    myexcept
    {
    }

ExitHere:
    return ptCallHubClient;
}


PTLINEAPP
PASCAL
IsValidLineApp(
    HLINEAPP    hLineApp,
    PTCLIENT    ptClient
    )
{
    PTLINEAPP   ptLineApp;


    if ((ptLineApp = ReferenceObject (ghHandleTable, hLineApp, TLINEAPP_KEY)))
    {
        if (ptLineApp->ptClient != ptClient)
        {
            ptLineApp = NULL;
        }

        DereferenceObject (ghHandleTable, hLineApp, 1);
    }

    return ptLineApp;
}


BOOL
PASCAL
WaitForExclusivetCallAccess(
    PTCALL  ptCall,
    DWORD   dwKey
    )
{
    //
    // Retrieve the call instance & check the key to make sure it's
    // really a tCall object, then wait for exclusive access, then
    // reverify the key and the call instance (it's possible the tCall
    // object might have been freed & reallocated while we were waiting,
    // in which case it will have come back to life with a different
    // dwCallInstance value)
    //

    BOOL bUnlock = FALSE;


    try
    {
        HCALL   hCall = ptCall->hCall;


        if (ptCall->dwKey == dwKey)
        {
            LOCKTCALL (ptCall);

            bUnlock = TRUE;

            if (ptCall->dwKey == dwKey  &&
                ptCall->hCall == hCall)
            {
                return TRUE;
            }
        }
    }
    myexcept
    {
        // just fall thru
    }

    if (bUnlock)
    {
        UNLOCKTCALL (ptCall);
    }

    return FALSE;
}


BOOL
PASCAL
WaitForExclusivetLineAccess(
    PTLINE      ptLine,
    HANDLE     *phMutex,
    BOOL       *pbDupedMutex,
    DWORD       dwTimeout
    )
{
    BOOL    bResult = FALSE;


    try
    {
        if (ptLine->dwKey == TLINE_KEY  &&

            WaitForMutex(
                ptLine->hMutex,
                phMutex,
                pbDupedMutex,
                (LPVOID) ptLine,
                TLINE_KEY,
                dwTimeout
                ))
        {
            if (ptLine->dwKey == TLINE_KEY)
            {
                bResult = TRUE;
            }
            else
            {
                MyReleaseMutex (*phMutex, *pbDupedMutex);
            }
        }
    }
    myexcept
    {
        // do nothing
    }

    return bResult;
}


BOOL
PASCAL
WaitForExclusiveLineClientAccess(
    PTLINECLIENT    ptLineClient
    )
{
    //
    // Assumes ptXxxClient->hXxx has already been referenced,
    // so we can safely access ptXxxClient
    //

    LOCKTLINECLIENT (ptLineClient);

    if (ptLineClient->dwKey == TLINECLIENT_KEY)
    {
        return TRUE;
    }

    UNLOCKTLINECLIENT (ptLineClient);

    return FALSE;
}


PTLINEAPP
PASCAL
WaitForExclusiveLineAppAccess(
    HLINEAPP    hLineApp,
    PTCLIENT    ptClient
    )
{
    PTLINEAPP   ptLineApp;


    if (!(ptLineApp = ReferenceObject(
            ghHandleTable,
            hLineApp,
            TLINEAPP_KEY
            )))
    {
        return NULL;
    }

    LOCKTLINEAPP (ptLineApp);

    if ((ptLineApp->dwKey != TLINEAPP_KEY)  ||
        (ptLineApp->ptClient != ptClient))
    {

        UNLOCKTLINEAPP (ptLineApp);

        ptLineApp = NULL;
    }

    DereferenceObject (ghHandleTable, hLineApp, 1);

    return ptLineApp;
}


PTCLIENT
PASCAL
WaitForExclusiveClientAccess(
    PTCLIENT    ptClient
    )
{
    LOCKTCLIENT (ptClient);

    try
    {
        if (ptClient->dwKey == TCLIENT_KEY)
        {
            return (ptClient);
        }
    }
    myexcept
    {
    }

    UNLOCKTCLIENT (ptClient);

    return NULL;
}


LONG
PASCAL
FindProxy(
    PTLINECLIENT    ptLineClient,
    DWORD           dwAddressID,
    DWORD           dwRequestType,
    PTLINECLIENT   *ppProxy,
    LPDWORD         lpdwDeviceID,
    DWORD           dwMinAPIVersion
    )
{
    LONG    lResult;
    PTLINE  ptLine;

    try
    {
        if (ptLineClient->ptLineApp->dwAPIVersion >= dwMinAPIVersion)
        {
            ptLine = ptLineClient->ptLine;
            *ppProxy = ptLine->apProxys[dwRequestType];
            *lpdwDeviceID = ptLine->dwDeviceID;

            if (dwAddressID >= ptLine->dwNumAddresses)
            {
                lResult = LINEERR_INVALADDRESSID;
            }
            else if (ptLine->dwKey != TLINE_KEY)
            {
                lResult = LINEERR_INVALLINEHANDLE;
            }
            else
            {
                lResult = 0;
            }
        }
        else
        {
            lResult = LINEERR_INCOMPATIBLEAPIVERSION;
        }
    }
    myexcept
    {
        lResult = LINEERR_OPERATIONUNAVAIL;
    }

    return lResult;
}


LONG
PASCAL
CreateProxyRequest(
    PTLINECLIENT            pProxy,
    DWORD                   dwRequestType,
    DWORD                   dwExtraBytes,
    PASYNCREQUESTINFO       pAsyncReqInfo,
    PPROXYREQUESTWRAPPER   *ppWrapper
    )
{
    HLINE                   hLine;
    DWORD                   dwSize, dwComputerNameSize, dwUserNameSize;
    PTCLIENT                ptClient = pAsyncReqInfo->ptClient;
    DWORD                   initContext, openContext;
    PPROXYREQUESTWRAPPER    pWrapper;


    LOG((TL_TRACE,  "CreateProxyRequest: enter..."));
    
    //
    // Safely get info from the proxy, then make sure it's still valid
    // if the proxy is refusing LINE_PROXYREQUEST msg, return failure
    //

    try
    {
        hLine       = pProxy->hLine;
        initContext = pProxy->ptLineApp->InitContext;
        openContext = pProxy->OpenContext;

        if (pProxy->dwKey != TLINECLIENT_KEY ||
            (FMsgDisabled(
                pProxy->ptLineApp->dwAPIVersion,
                pProxy->adwEventSubMasks,
                LINE_PROXYREQUEST,
                0)))
        {
            return LINEERR_OPERATIONUNAVAIL;
        }
    }
    myexcept
    {
        return LINEERR_OPERATIONUNAVAIL;
    }

    dwComputerNameSize = (ptClient->dwComputerNameSize + TALIGN_COUNT) & TALIGN_MASK;
    dwUserNameSize     = (ptClient->dwUserNameSize + TALIGN_COUNT) & TALIGN_MASK;


    //
    // Calculate, alloc, & initalize a PROXYREQUESTWRAPPER struct.  At the
    // head of this struct is the msg info for the LINE_PROXYREQUEST,
    // followed by the actual request data.
    //

    dwSize =
        (sizeof (ASYNCEVENTMSG) +   // LINE_PROXYREQUEST msg info
        7 * sizeof (DWORD) +        // Non-union fields in LINEPROXYREQUEST
        dwExtraBytes +              // Request-specific size
        dwUserNameSize +            // User name size
        dwComputerNameSize +        // Computer name size
        TALIGN_COUNT) & TALIGN_MASK;// make sure size is a ULONG_PTR multiple
                                    //   so our lstrcpyW's below don't fault
                                    //   and so that when this msg eventually
                                    //   gets copied to some client's async
                                    //   event buf we don't start running into
                                    //   alignment problems (the msgs's
                                    //   TotalSize field must be ULONG_PTR-
                                    //   aligned)

    if (!(pWrapper = ServerAlloc (dwSize)))
    {
        return LINEERR_NOMEM;
    }

    pWrapper->AsyncEventMsg.TotalSize            = dwSize;
    pWrapper->AsyncEventMsg.InitContext          = initContext;
    //pWrapper->AsyncEventMsg.hfnPostProcessProc =
    pWrapper->AsyncEventMsg.hDevice              = pProxy->hLine;
    pWrapper->AsyncEventMsg.Msg                  = LINE_PROXYREQUEST;
    pWrapper->AsyncEventMsg.OpenContext          = openContext;
    pWrapper->AsyncEventMsg.Param1               =
        pAsyncReqInfo->dwLocalRequestID;
    //pWrapper->AsyncEventMsg.Param2           =
    //pWrapper->AsyncEventMsg.Param3           =
    //pWrapper->AsyncEventMsg.Param4           =

    dwSize -= sizeof (ASYNCEVENTMSG);

    pWrapper->ProxyRequest.dwSize = dwSize;

    pWrapper->ProxyRequest.dwClientMachineNameSize   = ptClient->dwComputerNameSize;
    pWrapper->ProxyRequest.dwClientMachineNameOffset =
        dwSize - dwComputerNameSize;

    if (NULL != ptClient->pszComputerName)
    {
        wcscpy(
            (PWSTR)((LPBYTE) &pWrapper->ProxyRequest +
                pWrapper->ProxyRequest.dwClientMachineNameOffset),
            ptClient->pszComputerName
            );
    }

    pWrapper->ProxyRequest.dwClientUserNameSize   = ptClient->dwUserNameSize;
    pWrapper->ProxyRequest.dwClientUserNameOffset =
        (dwSize - dwComputerNameSize) - dwUserNameSize;

    if (NULL != ptClient->pszUserName)
    {
        wcscpy(
            (PWSTR)((LPBYTE) &pWrapper->ProxyRequest +
                pWrapper->ProxyRequest.dwClientUserNameOffset),
            ptClient->pszUserName
            );
    }

    pWrapper->ProxyRequest.dwClientAppAPIVersion = 0;
    pWrapper->ProxyRequest.dwRequestType = dwRequestType;

    *ppWrapper = pWrapper;


    //
    // Change the AsyncRequestInfo struct's key value to be ==
    // the proxy's hLine, so we can verify when app calls
    // lineProxyResponse
    //

    pAsyncReqInfo->dwKey = hLine;

    return 0;
}


LONG
PASCAL
SendProxyRequest(
    PTLINECLIENT            pProxy,
    PPROXYREQUESTWRAPPER    pWrapper,
    PASYNCREQUESTINFO       pAsyncRequestInfo
    )
{
    LONG    lResult;
    BOOL    bUnlock = FALSE;


    //
    // Add the request to the proxy's list, then send it the request.
    // Since the proxy (tLineClient) could get closed at any time we
    // wrap the following in a try/except.
    //
    // Note: the AsyncReqInfo.dwParam4 & dwParam5 fields are used as
    // the prev & next pointers for maintaining the list of proxy
    // requests pending on tLineClient.
    //

    try
    {
        if (WaitForExclusiveLineClientAccess (pProxy))
        {
            bUnlock = TRUE;

            if ((pAsyncRequestInfo->dwParam5 = (ULONG_PTR)
                    pProxy->pPendingProxyRequests))
            {
                ((PASYNCREQUESTINFO) pAsyncRequestInfo->dwParam5)->dwParam4 =
                     (ULONG_PTR) pAsyncRequestInfo;
            }

            pProxy->pPendingProxyRequests = pAsyncRequestInfo;

            UNLOCKTLINECLIENT (pProxy);

            bUnlock = FALSE;

            WriteEventBuffer (pProxy->ptClient, (PASYNCEVENTMSG) pWrapper);

            lResult = 0;
        }
        else
        {
            lResult = LINEERR_OPERATIONUNAVAIL;
        }
    }
    myexcept
    {
        lResult = LINEERR_OPERATIONUNAVAIL;
    }

    if (bUnlock)
    {
        UNLOCKTLINECLIENT (pProxy);
    }

    ServerFree (pWrapper);

    return lResult;
}


BOOL
PASCAL
NotifyHighestPriorityRequestRecipient(
    void
    )
{
    //
    // Send a LINE_REQUEST msg to the highest priority request recipient
    // to inform it that there are requests available for processing
    //

    PTLINEAPP       ptLineApp;
    ASYNCEVENTMSG   msg;

    LOG((TL_TRACE,  "NotifyHighestPriorityRequestRecipient: enter..."));

    EnterCriticalSection (&gPriorityListCritSec);

        ptLineApp = TapiGlobals.pHighestPriorityRequestRecipient->ptLineApp;

        msg.TotalSize          = sizeof (ASYNCEVENTMSG);
        msg.InitContext        = ptLineApp->InitContext;
    
        msg.fnPostProcessProcHandle = 0;
    
        msg.hDevice            = 0;
        msg.Msg                = LINE_REQUEST;
        msg.OpenContext        =
            (TapiGlobals.pHighestPriorityRequestRecipient->dwRegistrationInstance);
        msg.Param1             = LINEREQUESTMODE_MAKECALL;
        msg.Param2             =
        msg.Param3             = 0;

        WriteEventBuffer (ptLineApp->ptClient, &msg);

    LeaveCriticalSection (&gPriorityListCritSec);

    LOG((TL_TRACE,  "NotifyHighestPriorityRequestRecipient: finished."));

    return TRUE;
}


void
SetDrvCallFlags(
    PTCALL      ptCall,
    DWORD       dwDrvCallFlags
    )
{
    //
    // This func is called on return from TSPI_lineMakeCall (and other
    // TSPI_lineXxx funcs where calls are created) and sets the
    // dwDrvCallFlags field in the tCall as specified.  This keeps
    // another thread which is currently doing a DestroytCall on this
    // call from passing an invalid hdCall to the provider when
    // doing a TSPI_lineCloseCall.
    //
    //

    LOCKTCALL (ptCall);

    try
    {
        if ((ptCall->dwKey == TINCOMPLETECALL_KEY) ||
            (ptCall->dwKey == TCALL_KEY) ||
            (ptCall->dwKey == TZOMBIECALL_KEY))
        {
            // only set the loword

            ptCall->dwDrvCallFlags = MAKELONG(LOWORD(dwDrvCallFlags),
                HIWORD(ptCall->dwDrvCallFlags));
        }
    }
    myexcept
    {
    }

    UNLOCKTCALL (ptCall);
}


LONG
PASCAL
SetCallConfList(
    PTCALL              ptCall,
    PTCONFERENCELIST    pConfList,
    BOOL                bAddToConfPostProcess
    )
{
    LONG    lResult;
    BOOL    bAddToConfList = FALSE;


    if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
    {
        if (pConfList)
        {
            if (ptCall->pConfList && !bAddToConfPostProcess)
            {
                lResult = LINEERR_INVALCALLHANDLE;
            }
            else
            {
                ptCall->pConfList = pConfList;
                lResult = 0;
                bAddToConfList = TRUE;
            }
        }
        else
        {
            if (ptCall->pConfList)
            {
                pConfList = ptCall->pConfList;
                ptCall->pConfList = NULL;
                lResult = 0;
            }
            else
            {
                lResult = LINEERR_INVALCALLHANDLE;
            }
        }

        UNLOCKTCALL (ptCall);
    }
    else
    {
        lResult = LINEERR_INVALCALLHANDLE;
    }

    if (pConfList &&
        (pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff) ) &&
        (lResult == 0))
    {
        if (bAddToConfList)
        {
             while (pConfList->dwNumUsedEntries >=
                     pConfList->dwNumTotalEntries)
             {
                 if (pConfList->pNext)
                 {
                     pConfList = pConfList->pNext;
                 }
                 else
                 {
                    DWORD               dwSize;
                    PTCONFERENCELIST    pNewConfList;


                    dwSize = sizeof (TCONFERENCELIST) +  sizeof (PTCALL) *
                         (2 * pConfList->dwNumTotalEntries - 1);

                    if (!(pNewConfList = ServerAlloc (dwSize)))
                    {
                         ptCall->pConfList = NULL;
                         return LINEERR_NOMEM;
                    }

                    pNewConfList->dwNumTotalEntries =
                        2 * pConfList->dwNumTotalEntries;

                    pConfList->pNext = pNewConfList;

                    pConfList = pNewConfList;
                }
            }

            pConfList->aptCalls[pConfList->dwNumUsedEntries++] = ptCall;
        }
        else
        {
            while (pConfList)
            {
                DWORD   i, dwNumUsedEntries = pConfList->dwNumUsedEntries;
                PTCALL *pptCall = pConfList->aptCalls;


                for (i = 0; i < dwNumUsedEntries; i++)
                {
                    if (pConfList->aptCalls[i] == ptCall)
                    {
                        //
                        // Found the call in the list, shuffle all the
                        // following calls in list down by 1 to maintain
                        // continuity
                        //

                        for (; i < (dwNumUsedEntries - 1); i++)
                        {
                            pConfList->aptCalls[i] = pConfList->aptCalls[i+1];
                        }

                        pConfList->dwNumUsedEntries--;

                        pConfList = NULL;

                        break;
                    }

                    pptCall++;
                }

                if (pConfList)
                {
                    pConfList = pConfList->pNext;
                }
            }
        }
    }

    return lResult;
}


LONG
PASCAL
RemoveCallFromLineList(
    PTCALL  ptCall
    )
{
    PTLINE ptLine = (PTLINE) ptCall->ptLine;


    WaitForSingleObject (ptLine->hMutex, INFINITE);

    if (ptCall->pNext)
    {
        ptCall->pNext->pPrev = ptCall->pPrev;
    }

    if (ptCall->pPrev)
    {
        ptCall->pPrev->pNext = ptCall->pNext;
    }
    else
    {
        ptLine->ptCalls = ptCall->pNext;
    }

    ReleaseMutex (ptLine->hMutex);

    return 0;
}


LONG
PASCAL
RemoveCallClientFromLineClientList(
    PTCALLCLIENT    ptCallClient
    )
{
    PTLINECLIENT    ptLineClient = ptCallClient->ptLineClient;


    LOCKTLINECLIENT (ptLineClient);

    if (ptCallClient->pNextSametLineClient)
    {
        ptCallClient->pNextSametLineClient->pPrevSametLineClient =
            ptCallClient->pPrevSametLineClient;
    }

    if (ptCallClient->pPrevSametLineClient)
    {
        ptCallClient->pPrevSametLineClient->pNextSametLineClient =
            ptCallClient->pNextSametLineClient;
    }
    else
    {
        ptLineClient->ptCallClients = ptCallClient->pNextSametLineClient;
    }

    UNLOCKTLINECLIENT (ptLineClient);

    return 0;
}


LONG
PASCAL
GetConfCallListFromConf(
    PTCONFERENCELIST    pConfList,
    PTPOINTERLIST      *ppList
    )
{
    DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                    dwNumUsedEntries = 0, i;
    PTPOINTERLIST   pList = *ppList;

    while (pConfList)
    {
        if ((dwNumUsedEntries + pConfList->dwNumUsedEntries) >
                dwNumTotalEntries)
        {
            //
            // We need a larger list, so alloc a new one, copy the
            // contents of the current one, and the free the current
            // one iff we previously alloc'd it
            //

            PTPOINTERLIST   pNewList;


            do
            {
                dwNumTotalEntries <<= 1;

            } while ((dwNumUsedEntries + pConfList->dwNumUsedEntries) >
                        dwNumTotalEntries);

            if (!(pNewList = ServerAlloc(
                    sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                        (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                    )))
            {
                return LINEERR_NOMEM;
            }

            CopyMemory(
                pNewList->aEntries,
                pList->aEntries,
                dwNumUsedEntries * sizeof (LPVOID)
                );

            if (pList != *ppList)
            {
                ServerFree (pList);
            }

            pList = pNewList;
        }

        for (i = 0; i < pConfList->dwNumUsedEntries; i++)
        {
            pList->aEntries[dwNumUsedEntries++] = pConfList->aptCalls[i];
        }

        pConfList = pConfList->pNext;
    }

    pList->dwNumUsedEntries = dwNumUsedEntries;

    *ppList = pList;

    return 0;
}


LONG
PASCAL
GetList(
    LIST_ENTRY     *pListHead,
    PTPOINTERLIST  *ppList
    )
{
    DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                    dwNumUsedEntries = 0;
    PTPOINTERLIST   pList = *ppList;
    LIST_ENTRY     *pEntry;


    pEntry = pListHead->Flink;

    while (pEntry != pListHead)
    {
        if (dwNumUsedEntries == dwNumTotalEntries)
        {
            //
            // We need a larger list, so alloc a new one, copy the
            // contents of the current one, and the free the current
            // one iff we previously alloc'd it
            //

            PTPOINTERLIST   pNewList;


            dwNumTotalEntries <<= 1;

            if (!(pNewList = ServerAlloc(
                    sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                        (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                    )))
            {
                return LINEERR_NOMEM;
            }

            CopyMemory(
                pNewList->aEntries,
                pList->aEntries,
                dwNumUsedEntries * sizeof (LPVOID)
                );

            if (pList != *ppList)
            {
                ServerFree (pList);
            }

            pList = pNewList;
        }

        pList->aEntries[dwNumUsedEntries++] = pEntry;

        pEntry = pEntry->Flink;
    }

    pList->dwNumUsedEntries = dwNumUsedEntries;

    *ppList = pList;

    return 0;
}


LONG
PASCAL
GetCallClientListFromCall(
    PTCALL          ptCall,
    PTPOINTERLIST  *ppList
    )
{
    if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
    {
        DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                        dwNumUsedEntries = 0;
        PTPOINTERLIST   pList = *ppList;
        PTCALLCLIENT    ptCallClient = ptCall->ptCallClients;


        while (ptCallClient)
        {
            if (dwNumUsedEntries == dwNumTotalEntries)
            {
                //
                // We need a larger list, so alloc a new one, copy the
                // contents of the current one, and the free the current
                // one iff we previously alloc'd it
                //

                PTPOINTERLIST   pNewList;


                dwNumTotalEntries <<= 1;

                if (!(pNewList = ServerAlloc(
                        sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                            (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                        )))
                {
                    UNLOCKTCALL (ptCall);
                    return LINEERR_NOMEM;
                }

                CopyMemory(
                    pNewList->aEntries,
                    pList->aEntries,
                    dwNumUsedEntries * sizeof (LPVOID)
                    );

                if (pList != *ppList)
                {
                    ServerFree (pList);
                }

                pList = pNewList;
            }

            pList->aEntries[dwNumUsedEntries++] = ptCallClient;

            ptCallClient = ptCallClient->pNextSametCall;
        }

        UNLOCKTCALL (ptCall);

        pList->dwNumUsedEntries = dwNumUsedEntries;

        *ppList = pList;
    }
    else
    {
        return LINEERR_INVALCALLHANDLE;
    }

    return 0;
}


LONG
PASCAL
GetCallListFromLine(
    PTLINE          ptLine,
    PTPOINTERLIST  *ppList
    )
{
    BOOL    bDupedMutex;
    HANDLE  hMutex;


    if (WaitForExclusivetLineAccess(
            ptLine,
            &hMutex,
            &bDupedMutex,
            INFINITE
            ))
    {
        DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                        dwNumUsedEntries = 0;
        PTCALL          ptCall = ptLine->ptCalls;
        PTPOINTERLIST   pList = *ppList;


        while (ptCall)
        {
            if (dwNumUsedEntries == dwNumTotalEntries)
            {
                //
                // We need a larger list, so alloc a new one, copy the
                // contents of the current one, and the free the current
                // one iff we previously alloc'd it
                //

                PTPOINTERLIST   pNewList;


                dwNumTotalEntries <<= 1;

                if (!(pNewList = ServerAlloc(
                        sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                            (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                        )))
                {
                    MyReleaseMutex (hMutex, bDupedMutex);
                    return LINEERR_NOMEM;
                }

                CopyMemory(
                    pNewList->aEntries,
                    pList->aEntries,
                    dwNumUsedEntries * sizeof (LPVOID)
                    );

                if (pList != *ppList)
                {
                    ServerFree (pList);
                }

                pList = pNewList;
            }

            pList->aEntries[dwNumUsedEntries++] = ptCall;

            ptCall = ptCall->pNext;
        }

        MyReleaseMutex (hMutex, bDupedMutex);

        pList->dwNumUsedEntries = dwNumUsedEntries;

        *ppList = pList;
    }
    else
    {
        LOG((TL_TRACE,  "GetCallListFromLine: inval ptLine=x%p", ptLine));
        return LINEERR_INVALLINEHANDLE;
    }

    return 0;
}


LONG
PASCAL
GetLineClientListFromLine(
    PTLINE          ptLine,
    PTPOINTERLIST  *ppList
    )
{
    BOOL    bDupedMutex;
    HANDLE  hMutex;


    if (WaitForExclusivetLineAccess(
            ptLine,
            &hMutex,
            &bDupedMutex,
            INFINITE
            ))
    {
        DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                        dwNumUsedEntries = 0;
        PTPOINTERLIST   pList = *ppList;
        PTLINECLIENT    ptLineClient = ptLine->ptLineClients;


        while (ptLineClient)
        {
            if (dwNumUsedEntries == dwNumTotalEntries)
            {
                //
                // We need a larger list, so alloc a new one, copy the
                // contents of the current one, and the free the current
                // one iff we previously alloc'd it
                //

                PTPOINTERLIST   pNewList;


                dwNumTotalEntries <<= 1;

                if (!(pNewList = ServerAlloc(
                        sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                            (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                        )))
                {
                    MyReleaseMutex (hMutex, bDupedMutex);
                    return LINEERR_NOMEM;
                }

                CopyMemory(
                    pNewList->aEntries,
                    pList->aEntries,
                    dwNumUsedEntries * sizeof (LPVOID)
                    );

                if (pList != *ppList)
                {
                    ServerFree (pList);
                }

                pList = pNewList;
            }

            pList->aEntries[dwNumUsedEntries++] = ptLineClient;

            ptLineClient = ptLineClient->pNextSametLine;
        }

        MyReleaseMutex (hMutex, bDupedMutex);

        pList->dwNumUsedEntries = dwNumUsedEntries;

        *ppList = pList;
    }
    else
    {
        LOG((TL_ERROR, "GetLineClientListFromLine: inval ptLine=x%p", ptLine));
        return LINEERR_INVALLINEHANDLE;
    }

    return 0;
}


LONG
PASCAL
GetLineAppListFromClient(
    PTCLIENT        ptClient,
    PTPOINTERLIST  *ppList
    )
{
    if (WaitForExclusiveClientAccess (ptClient))
    {
        DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                        dwNumUsedEntries = 0;
        PTLINEAPP       ptLineApp = ptClient->ptLineApps;
        PTPOINTERLIST   pList = *ppList;


        while (ptLineApp)
        {
            if (dwNumUsedEntries == dwNumTotalEntries)
            {
                //
                // We need a larger list, so alloc a new one, copy the
                // contents of the current one, and the free the current
                // one iff we previously alloc'd it
                //

                PTPOINTERLIST   pNewList;


                dwNumTotalEntries <<= 1;

                if (!(pNewList = ServerAlloc(
                        sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                            (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                        )))
                {
                    UNLOCKTCLIENT (ptClient);
                    return LINEERR_NOMEM;
                }

                CopyMemory(
                    pNewList->aEntries,
                    pList->aEntries,
                    dwNumUsedEntries * sizeof (LPVOID)
                    );

                if (pList != *ppList)
                {
                    ServerFree (pList);
                }

                pList = pNewList;
            }

            pList->aEntries[dwNumUsedEntries++] = ptLineApp;

            ptLineApp = ptLineApp->pNext;
        }

        UNLOCKTCLIENT (ptClient);

        pList->dwNumUsedEntries = dwNumUsedEntries;

        *ppList = pList;
    }
    else
    {
        return LINEERR_OPERATIONFAILED;
    }

    return 0;
}


LONG
PASCAL
GetClientList(
    BOOL            bAdminOnly,
    PTPOINTERLIST   *ppList
    )
{
    DWORD           dwNumTotalEntries = DEF_NUM_PTR_LIST_ENTRIES,
                    dwNumUsedEntries = 0;
    PTPOINTERLIST   pList = *ppList;
    PTCLIENT        ptClient;


    TapiEnterCriticalSection (&TapiGlobals.CritSec);

    ptClient = TapiGlobals.ptClients;
    while (ptClient)
    {
        if (!bAdminOnly || IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
        {
            if (dwNumUsedEntries == dwNumTotalEntries)
            {
                //
                // We need a larger list, so alloc a new one, copy the
                // contents of the current one, and the free the current
                // one iff we previously alloc'd it
                //
                PTPOINTERLIST   pNewList;

                dwNumTotalEntries <<= 1;

                if (!(pNewList = ServerAlloc(
                        sizeof (TPOINTERLIST) + sizeof (LPVOID) *
                        (dwNumTotalEntries - DEF_NUM_PTR_LIST_ENTRIES)
                    )))
                {
                    TapiLeaveCriticalSection (&TapiGlobals.CritSec);
                    return LINEERR_NOMEM;
                }

                CopyMemory(
                    pNewList->aEntries,
                    pList->aEntries,
                    dwNumUsedEntries * sizeof (LPVOID)
                    );

                if (pList != *ppList)
                {
                    ServerFree (pList);
                }

                pList = pNewList;
            }

            pList->aEntries[dwNumUsedEntries++] = ptClient;
        }
        ptClient = ptClient->pNext;
    }

    TapiLeaveCriticalSection (&TapiGlobals.CritSec);
    pList->dwNumUsedEntries = dwNumUsedEntries;
    *ppList = pList;
    return 0;
}

void
PASCAL
SendMsgToCallClients(
    PTCALL          ptCall,
    PTCALLCLIENT    ptCallClientToExclude,
    DWORD           Msg,
/*
    ULONG_PTR       Param1,
    ULONG_PTR       Param2,
    ULONG_PTR       Param3 */
    DWORD           Param1,
    DWORD           Param2,
    DWORD           Param3

    )
{
    DWORD           i;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    ASYNCEVENTMSG   msg[2];


    if (GetCallClientListFromCall (ptCall, &pClientList) != 0)
    {
        return;
    }

    msg->TotalSize          = sizeof (ASYNCEVENTMSG) + sizeof (HCALLHUB);
    msg->fnPostProcessProcHandle = 0;
    msg->Msg                = Msg;
    msg->Param1             = Param1;
    msg->Param2             = Param2;
    msg->Param3             = Param3;

    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        try
        {
            PTCLIENT        ptClient;
            PTCALLCLIENT    ptCallClient = pClientList->aEntries[i];
            PTLINECLIENT    ptLineClient = ptCallClient->ptLineClient;


            if (ptCallClient == ptCallClientToExclude)
            {
                continue;
            }

            if (Msg == LINE_MONITORDIGITS)
            {
                if ((ptCallClient->dwMonitorDigitModes & Param2) == 0)
                {
                    continue;
                }
            }
            else if (Msg == LINE_MONITORMEDIA)
            {
                // ULONG_PTR   mediaModes = Param1;
                DWORD   mediaModes = Param1;


                //
                // Munge the media modes so we don't pass unexpected flags
                // to old apps
                //

                if (ptLineClient->dwAPIVersion == TAPI_VERSION1_0)
                {
                    if ((mediaModes & ~AllMediaModes1_0))
                    {
                        mediaModes = (mediaModes & AllMediaModes1_0) |
                            LINEMEDIAMODE_UNKNOWN;
                    }
                }

                if (ptCallClient->dwMonitorMediaModes & mediaModes)
                {
                    msg->Param1 = mediaModes;
                }
                else
                {
                    continue;
                }
            }
            else if (Msg == LINE_MONITORTONE)
            {
                if (!ptCallClient->bMonitoringTones)
                {
                    continue;
                }
            }
            else if (FMsgDisabled(
                ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                ptCallClient->adwEventSubMasks,
                (DWORD) Msg,
                (DWORD) Param1
                ))
            {
                continue;
            }

            msg->InitContext = ptLineClient->ptLineApp->InitContext;
            msg->hDevice     = ptCallClient->hCall;
            msg->OpenContext = ptLineClient->OpenContext;

            //
            // Indicate the hRemoteLine in p4 to make life easier for remotesp
            //

            msg->Param4 = ptLineClient->hRemoteLine;

            *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                        (ptCallClient->ptCallHubClient)?
                            ptCallClient->ptCallHubClient->hCallHub : 
                            (HCALLHUB)0;

            ptClient = ptLineClient->ptClient;

            if (ptCallClient->dwKey == TCALLCLIENT_KEY)
            {
                WriteEventBuffer (ptClient, msg);
            }
        }
        myexcept
        {
            // just continue
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }
}


void
PASCAL
SendBufferMsgToCallClients(
    PTCALL          ptCall,
    PTCALLCLIENT    ptCallClientToExclude,
    DWORD           Msg,
    DWORD           Param1,
    DWORD           Size,
    LPBYTE          pBuffer
    )
{
    DWORD           i;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    PASYNCEVENTMSG  pmsg;


    DWORD           dwTotalSize =
                        (sizeof (ASYNCEVENTMSG) + (DWORD) Size +
                            TALIGN_COUNT) & TALIGN_MASK;

    if (!(pmsg = (ASYNCEVENTMSG *) ServerAlloc (dwTotalSize)))
    {
        return;
    }

    if (GetCallClientListFromCall (ptCall, &pClientList) != 0)
    {
        ServerFree( pmsg );
        return;
    }

    pmsg->TotalSize        = dwTotalSize;
    pmsg->fnPostProcessProcHandle = 0;
    pmsg->Msg              = Msg;
    pmsg->Param2           = Size;
    pmsg->Param3           = 0;

    CopyMemory ((PBYTE)(pmsg+1), pBuffer, Size);

    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        try
        {
            PTCLIENT        ptClient;
            PTCALLCLIENT    ptCallClient = pClientList->aEntries[i];
            PTLINECLIENT    ptLineClient = ptCallClient->ptLineClient;


            if (ptCallClient == ptCallClientToExclude)
            {
                continue;
            }

            if (FMsgDisabled (
                ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                ptCallClient->adwEventSubMasks,
                (DWORD) Msg,
                (DWORD) Param1
                ))
            {
                continue;
            }

            pmsg->Param1       = (DWORD) ptCallClient->hCall;
            pmsg->InitContext  = ptLineClient->ptLineApp->InitContext;
            pmsg->hDevice        = (DWORD) ptLineClient->hLine;
            pmsg->OpenContext = ptLineClient->OpenContext;


            //
            // Indicate the hRemoteLine in p4 to make life easier for remotesp
            //

            pmsg->Param4 = ptLineClient->hRemoteLine;

            ptClient = ptCallClient->ptClient;

            if (ptCallClient->dwKey == TCALLCLIENT_KEY)
            {
                WriteEventBuffer (ptClient, pmsg);
            }
        }
        myexcept
        {
            // just continue
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }

    ServerFree( pmsg );
}


void
PASCAL
SendAMsgToAllLineApps(
    DWORD       dwWantVersion,
    DWORD       Msg,
    DWORD       Param1, // ULONG_PTR   Param1,
    DWORD       Param2, // ULONG_PTR   Param2
    DWORD       Param3  // ULONG_PTR   Param3
    )
{
    DWORD           i, j;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    ASYNCEVENTMSG   lineMsg;


    if (GetClientList (FALSE, &pClientList) != 0)
    {
        return;
    }

    ZeroMemory (&lineMsg, sizeof (ASYNCEVENTMSG));

    lineMsg.TotalSize = sizeof (ASYNCEVENTMSG);
    lineMsg.Msg       = Msg;
    lineMsg.Param2    = Param2;
    lineMsg.Param3    = Param3;


    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        PTCLIENT        ptClient = (PTCLIENT) pClientList->aEntries[i];
        TPOINTERLIST    xxxAppList, *pXxxAppList = &xxxAppList;

        if (NULL == ptClient)
        {
            break;
        }

        lineMsg.Param1    = Param1;

        //
        //  For LINE_REMOVE, need to remap the dwDeviceID(Param1)
        //
        if (Msg == LINE_REMOVE)
        {
            DWORD           dwNumDevices;
            LPDWORD         lpdwDevices;
            
            if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
                WaitForExclusiveClientAccess (ptClient))
            {
                if (!IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
                {
                    dwNumDevices = ptClient->dwLineDevices;
                    lpdwDevices = ptClient->pLineDevices;
                    for (j = 0; j < dwNumDevices; ++j, ++lpdwDevices)
                    {
                        if (*lpdwDevices == Param1)
                        {
                            break;
                        }
                    }
                    if (j >= dwNumDevices)
                    {
                        //  Not found in the device map, ignore
                        UNLOCKTCLIENT (ptClient);
                        continue;
                    }
                    else
                    {
                        lineMsg.Param1 = j;
                    }
                }
                UNLOCKTCLIENT (ptClient);
            }
        }
        else if (Msg == LINE_CREATE)
        {
            if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
                WaitForExclusiveClientAccess (ptClient))
            {
                BOOL bAdmin = IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR);
                UNLOCKTCLIENT (ptClient);
                if (!bAdmin)
                {
                    continue;
                }
            }
        }


        if (GetLineAppListFromClient (ptClient, &pXxxAppList) == 0)
        {
            for (j = 0; j < pXxxAppList->dwNumUsedEntries; j++)
            {
                PTLINEAPP ptLineApp = (PTLINEAPP) pXxxAppList->aEntries[j];

                try
                {
                    lineMsg.InitContext = ptLineApp->InitContext;

                    if ((ptLineApp->dwKey == TLINEAPP_KEY)  &&
                          ((dwWantVersion == 0) ||
                             (ptLineApp->dwAPIVersion == dwWantVersion)  ||
                             ((dwWantVersion & 0x80000000)   &&
                                 (ptLineApp->dwAPIVersion >=
                                     (dwWantVersion & 0x7fffffff)))))
                    {
                        if (!FMsgDisabled (
                            ptLineApp->dwAPIVersion,
                            ptLineApp->adwEventSubMasks,
                            (DWORD) Msg,
                            (DWORD) Param1
                            ))
                        {
                            WriteEventBuffer (ptClient, &lineMsg);
                        }
                    }
                }
                myexcept
                {
                    // just continue
                }
            }

            if (pXxxAppList != &xxxAppList)
            {
                ServerFree (pXxxAppList);
            }
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }
}


void
PASCAL
SendAMsgToAllPhoneApps(
    DWORD       dwWantVersion,
    DWORD       Msg,
    DWORD       Param1,     // ULONG_PTR   Param1,
    DWORD       Param2,     // ULONG_PTR   Param2,
    DWORD       Param3      // ULONG_PTR   Param3
    )
{
    DWORD           i, j;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    ASYNCEVENTMSG   phoneMsg;


    if (GetClientList (FALSE, &pClientList) != 0)
    {
        return;
    }

    ZeroMemory (&phoneMsg, sizeof (ASYNCEVENTMSG));

    phoneMsg.TotalSize = sizeof (ASYNCEVENTMSG);
    phoneMsg.Msg       = Msg;
    phoneMsg.Param2    = Param2;
    phoneMsg.Param3    = Param3;

    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        PTCLIENT        ptClient = (PTCLIENT) pClientList->aEntries[i];
        TPOINTERLIST    xxxAppList, *pXxxAppList = &xxxAppList;

        if (NULL == ptClient)
        {
            break;
        }

        phoneMsg.Param1    = Param1;

        //
        //  For PHONE_REMOVE, need to remap the dwDeviceID(Param1)
        //
        if (Msg == PHONE_REMOVE)
        {
            DWORD           dwNumDevices;
            LPDWORD         lpdwDevices;
            
            if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
                WaitForExclusiveClientAccess (ptClient))
            {
                if (!IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
                {
                    dwNumDevices = ptClient->dwPhoneDevices;
                    lpdwDevices = ptClient->pPhoneDevices;
                    for (j = 0; j < dwNumDevices; ++j, ++lpdwDevices)
                    {
                        if (*lpdwDevices == Param1)
                        {
                            break;
                        }
                    }
                    if (j >= dwNumDevices)
                    {
                        //  Not found in the device map, ignore
                        UNLOCKTCLIENT (ptClient);
                        continue;
                    }
                    else
                    {
                        phoneMsg.Param1 = j;
                    }
                }
                UNLOCKTCLIENT (ptClient);
            }
        }
        else if (Msg == PHONE_CREATE)
        {
            if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
                WaitForExclusiveClientAccess (ptClient))
            {
                BOOL bAdmin = IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR);
                UNLOCKTCLIENT (ptClient);
                if (!bAdmin)
                {
                    continue;
                }
            }
        }

        if (GetPhoneAppListFromClient (ptClient, &pXxxAppList) == 0)
        {
            for (j = 0; j < pXxxAppList->dwNumUsedEntries; j++)
            {
                PTPHONEAPP  ptPhoneApp = (PTPHONEAPP) pXxxAppList->aEntries[j];

                try
                {
                    phoneMsg.InitContext = ptPhoneApp->InitContext;

                    if ((ptPhoneApp->dwKey == TPHONEAPP_KEY)  &&
                        ((dwWantVersion == 0) ||
                            (ptPhoneApp->dwAPIVersion == dwWantVersion) ||
                            ((dwWantVersion & 0x80000000)  &&
                                (ptPhoneApp->dwAPIVersion >=
                                   (dwWantVersion & 0x7fffffff)))))
                    {
                        if (!FMsgDisabled (
                            ptPhoneApp->dwAPIVersion,
                            ptPhoneApp->adwEventSubMasks,
                            (DWORD) Msg,
                            (DWORD) Param1
                            ))
                        {
                            WriteEventBuffer (ptClient, &phoneMsg);
                        }
                    }
                }
                myexcept
                {
                    // just continue
                }
            }

            if (pXxxAppList != &xxxAppList)
            {
                ServerFree (pXxxAppList);
            }
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }
}


void
PASCAL
SendReinitMsgToAllXxxApps(
    void
    )
{
    TapiGlobals.dwFlags |= TAPIGLOBALS_REINIT;

    SendAMsgToAllLineApps (0, LINE_LINEDEVSTATE, LINEDEVSTATE_REINIT, 0, 0);

    SendAMsgToAllPhoneApps (0, PHONE_STATE, PHONESTATE_REINIT, 0, 0);
}


void
PASCAL
SendBufferMsgToLineClients(
    PTLINE          ptLine,
    PTLINECLIENT    ptLineClientToExclude,
    DWORD           dwMsg,
    DWORD           dwParam1,   // ULONG_PTR       dwParam1,
    DWORD           dwSize,     // ULONG_PTR       dwSize,
    LPBYTE          pBuffer
    )
{
    DWORD           i;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    ASYNCEVENTMSG * pmsg;
    DWORD           dwTotalSize = (sizeof (ASYNCEVENTMSG) +
                        (DWORD) dwSize + TALIGN_COUNT) & TALIGN_MASK;


    if (!(pmsg = (ASYNCEVENTMSG *) ServerAlloc (dwTotalSize)))
    {
        LOG((TL_ERROR, "SendBufferMsgToLineClients - Cannot allocate memory for message"));
        return;
    }

    if (GetLineClientListFromLine (ptLine, &pClientList) != 0)
    {
        ServerFree (pmsg);
        return;
    }

    pmsg->TotalSize          = dwTotalSize;
    pmsg->fnPostProcessProcHandle = 0;
    pmsg->Msg                = dwMsg;
    pmsg->Param1             = dwParam1;
    pmsg->Param2             = dwSize;
    pmsg->Param3             = 0;
    pmsg->Param4             = 0; // remotesp chks this on LINE_DEVSPEC(FEATURE)

    CopyMemory ((PBYTE)(pmsg+1), pBuffer, dwSize);

    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        try
        {
            PTCLIENT     ptClient;
            PTLINECLIENT ptLineClient = pClientList->aEntries[i];


            if (ptLineClient == ptLineClientToExclude)
            {
                continue;
            }

            if (FMsgDisabled (
                ptLineClient->ptLineApp->dwAPIVersion,
                ptLineClient->adwEventSubMasks,
                dwMsg,
                (DWORD) dwParam1
                ))
            {
                continue;
            }

            pmsg->InitContext = ptLineClient->ptLineApp->InitContext;
            pmsg->hDevice     = ptLineClient->hRemoteLine;
            pmsg->OpenContext = ptLineClient->OpenContext;

            ptClient = ptLineClient->ptClient;

            if (ptLineClient->dwKey == TLINECLIENT_KEY)
            {
                WriteEventBuffer (ptClient, pmsg);
            }
        }
        myexcept
        {
            // just continue
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }

    ServerFree (pmsg);
}


void
PASCAL
SendMsgToLineClients(
    PTLINE          ptLine,
    PTLINECLIENT    ptLineClientToExclude,
    DWORD           Msg,
    DWORD           Param1, // ULONG_PTR       Param1,
    DWORD           Param2, // ULONG_PTR       Param2,
    DWORD           Param3  // ULONG_PTR       Param3
    )
{
    DWORD           i;
    TPOINTERLIST    clientList, *pClientList = &clientList;
    ASYNCEVENTMSG   msg;


    LOG((TL_TRACE, "SendMsgToLineClients - enter"));
    if (Msg == LINE_LINEDEVSTATE  &&  Param1 & LINEDEVSTATE_REINIT)
    {
        SendReinitMsgToAllXxxApps();

        if (Param1 == LINEDEVSTATE_REINIT)
        {
            return;
        }
        else
        {
            Param1 &= ~LINEDEVSTATE_REINIT;
        }
    }


    if (GetLineClientListFromLine (ptLine, &pClientList) != 0)
    {
        return;
    }

    msg.TotalSize          = sizeof (ASYNCEVENTMSG);
    msg.fnPostProcessProcHandle = 0;
    msg.Msg                = Msg;
    msg.Param1             = Param1;
    msg.Param2             = Param2;
    msg.Param3             = Param3;
    msg.Param4             = 0; // remotesp chks this on LINE_DEVSPEC(FEATURE)

    LOG((TL_INFO, "SendMsgToLineClients - number of Clients:%u", pClientList->dwNumUsedEntries));
    for (i = 0; i < pClientList->dwNumUsedEntries; i++)
    {
        try
        {
            PTCLIENT     ptClient;
            PTLINECLIENT ptLineClient = pClientList->aEntries[i];

            if (ptLineClient == ptLineClientToExclude)
            {
                continue;
            }
            LOG((TL_INFO, "SendMsgToLineClients: Msg:%x -- Param1:%x", Msg, Param1));
            if (FMsgDisabled (
                ptLineClient->ptLineApp->dwAPIVersion,
                ptLineClient->adwEventSubMasks,
                (DWORD) Msg,
                (DWORD) Param1
                ))
            {
                continue;
            }

            if (Msg == LINE_ADDRESSSTATE)
            {
                DWORD   addressStates = Param2;     // ULONG_PTR   addressStates = Param2;


                //
                // Munge the state flags so we don't pass
                // unexpected flags to old apps
                //

                switch (ptLineClient->dwAPIVersion)
                {
                case TAPI_VERSION1_0:

                    addressStates &= AllAddressStates1_0;
                    break;

                //case TAPI_VERSION1_4:
                //case TAPI_VERSION2_0:
                //case TAPI_VERSION2_1:
                //case TAPI_VERSION2_2:
                //case TAPI_VERSION_CURRENT:
                default:

                    addressStates &= AllAddressStates1_4;
                    break;
                }

                if ((addressStates &= ptLineClient->dwAddressStates))
                {
                    msg.Param2 = addressStates;
                }
                else
                {
                    continue;
                }

                if ((Param2 & LINEADDRESSSTATE_CAPSCHANGE))
                {
                }
            }
            else if (Msg == LINE_LINEDEVSTATE)
            {
                DWORD   lineStates = Param1;    // ULONG_PTR   lineStates = Param1;


                //
                // Munge the state flags so we don't pass unexpected flags
                // to old apps
                //

                switch (ptLineClient->dwAPIVersion)
                {
                case TAPI_VERSION1_0:

                    lineStates &= AllLineStates1_0;
                    break;

                default:    // case TAPI_VERSION1_4:
                            // case TAPI_VERSION_CURRENT:

                    lineStates &= AllLineStates1_4;
                    break;
                }

                if ((lineStates &= ptLineClient->dwLineStates))
                {
                    msg.Param1 = lineStates;
                }
                else
                {
                    continue;
                }

                if ((Param1 & (LINEDEVSTATE_CAPSCHANGE |
                        LINEDEVSTATE_TRANSLATECHANGE)))
                {
                }

            }
            else if (Msg == LINE_PROXYSTATUS)
            {
                //
                // Don't pass this message to older apps
                //

                if (ptLineClient->dwAPIVersion < TAPI_VERSION2_2)
                {
                    continue;
                }
            }

            msg.InitContext = ptLineClient->ptLineApp->InitContext;
            msg.hDevice     = ptLineClient->hRemoteLine;
            msg.OpenContext = ptLineClient->OpenContext;

            ptClient = ptLineClient->ptClient;

            if (ptLineClient->dwKey == TLINECLIENT_KEY)
            {
                WriteEventBuffer (ptClient, &msg);
            }
        }
        myexcept
        {
            // just continue
        }
    }

    if (pClientList != &clientList)
    {
        ServerFree (pClientList);
    }
}


LONG
PASCAL
GetCallIDs(
    PTCALL  ptCall
    )
{
    //
    // NOTE: Caller must already have exclusive access to the
    //       tCall when invoking this function.  On return they
    //       will still have exclusive access to the tCall.
    //
    //       Also, it is assumed that only the LINE_CALLSTATE
    //       msg handler & MakeCallPostProcess-style functions
    //       will call this function.  Otherwise, the code that
    //       resets the tCall's dwKey down below will need to
    //       be changed.
    //

    //
    // We don't want to hold the lock while we call the provider,
    // since this request might take a while (possible ring
    // transitions, etc). But we also don't want another thread
    // to destroy this tCall in the meantime (because we didn't
    // allow for that in NT 4.0, and because it makes life more
    // difficult for the calling function).  So, we'll reset the
    // tCall's dwKey value to ZOMBIE, thereby causing any other
    // thread(s) that is waiting to destroy this call to effectively
    // wait/spin, and we'll then restore the dwKey value down below
    // after we reacquire the lock. (Since this func is only called
    // by MakeCallPostProcess-style functions & the LINE_CALLSTATE
    // handler [on the first callstate msg received on an incoming
    // call], the only way another thread would be destroying this
    // call is if the line is being closed, either by an app or as
    // the result of a LINE_CLOSE, and DestroytLine spins until
    // it has destroyed all tCall's in the tLine's list.)

    DWORD       dwNumAddresses = ptCall->ptLine->dwNumAddresses, dwSavedKey;
    PTPROVIDER  ptProvider = ptCall->ptProvider;

    if (ptProvider->apfn[SP_LINEGETCALLINFO] == NULL)
    {
        return LINEERR_OPERATIONUNAVAIL;
    }

    dwSavedKey = ptCall->dwKey;
    ptCall->dwKey = TZOMBIECALL_KEY;

    UNLOCKTCALL (ptCall);

    if (ptProvider->apfn[SP_LINEGETCALLIDS])
    {
        CallSP4(
            ptProvider->apfn[SP_LINEGETCALLIDS],
            "lineGetCalIDs",
            SP_FUNC_SYNC,
            (ULONG_PTR) ptCall->hdCall,
            (ULONG_PTR) &ptCall->dwAddressID,
            (ULONG_PTR) &ptCall->dwCallID,
            (ULONG_PTR) &ptCall->dwRelatedCallID
            );
    }
    else
    {
        DWORD           dwSPIVersion, dwFixedSizeSP;
        LINECALLINFO    callInfo;


        //
        // Determine the fixed size of the structure expected by the SP
        //

        dwSPIVersion = ((PTLINE) ptCall->ptLine)->dwSPIVersion;

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeSP = 296;    // 69 * sizeof(DWORD) + sizeof (HLINE)
                                    //     + sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_2:

            dwFixedSizeSP = 324;    // 76 * sizeof(DWORD) + sizeof (HLINE)
                                    //     + sizeof (LINEDIALPARAMS)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINECALLINFO);
            break;
        }

        InitTapiStruct (&callInfo, dwFixedSizeSP, dwFixedSizeSP, TRUE);

        CallSP2(
            ptProvider->apfn[SP_LINEGETCALLINFO],
            "lineGetCallInfo",
            SP_FUNC_SYNC,
            (ULONG_PTR) ptCall->hdCall,
            (ULONG_PTR) &callInfo
            );

        ptCall->dwAddressID     = callInfo.dwAddressID;
        ptCall->dwCallID        = callInfo.dwCallID;
        ptCall->dwRelatedCallID = callInfo.dwRelatedCallID;
    }


    //
    // Reacquire the call lock, restore the dwKey value, & fill
    // in the address id
    //

    LOCKTCALL (ptCall);
    ptCall->dwKey = dwSavedKey;

    return 0;
}


VOID
PASCAL
AcquireHashTableReaderLock(
    PTPROVIDER  ptProvider
    )
{
    EnterCriticalSection (&ptProvider->HashTableCritSec);
    InterlockedIncrement (&ptProvider->lHashTableReaderCount);
    LeaveCriticalSection (&ptProvider->HashTableCritSec);
}


VOID
PASCAL
AcquireHashTableWriterLock(
    PTPROVIDER  ptProvider
    )
{
    EnterCriticalSection (&ptProvider->HashTableCritSec);

    if (InterlockedDecrement (&ptProvider->lHashTableReaderCount) >= 0)
    {
        WaitForSingleObject (ptProvider->hHashTableReaderEvent, INFINITE);
    }
}


VOID
PASCAL
ReleaseHashTableReaderLock(
    PTPROVIDER  ptProvider
    )
{
    if (InterlockedDecrement (&ptProvider->lHashTableReaderCount) < 0)
    {
        SetEvent (ptProvider->hHashTableReaderEvent);
    }
}


VOID
PASCAL
ReleaseHashTableWriterLock(
    PTPROVIDER  ptProvider
    )
{
    InterlockedIncrement (&ptProvider->lHashTableReaderCount);
    LeaveCriticalSection (&ptProvider->HashTableCritSec);
}


PTHASHTABLEENTRY
PASCAL
AcquireHashTableEntryLock(
    PTPROVIDER  ptProvider,
    DWORD       dwCallHubID
    )
{
    LONG                lPreviousCookie;
    PTHASHTABLEENTRY    pEntry;


    AcquireHashTableReaderLock (ptProvider);

    pEntry = ptProvider->pHashTable +
        (dwCallHubID % ptProvider->dwNumHashTableEntries);

    do
    {
        lPreviousCookie = InterlockedExchange (&pEntry->lCookie, 1);

    } while (lPreviousCookie != 0);

    return pEntry;
}


VOID
PASCAL
ReleaseHashTableEntryLock(
    PTPROVIDER          ptProvider,
    PTHASHTABLEENTRY    pEntry
    )
{
    InterlockedExchange (&pEntry->lCookie, 0);

    ReleaseHashTableReaderLock (ptProvider);
}


VOID
PASCAL
FreeHashTable(
    PTHASHTABLEENTRY    pHashTable,
    DWORD               dwNumHashTableEntries,
    DWORD               dwNumDynamicHashTableEntries
    )
{
    //
    // Walk thru hash table to find any dynamic entries that need to
    // be freed.  We compare against both dwNumDynamicHashTableEntries
    // and dwNumHashTableEntries in case something went wrong somewhere.
    //
    // Then free the table itself & return
    //

    DWORD               i;
    PTHASHTABLEENTRY    pEntry, pEntry2;


    for(
        i = 0, pEntry = pHashTable;
        dwNumDynamicHashTableEntries  &&  i < dwNumHashTableEntries;
        i++, pEntry++
        )
    {
        while (pEntry->pNext)
        {
            dwNumDynamicHashTableEntries--;

            pEntry2 = pEntry->pNext->pNext;

            ServerFree (pEntry->pNext);

            pEntry->pNext = pEntry2;
        }
    }

    ServerFree (pHashTable);
}


PTHASHTABLEENTRY
PASCAL
FindDynamicHashTableEntry(
    PTHASHTABLEENTRY    pEntry,
    DWORD               dwCallHubID
    )
{
    //
    // Note that the pEntry passed to us is static, so no need to check that
    //

    while (pEntry->pNext)
    {
        if (pEntry->pNext->dwCallHubID == dwCallHubID)
        {
            break;
        }

        pEntry = pEntry->pNext;
    }

    return pEntry->pNext;
}


VOID
PASCAL
RemoveDynamicHashTableEntry(
    PTPROVIDER          ptProvider,
    PTHASHTABLEENTRY    pStaticEntry,
    PTHASHTABLEENTRY    pDynamicEntry
    )
{
    while (pStaticEntry->pNext != pDynamicEntry)
    {
        pStaticEntry = pStaticEntry->pNext;
    }

    pStaticEntry->pNext = pDynamicEntry->pNext;

    ServerFree (pDynamicEntry);

    InterlockedDecrement ((LPLONG) &ptProvider->dwNumDynamicHashTableEntries);
}


DWORD
PASCAL
GetNumDynamicHashTableEntries(
    PTHASHTABLEENTRY    pEntry
    )
{
    //
    // Note that the pEntry passed to us is static, so no need to count that
    //

    DWORD  i;


    for (i = 0; (pEntry = pEntry->pNext); i++);

    return i;
}


//
//  SendNewCallHubEvent
//
//  Utility function used by DoCallHubHashing & UpdateCallHubHashing
//  to send LINE_NEWCALLHUB event if not sent yet
//

LONG
PASCAL
SendNewCallHubEvent (
    PTCALL                  ptCall,
    PTHASHTABLEENTRY        pTargetEntry
    )
{
    //
    // For each tCallClient see if tLineClient has tracking
    // enabled, and if so then see if there is already an
    // associated (via common tLineApp) tCallHubClient - if
    // not then create one & notify the app
    //

    DWORD   i;
    BOOL    bExistingCallHubClients = 
        (pTargetEntry->ptCallHubClients != NULL);
    TPOINTERLIST        fastCallClientList, *ptCallClientList;

    if (ptCall->ptLine->dwNumCallHubTrackers != 0)
    {
        ptCallClientList = &fastCallClientList;
        if (GetCallClientListFromCall (ptCall, &ptCallClientList) != 0)
        {
            ptCallClientList = NULL;
        }
    }
    else
    {
        ptCallClientList = NULL;
    }

    if (ptCallClientList == NULL || pTargetEntry == NULL)
    {
        return 0;
    }

    for (i = 0; i < ptCallClientList->dwNumUsedEntries; i++)
    {
        PTLINEAPP       ptLineApp;
        PTCALLCLIENT    ptCallClient = ptCallClientList->aEntries[i];
        ASYNCEVENTMSG   msg;
        PTCALLHUBCLIENT ptCallHubClient;


        if (ptCallClient->ptLineClient->dwCurrentTracking ||
            bExistingCallHubClients)
        {
            ptLineApp = ptCallClient->ptLineClient->ptLineApp;
            ptCallHubClient = pTargetEntry->ptCallHubClients;

            while (ptCallHubClient)
            {
                if (ptCallHubClient->ptLineApp == ptLineApp)
                {
                    break;
                }

                ptCallHubClient = ptCallHubClient->pNext;
            }

            if (ptCallClient->ptLineClient->dwCurrentTracking  &&
                !ptCallHubClient &&
                (!FMsgDisabled (
                    ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                    ptCallClient->adwEventSubMasks,
                    LINE_APPNEWCALLHUB,
                    0)))
            {
                DWORD hptClientHandle = 0;


                ptCallHubClient = ServerAlloc (sizeof(TCALLHUBCLIENT));

                if (!ptCallHubClient)
                {
                    i = ptCallClientList->dwNumUsedEntries;
                    continue;
                }

                ptCallHubClient->hCallHub = (DWORD) NewObject(
                    ghHandleTable,
                    ptCallHubClient,
                    NULL
                    );

                if (!ptCallHubClient->hCallHub)
                {
                    ServerFree(ptCallHubClient);
                    i = ptCallClientList->dwNumUsedEntries;
                    continue;
                }

                ptCallHubClient->dwKey = TCALLHUBCLIENT_KEY;
                ptCallHubClient->ptClient = ptCallClient->ptClient;
                ptCallHubClient->ptProvider = ptCall->ptProvider;
                ptCallHubClient->dwCallHubID = ptCall->dwCallID;
                ptCallHubClient->ptLineApp = ptLineApp;
                ptCallHubClient->pNext = pTargetEntry->ptCallHubClients;
                pTargetEntry->ptCallHubClients = ptCallHubClient;


                //
                // Queue a msg to alert the app of the new call
                // hub.  We do this rather than sending a msg
                // directly from here to make sure the app gets
                // the LINE_REPLY and/or APPNEWCALL msgs first
                // (i.e. before calling lineGetHubRelatedCalls
                // in response to LINE_APPNEWCALLHUB)
                //

                LineEventProcSP(
                    (HTAPILINE) 0,
                    (HTAPICALL) 0,
                    LINE_APPNEWCALLHUB,
                    ptCallHubClient->hCallHub,
                    ptLineApp->InitContext,
                    (ULONG_PTR)ptLineApp->ptClient
                    );
            }

            ptCallClient->ptCallHubClient = ptCallHubClient;
        }
    }

    return 0;
}

//
//  Per Bug 7591
//      There might be a owner ship transferring of a call after
//  it has been created. The new owner will not get the new call
//  hub event since such event is only generated while the call
//  is created. UpdateCallHubHashing goes through the call client
//  if any of the client did not receive the LINE_NEWCALLHUB event
//  it will send it one.
//

LONG
PASCAL
UpdateCallHubHashing (
    PTCALL      ptCall
    )
{
    if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
    {
        PTHASHTABLEENTRY    pStaticEntry, pTargetEntry;
        
        pStaticEntry = AcquireHashTableEntryLock(
            ptCall->ptProvider,
            ptCall->dwCallID
            );
        if (pStaticEntry->dwCallHubID == ptCall->dwCallID)
        {
            pTargetEntry = pStaticEntry;
        }
        else
        {
            pTargetEntry = FindDynamicHashTableEntry(
                pStaticEntry,
                ptCall->dwCallID
                );
        }
        if (pTargetEntry)
        {
            SendNewCallHubEvent (ptCall, pTargetEntry);
        }
        ReleaseHashTableEntryLock (ptCall->ptProvider, pStaticEntry);
        UNLOCKTCALL (ptCall);
    }

    return 0;
}

LONG
PASCAL
DoCallHubHashing(
    PTCALL  ptCall,
    DWORD   dwPreviousCallID
    )
{
    //
    // Assumes caller has exclusive access to the tCall
    //

    DWORD               i;
    PTPROVIDER          ptProvider = ptCall->ptProvider;
    PTHASHTABLEENTRY    pStaticEntry, pDynamicEntry;


    if (ptCall->dwCallID == dwPreviousCallID)
    {
        return 0;
    }

    if (dwPreviousCallID != 0)
    {
        //
        // Remove tCall from call hub hash table (based on
        // previous call id).  If this is the last tCall in
        // that hash table entry then destroy any associated
        // tCallHubClients and alert apps.
        //
        // Note that (when freeing tCallHubClients) we call
        // LineEventProcSP() to queue the CALLHUBCLOSE msgs
        // rather than sending them directly via
        // WriteEventBuffer() because we already own the
        // tCall lock and we don't want to entertain the
        // possibility of deadlock by grabbing other locks.
        //

        PTCALLHUBCLIENT ptCallHubClient = NULL,  pNext;


        pStaticEntry = AcquireHashTableEntryLock(
            ptProvider,
            dwPreviousCallID
            );

        if (pStaticEntry->dwCallHubID == dwPreviousCallID)
        {
            RemoveEntryList (&ptCall->CallHubList);

            ptCall->CallHubList.Flink = NULL;
            ptCall->CallHubList.Blink = NULL;

            if (IsListEmpty (&pStaticEntry->CallHubList))
            {
                ptCallHubClient = pStaticEntry->ptCallHubClients;
                pStaticEntry->ptCallHubClients = NULL;
                pStaticEntry->dwCallHubID = 0;
            }
        }
        else if ((pDynamicEntry = FindDynamicHashTableEntry(
                        pStaticEntry,
                        dwPreviousCallID
                        )))
        {
            RemoveEntryList (&ptCall->CallHubList);

            ptCall->CallHubList.Flink = NULL;
            ptCall->CallHubList.Blink = NULL;

            if (IsListEmpty (&pDynamicEntry->CallHubList))
            {
                ptCallHubClient = pDynamicEntry->ptCallHubClients;

                RemoveDynamicHashTableEntry(
                    ptProvider,
                    pStaticEntry,
                    pDynamicEntry
                    );
            }
        }
        else
        {
            // TODO assert
        }

        ReleaseHashTableEntryLock (ptProvider, pStaticEntry);

        if (ptCallHubClient)
        {
            while (ptCallHubClient)
            {
                BOOL        bSendMsg = FALSE;
                DWORD       param2 = 0;
                DWORD       param3 = 0; // ULONG_PTR   param2, param3;
                PTLINEAPP   ptLineApp;


                try
                {
                    ptLineApp = ptCallHubClient->ptLineApp;

                    param2 = ptLineApp->InitContext;
                    
                    if (ptLineApp->dwKey == TLINEAPP_KEY &&
                        (!FMsgDisabled(
                            ptLineApp->dwAPIVersion,
                            ptLineApp->adwEventSubMasks,
                            LINE_CALLHUBCLOSE,
                            0
                            )))
                    {
                        bSendMsg = TRUE;
                    }
                }
                except (EXCEPTION_EXECUTE_HANDLER)
                {
                    // tLineApp is gone, just fall through
                }

                if (bSendMsg)
                {
                    LineEventProcSP(
                        (HTAPILINE) UIntToPtr(ptCallHubClient->hCallHub), // "random" seed for MP
                        (HTAPICALL) 0,
                        LINE_CALLHUBCLOSE,
                        ptCallHubClient->hCallHub,
                        param2,
                        (ULONG_PTR)ptLineApp->ptClient
                        );
                }

                pNext = ptCallHubClient->pNext;
                ptCallHubClient->dwKey = INVAL_KEY;

                DereferenceObject(
                    ghHandleTable,
                    ptCallHubClient->hCallHub,
                    1
                    );

                ptCallHubClient = pNext;
            }
        }
    }

    if (ptCall->dwCallID != 0 &&
        ptCall->CallHubList.Flink == NULL &&
        ptCall->CallHubList.Blink == NULL)
    {
        //
        // If at least one tLineClient has call hub tracking enabled
        // then retrieve the list of tCallClients before we acquire
        // exclusive access to the hash table
        //

        DWORD               dwOldNumHashTableEntries,
                            dwOldNumDynamicHashTableEntries;
        PTHASHTABLEENTRY    pOldHashTable = NULL, pTargetEntry;


        //
        // Insert tCall in call hub hash table (based on current call ID).
        //
        // In the event of collision, first check to see if there's an
        // existing dynamic entry with corresponding to the call ID.
        // If so, party on the dynamic entry.  Otherwise, try to create
        // a dynamic entry if we're still within the dynamic entry
        // threshholds.
        //
        // Finally, failing all the above, attempt to grow the hash table.
        //

acquireTableEntryLock:

        pStaticEntry = AcquireHashTableEntryLock(
            ptProvider,
            ptCall->dwCallID
            );

        if (pStaticEntry->dwCallHubID == ptCall->dwCallID)
        {
            //
            // Add tCall to list (static entry)
            //

            InsertTailList (&pStaticEntry->CallHubList, &ptCall->CallHubList);

            pTargetEntry = pStaticEntry;
        }
        else if(pStaticEntry->dwCallHubID == 0)
        {
            //
            //  Check to see if there is already a dynamic entry for this dwCallID,
            //  if so, use it
            //
        
            pTargetEntry = pStaticEntry->pNext;
            
            while (pTargetEntry && pTargetEntry->dwCallHubID != ptCall->dwCallID)
            {
                pTargetEntry = pTargetEntry->pNext;
            }
            if (pTargetEntry == NULL)
            {
                pTargetEntry = pStaticEntry;
                pTargetEntry->dwCallHubID = ptCall->dwCallID;
            }
            
            InsertTailList (&pTargetEntry->CallHubList, &ptCall->CallHubList);
        }
        else if ((pTargetEntry = FindDynamicHashTableEntry(
                        pStaticEntry,
                        ptCall->dwCallID
                        )))
        {
            //
            // Add tCall to list (existing dynamic entry)
            //

            InsertTailList (&pTargetEntry->CallHubList, &ptCall->CallHubList);
        }
        else if (InterlockedIncrement(
                    (LPLONG) &ptProvider->dwNumDynamicHashTableEntries
                    )

                    <  (LONG) GetMaxDynamicHashTableEntries(
                                   ptProvider->dwNumHashTableEntries
                                   )                                   &&

                 GetNumDynamicHashTableEntries (pStaticEntry)

                    < MAX_DYNAMIC_HASH_ENTRIES_PER_SLOT)
        {
            //
            // Add tCall to list (new dynamic entry)
            //

            if (!(pTargetEntry = ServerAlloc (sizeof (*pTargetEntry))))
            {
                //
                // Failed to allocate memory, so we'll reset the call
                // hub id for this call to zero so as not to confuse
                // things later
                //

                InterlockedDecrement(
                    (LPLONG) &ptProvider->dwNumDynamicHashTableEntries
                    );

                ReleaseHashTableEntryLock (ptProvider, pStaticEntry);
                ptCall->dwCallID = 0;

                return 0;
            }

            pTargetEntry->dwCallHubID = ptCall->dwCallID;

            InitializeListHead (&pTargetEntry->CallHubList);

            InsertTailList (&pTargetEntry->CallHubList, &ptCall->CallHubList);

            pTargetEntry->pNext = pStaticEntry->pNext;

            pStaticEntry->pNext = pTargetEntry;
        }
        else
        {
            //
            // Grow table
            //

            DWORD               dwNewNumHashTableEntries, dwMaxDynamicEntries,
                                dwNewNumDynamicHashTableEntries;
            PTHASHTABLEENTRY    pNewHashTable, pNewEntry, pEntry2, pEndOfTable;


            //
            // Decrement to compensate for the failed check above
            //

            InterlockedDecrement(
                &ptProvider->dwNumDynamicHashTableEntries
                );


            //
            // Grab the current number of hash table entries before
            // we release the entry lock, so we can compare with
            // that after the table writer lock is acquired.  (Another
            // thread might have grown table in time it took to acquire
            // table writer lock, in which case we want to jump up
            // top again.)
            //
            // The chances of another thread having released the
            // entry we collided with (or freed up a related dynamic
            // entry, etc) are fairly slim, so we won't bother checking
            // for that
            //

            {
                DWORD   dwNumHashTableEntries =
                            ptProvider->dwNumHashTableEntries;


                ReleaseHashTableEntryLock (ptProvider, pStaticEntry);

                AcquireHashTableWriterLock (ptProvider);

                if (dwNumHashTableEntries < ptProvider->dwNumHashTableEntries)
                {
                    ReleaseHashTableWriterLock (ptProvider);

                    goto acquireTableEntryLock;
                }

                //
                //  Because we released the lock & reaquired a lock, if
                //  another call with the same dwCallID got hashed in, we
                //  would got two hash table entries with the same dwCallHubID
                //  that eventually leads to memory corruption when grow
                //  the table again. So check for it
                //
                pTargetEntry = ptProvider->pHashTable +
                    (ptCall->dwCallID % ptProvider->dwNumHashTableEntries);
                while (pTargetEntry && pTargetEntry->dwCallHubID != ptCall->dwCallID)
                {
                    pTargetEntry = pTargetEntry->pNext;
                }
                if (pTargetEntry)
                {
                    //
                    //  Found such entry, go back & retry
                    //
                    ReleaseHashTableWriterLock (ptProvider);

                    goto acquireTableEntryLock;
                }
            }


            //
            // Ok, we really do need to grow the table.  Find the next
            // larger number of entries.
            //

            for(
                i = 0;
                ptProvider->dwNumHashTableEntries >= TapiPrimes[i] &&
                    TapiPrimes[i];
                i++
                );

alloc_new_hash_table:

            if (!(dwNewNumHashTableEntries = TapiPrimes[i]))
            {
                //
                // We won't attempt to grow the hash table any further,
                // so we'll reset the call hub id for this call to zero
                // so as not to confuse things later
                //

                ptCall->dwCallID = 0;
                ReleaseHashTableWriterLock (ptProvider);
                return 0;
            }

            pNewHashTable = ServerAlloc(
                dwNewNumHashTableEntries * sizeof (THASHTABLEENTRY)
                );

            if (!pNewHashTable)
            {
                //
                // Failed to allocate a new hash table, so we'll reset the
                // call hub id for this call to zero so as not to confuse
                // things later
                //

                ptCall->dwCallID = 0;
                ReleaseHashTableWriterLock (ptProvider);
                return 0;
            }


            //
            // Move all existing hash table entries to new table
            //

            pEndOfTable = ptProvider->pHashTable +
                ptProvider->dwNumHashTableEntries;

            dwNewNumDynamicHashTableEntries = 0;

            dwMaxDynamicEntries = GetMaxDynamicHashTableEntries(
                dwNewNumHashTableEntries
                );

            for(
                pStaticEntry = ptProvider->pHashTable;
                pStaticEntry != pEndOfTable;
                pStaticEntry++
                )
            {
                //
                // If this entry is in use somehow, check to see
                // if we need to start walking at the static entry
                // or the dynamic entry.  Else, simply continue.
                //

                if (pStaticEntry->dwCallHubID == 0)
                {
                    if (!pStaticEntry->pNext)
                    {
                        continue;
                    }

                    pEntry2 = pStaticEntry->pNext;
                }
                else
                {
                    pEntry2 = pStaticEntry;
                }

                while (pEntry2)
                {
                    pNewEntry = pNewHashTable +
                        (pEntry2->dwCallHubID % dwNewNumHashTableEntries);

                    if (pNewEntry->dwCallHubID != 0)
                    {
                        //
                        // Collision, try to add a dynamic entry
                        //

                        if (dwNewNumDynamicHashTableEntries  <
                                dwMaxDynamicEntries  &&

                            GetNumDynamicHashTableEntries (pNewEntry)
                                < MAX_DYNAMIC_HASH_ENTRIES_PER_SLOT)
                        {
                            if (!(pDynamicEntry = ServerAlloc(
                                    sizeof (*pDynamicEntry)
                                    )))
                            {
                                //
                                // Failed to allocate a new dynamic entry,
                                // so we'll reset the call hub id for this
                                // call to zero so as not to confuse things
                                // later
                                //

                                ptCall->dwCallID = 0;
                                ReleaseHashTableWriterLock (ptProvider);

                                FreeHashTable(
                                    pNewHashTable,
                                    dwNewNumHashTableEntries,
                                    dwNewNumDynamicHashTableEntries
                                    );

                                return 0;
                            }

                            pDynamicEntry->pNext = pNewEntry->pNext;
                            pNewEntry->pNext = pDynamicEntry;

                            pNewEntry = pDynamicEntry;

                            dwNewNumDynamicHashTableEntries++;
                        }
                        else
                        {
                            //
                            // Free new table and try for a larger one
                            //

                            FreeHashTable(
                                pNewHashTable,
                                dwNewNumHashTableEntries,
                                dwNewNumDynamicHashTableEntries
                                );

                            i++;

                            goto alloc_new_hash_table;
                        }
                    }

                    pNewEntry->dwCallHubID       = pEntry2->dwCallHubID;
                    pNewEntry->CallHubList.Flink = pEntry2->CallHubList.Flink;
                    pNewEntry->CallHubList.Blink = pEntry2->CallHubList.Blink;
                    pNewEntry->ptCallHubClients  = pEntry2->ptCallHubClients;

                    pEntry2 = pEntry2->pNext;
                }
            }


            //
            // Now init the new entry
            //

            pNewEntry = pNewHashTable +
                (ptCall->dwCallID % dwNewNumHashTableEntries);

            if (pNewEntry->dwCallHubID != 0)
            {
                //
                // Collision, try to add a dynamic entry.
                //
                // We intentionally ignore the dyna entry threshhold
                // checks, as they'd be overkill here.
                //

                if ((pDynamicEntry = ServerAlloc (sizeof(*pDynamicEntry))))
                {
                    pDynamicEntry->pNext = pNewEntry->pNext;
                    pNewEntry->pNext = pDynamicEntry;

                    pNewEntry = pDynamicEntry;

                    dwNewNumDynamicHashTableEntries++;
                }
                else
                {
                    FreeHashTable(
                        pNewHashTable,
                        dwNewNumHashTableEntries,
                        dwNewNumDynamicHashTableEntries
                        );

                    i++;

                    goto alloc_new_hash_table;
                }
            }

            pNewEntry->dwCallHubID       = ptCall->dwCallID;
            pNewEntry->CallHubList.Flink =
            pNewEntry->CallHubList.Blink = &ptCall->CallHubList;


            //
            // Save the old table info (so we can free it & dyna
            // entries later when the lock is released), then save
            // the new table info
            //

            pOldHashTable = ptProvider->pHashTable;
            dwOldNumHashTableEntries = ptProvider->dwNumHashTableEntries;
            dwOldNumDynamicHashTableEntries =
                ptProvider->dwNumDynamicHashTableEntries;


            ptProvider->pHashTable = pNewHashTable;
            ptProvider->dwNumHashTableEntries = dwNewNumHashTableEntries;
            ptProvider->dwNumDynamicHashTableEntries =
                dwNewNumDynamicHashTableEntries;


            //
            // Init the unused table entries and the head & tail
            // list items in the used entries
            //

            pEndOfTable = ptProvider->pHashTable +
                ptProvider->dwNumHashTableEntries;

            for(
                pStaticEntry = ptProvider->pHashTable;
                pStaticEntry != pEndOfTable;
                pStaticEntry++
                )
            {
                if (pStaticEntry->dwCallHubID == 0)
                {
                    InitializeListHead (&pStaticEntry->CallHubList);
                }
                else
                {
                    pEntry2 = pStaticEntry;

                    while (pEntry2)
                    {
                        pEntry2->CallHubList.Flink->Blink =
                        pEntry2->CallHubList.Blink->Flink =
                            &pEntry2->CallHubList;

                        pEntry2 = pEntry2->pNext;
                    }
                }
            }


            //
            // Set pTargetEntry to point to the "new" entry (as expected below)
            //

            pTargetEntry = pNewEntry;
        }


        //
        // Check to see if we need to create any tCallHubClient objects
        //

        SendNewCallHubEvent (ptCall, pTargetEntry);

        //
        // Release the appropriate hash table lock, then if we grew
        // the table free the old table & dynamic table entries
        //

        if (!pOldHashTable)
        {
            ReleaseHashTableEntryLock (ptProvider, pStaticEntry);
        }
        else
        {
            ReleaseHashTableWriterLock (ptProvider);

            FreeHashTable(
                pOldHashTable,
                dwOldNumHashTableEntries,
                dwOldNumDynamicHashTableEntries
                );
        }
    }

    return 0;
}


LONG
PASCAL
CreatetCall(
    PTLINE              ptLine,
    BOOL                bIncoming,
    HDRVCALL            hdCall,
    PTCALL             *pptCall,
    LPLINECALLPARAMS    pCallParams,
    PTCALL              ptCallAssociate
    )
{
    BOOL    bDupedMutex;
    DWORD   dwExtraBytes;
    HANDLE  hMutex;
    PTCALL  ptCall;


    LOG((TL_TRACE,  "CreatetCall: enter, ptLine=%p", ptLine));


    //
    // If there's call params specified check to see if we need to alloc
    // any extra space for the CalledParty, DisplayableAddr, or Comment
    // fields.  Also, if any of these fields are non-NULL make sure to
    // get extra space to keep these fields 64-bit aligned.
    //

    dwExtraBytes = (pCallParams == NULL ? 0 : pCallParams->dwCalledPartySize +
        pCallParams->dwDisplayableAddressSize + pCallParams->dwCommentSize);

    if (dwExtraBytes != 0)
    {
        dwExtraBytes += (sizeof (TCALL) & 4) + 16;
    }


    //
    // Alloc necessary resources
    //

    if (!(ptCall = ServerAlloc (sizeof (TCALL) + dwExtraBytes)))
    {
        return LINEERR_NOMEM;
    }


    //
    // Init tCall & add to tLine's tCall list
    //

    if (bIncoming)
    {
        //
        // This is an incoming call (we're being called by the
        // LINE_NEWCALL handler)
        //

        ptCall->dwKey          = INVAL_KEY;
        ptCall->dwDrvCallFlags = DCF_SPIRETURNED | DCF_DRVCALLVALID;
        ptCall->bAlertApps     = TRUE;
        ptCall->dwCallState    = LINECALLSTATE_UNKNOWN;
        ptCall->hdCall         = hdCall;
    }
    else
    {
        //
        // This is an outgoing call (we're not being called by
        // the LINE_NEWCALL handler)
        //

        ptCall->dwKey = TINCOMPLETECALL_KEY;
    }

    if (pCallParams)
    {
        DWORD dwOffset = sizeof (TCALL) + (sizeof (TCALL) & 4);


        if (pCallParams->dwDisplayableAddressSize != 0)
        {
            CopyMemory(
                (ptCall->pszDisplayableAddress = (WCHAR *)
                    (((LPBYTE) ptCall) + dwOffset)),
                ((LPBYTE) pCallParams) +
                    pCallParams->dwDisplayableAddressOffset,
                (ptCall->dwDisplayableAddressSize =
                    pCallParams->dwDisplayableAddressSize)
                );

            dwOffset += ((ptCall->dwDisplayableAddressSize + 8) & 0xfffffff8);
        }

        if (pCallParams->dwCalledPartySize)
        {
            CopyMemory(
                (ptCall->pszCalledParty = (WCHAR *)
                    (((LPBYTE)ptCall) + dwOffset)),
                ((LPBYTE) pCallParams) + pCallParams->dwCalledPartyOffset,
                (ptCall->dwCalledPartySize = pCallParams->dwCalledPartySize)
                );

            dwOffset += ((ptCall->dwCalledPartySize + 8) & 0xfffffff8);
        }

        if (pCallParams->dwCommentSize)
        {
            CopyMemory(
                (ptCall->pszComment = (WCHAR *)
                    (((LPBYTE) ptCall) + dwOffset)),
                ((LPBYTE) pCallParams) + pCallParams->dwCommentOffset,
                (ptCall->dwCommentSize = pCallParams->dwCommentSize)
                );
        }
    }


    //
    // If this call has an associated call (i.e. this func is being
    // called during the processing of a lineSetupConference request)
    // then we want to make sure we get an htCall which maps to the
    // same SPEventHandlerThread as that of the associated call.
    // This is necessary on MP boxes (or any time we create > 1
    // SPEventHandlerThread) to prevent call state, etc msgs for
    // a call being processed (by thread A) prior to the call-creation
    // completion msg being processed (by thread B).
    //

    if (ptCallAssociate  &&  (gdwNumSPEventHandlerThreads > 1))
    {
        ptCall->hCall = (HCALL) NewObjectEx(
            ghHandleTable,
            ptCall,
            NULL,
            gdwNumSPEventHandlerThreads,
            (DWORD) MAP_HANDLE_TO_SP_EVENT_QUEUE_ID(
                (ULONG_PTR) ptLine->hLine
                )
            );
    }
    else
    {
        LOG((TL_INFO, "CreatetCall: calling NewObject ptCall %p", ptCall));

        ptCall->hCall = (HCALL) NewObject(
            ghHandleTable,
            ptCall,
            NULL
            );
    }

    LOG((TL_TRACE, "CreatetCall: NewObject returned 0x%lx", ptCall->hCall));
    

    if (!ptCall->hCall)
    {
        ServerFree (ptCall);
        return LINEERR_NOMEM;
    }


    //
    // Add the new tCall to the tLine's list
    //

    if (WaitForExclusivetLineAccess(
            ptLine,
            &hMutex,
            &bDupedMutex,
            INFINITE
            ))
    {
        ptCall->ptLine     = ptLine;
        ptCall->ptProvider = ptLine->ptProvider;

        if ((ptCall->pNext = ptLine->ptCalls))
        {
           ptCall->pNext->pPrev = ptCall;
        }

        ptLine->ptCalls = ptCall;

        MyReleaseMutex (hMutex, bDupedMutex);
    }
    else
    {
        //
        // tLine was destroyed, so clean up. Note that we return
        // a generic OPFAILED error, since some calling routines
        // might no be spec'd to return INVALLINEHANDLE, etc.
        //

        DereferenceObject (ghHandleTable, ptCall->hCall, 1);
        return LINEERR_OPERATIONFAILED;
    }


    //
    // Fill in caller's pointer & return success
    //

    *pptCall = ptCall;

    PerfBlock.dwTotalOutgoingCalls++;
    PerfBlock.dwCurrentOutgoingCalls++;

    //
    //  For incoming call, the call is ready to be used
    //
    if (bIncoming)
    {
        ptCall->dwKey = TCALL_KEY;
    }

    LOG((TL_TRACE,  "CreatetCall: exit, new ptCall=%p", *pptCall));

    return 0;
}


LONG
PASCAL
CreatetCallClient(
    PTCALL          ptCall,
    PTLINECLIENT    ptLineClient,
    DWORD           dwPrivilege,
    BOOL            bValidate,
    BOOL            bSendCallInfoMsg,
    PTCALLCLIENT   *pptCallClient,
    BOOL            bIndicatePrivilege
    )
{
    BOOL            bFastCallClient, bValidLineClient;
    PTCALLCLIENT    ptCallClient;
    PTLINECLIENT    ptLineClient2 = NULL;


    LOG((TL_TRACE,  "CreatetCallClient: enter, ptCall=%p,", ptCall));

    if (WaitForExclusivetCallAccess(
            ptCall,
            (bValidate ? TCALL_KEY : TINCOMPLETECALL_KEY)
            ))
    {
        if (ptCall->lUsedFastCallClients < DEF_NUM_FAST_CALLCLIENTS)
        {
            ptCallClient = ptCall->aFastCallClients +
                ptCall->lUsedFastCallClients++;
            ptCall->lActiveFastCallClients++;
            bFastCallClient = TRUE;
        }
        else if ((ptCallClient = ServerAlloc (sizeof(TCALLCLIENT))))
        {
            bFastCallClient = FALSE;
        }
        else
        {
            UNLOCKTCALL(ptCall);
            return LINEERR_NOMEM;
        }

        LOG((TL_INFO, "CreatetCallClient: calling NewObject, ptCallClient = [%p]", ptCallClient));

        if (!(ptCallClient->hCall = (HCALL) NewObject(
                ghHandleTable,
                ptCallClient,
                (LPVOID) UIntToPtr(bFastCallClient)
                )))
        {
            if (bFastCallClient)
            {
                ptCall->lActiveFastCallClients--;
                ptCall->lUsedFastCallClients--;
            }
            else
            {
                ServerFree (ptCallClient);
            }

            UNLOCKTCALL(ptCall);
            return LINEERR_NOMEM;
        }

        if (dwPrivilege == LINECALLPRIVILEGE_OWNER)
        {
            ptCall->dwNumOwners++;
        }
        else
        {
            ptCall->dwNumMonitors++;
        }

        if ((ptCallClient->pNextSametCall = ptCall->ptCallClients))
        {
            ptCallClient->pNextSametCall->pPrevSametCall =
                ptCallClient;
        }

        ptCall->ptCallClients = ptCallClient;

        UNLOCKTCALL (ptCall);

        ptCallClient->ptLineClient = ptLineClient;
        ptCallClient->ptCall       = ptCall;
        ptCallClient->dwPrivilege  = dwPrivilege;
        ptCallClient->bIndicatePrivilege = (bIndicatePrivilege ? 1 : 0);
    }
    else
    {
        //
        // tCall was destroyed, so return error. Note that we return
        // a generic OPFAILED error, since some calling routines
        // might not be spec'd to return INVALCALLHANDLE, etc.
        //

        return LINEERR_OPERATIONFAILED;
    }


    //
    // Add to tLineClient's tCallClient list
    //

    LOCKTLINECLIENT (ptLineClient);

    try
    {
        bValidLineClient =
            (ptLineClient->dwKey == TLINECLIENT_KEY ? TRUE : FALSE);
        if (bValidLineClient)
        {
            ptLineClient2 = (PTLINECLIENT) ReferenceObject (
                ghHandleTable,
                ptLineClient->hLine,
                TLINECLIENT_KEY
                );
            if (ptLineClient2 == NULL || ptLineClient != ptLineClient2)
            {
                bValidLineClient = FALSE;
            }
        }
    }
    myexcept
    {
        bValidLineClient = FALSE;
    }

    if (bValidLineClient)
    {
        ptCallClient->ptClient = ptLineClient->ptClient;

        if ((ptCallClient->pNextSametLineClient = ptLineClient->ptCallClients))
        {
            ptCallClient->pNextSametLineClient->pPrevSametLineClient =
                ptCallClient;
        }

        ptLineClient->ptCallClients = ptCallClient;

        if (ptLineClient2)
        {
            DereferenceObject (
                ghHandleTable,
                ptLineClient2->hLine,
                1
                );
        }

        if (ptLineClient->ptLineApp->dwAPIVersion <= TAPI_VERSION3_0)
        {
            FillMemory (
                ptCallClient->adwEventSubMasks, 
                sizeof(DWORD) * EM_NUM_MASKS,
                (BYTE) 0xff
                );
        }
        else
        {
            CopyMemory (
                ptCallClient->adwEventSubMasks, 
                ptLineClient->adwEventSubMasks,
                sizeof(DWORD) * EM_NUM_MASKS
                );
        }

        UNLOCKTLINECLIENT (ptLineClient);
    }
    else
    {
        if (ptLineClient2)
        {
            DereferenceObject (
                ghHandleTable,
                ptLineClient2->hLine,
                1
                );
        }

        UNLOCKTLINECLIENT (ptLineClient);


        //
        // Couldn't add tCallClient to tLineClient's list, so safely
        // remove it from tCall's list, dec the owner or monitor count,
        // free the tCallClient, and return an appropriate error
        //
        // Note that no validation of tCall is necessary - it has to be
        // valid since we previously added a tCallClient to it's list,
        // and that tCallClient's key was not yet validated (so any
        // threads attempting to destroy the tCall would be spinning)
        //

        LOCKTCALL (ptCall);

        if (dwPrivilege == LINECALLPRIVILEGE_OWNER)
        {
            ptCall->dwNumOwners--;
        }
        else
        {
            ptCall->dwNumMonitors--;
        }

        if (ptCallClient->pNextSametCall)
        {
            ptCallClient->pNextSametCall->pPrevSametCall =
                ptCallClient->pPrevSametCall;
        }

        if (ptCallClient->pPrevSametCall)
        {
            ptCallClient->pPrevSametCall->pNextSametCall =
                ptCallClient->pNextSametCall;
        }
        else
        {
            ptCall->ptCallClients = ptCallClient->pNextSametCall;
        }

        if (bFastCallClient)
        {
            ptCall->lActiveFastCallClients--;
        }

        UNLOCKTCALL (ptCall);

        DereferenceObject (ghHandleTable, ptCallClient->hCall, 1);

        return LINEERR_INVALLINEHANDLE;
    }


    //
    // If here success, so mark tCallClient as valid (if another thread
    // was tearing down the corresponding tCall or tLineClient it will
    // have been spinning waiting for us to validate this tCallClient)
    //

    ptCallClient->dwKey = (bValidate ? TCALLCLIENT_KEY :
        TINCOMPLETECALLCLIENT_KEY);


    //
    // Send a call info msg other call clients if appropriate
    //

    if (bSendCallInfoMsg)
    {
        SendMsgToCallClients(
            ptCall,
            ptCallClient,
            LINE_CALLINFO,
            (dwPrivilege == LINECALLPRIVILEGE_OWNER ?
                LINECALLINFOSTATE_NUMOWNERINCR :
                LINECALLINFOSTATE_NUMMONITORS),
            0,
            0
            );
    }


    //
    // Fill in caller's pointer & return success
    //

    *pptCallClient = ptCallClient;

    LOG((TL_TRACE, 
        "CreatetCallClient: exit, new ptCallClient=%p",
        *pptCallClient
        ));

    return 0;
}


LONG
PASCAL
CreatetCallAndClient(
    PTLINECLIENT        ptLineClient,
    PTCALL             *pptCall,
    PTCALLCLIENT       *pptCallClient,
    LPLINECALLPARAMS    pCallParams,
    PTCALL              ptCallAssociate
    )
{
    LONG            lResult = 0;
    DWORD           dwAppNameSize;
    WCHAR          *pszAppName = NULL;
    PTCALL          ptCall = NULL;
    PTLINE          ptLine;

    LOG((TL_TRACE,  "CreatetCallAndClient: enter, ptLineClient=%p", ptLineClient));

    try
    {
        ptLine = ptLineClient->ptLine;

        dwAppNameSize = ptLineClient->ptLineApp->dwFriendlyNameSize;

        if (ptLineClient->dwKey != TLINECLIENT_KEY)
        {
            lResult = LINEERR_INVALLINEHANDLE;
        }
        else if ((pszAppName = ServerAlloc (dwAppNameSize)))
        {
            CopyMemory(
                pszAppName,
                ptLineClient->ptLineApp->pszFriendlyName,
                dwAppNameSize
                );
        }
        else
        {
            lResult = LINEERR_NOMEM;
        }
    }
    myexcept
    {
        ServerFree (pszAppName);
        lResult = LINEERR_INVALLINEHANDLE;
    }

    if (lResult != 0)
    {
        return lResult;
    }

    if ((lResult = CreatetCall(
            ptLine,
            FALSE,
            (HDRVCALL) 0,
            &ptCall,
            pCallParams,
            ptCallAssociate

            )) != 0)
    {
        ServerFree (pszAppName);

        return lResult;
    }

    if ((lResult = CreatetCallClient(
            ptCall,
            ptLineClient,
            LINECALLPRIVILEGE_OWNER,
            FALSE,
            FALSE,
            pptCallClient,
            FALSE

            )) != 0)
    {
        ServerFree (pszAppName);

        ptCall->dwDrvCallFlags = DCF_SPIRETURNED;

        DestroytCall (ptCall);

        *pptCall = (PTCALL) NULL;
        return lResult;
    }

    ptCall->dwAppNameSize = dwAppNameSize;
    ptCall->pszAppName    = pszAppName;

    *pptCall = ptCall;

    return lResult;
}


LONG
PASCAL
CreateCallMonitors(
    PTCALL  ptCall,
    BOOL    bIncoming
    )
{
    //
    // This func is called by post processing routines when
    // a call was successfully created, or on receiving the
    // first call state message for an incoming call, at
    // which times we want to create call handles for any
    // monitoring apps.
    //
    // Assumes tCall only has has either no clients at all
    // or a single (owner) client
    //
    // Returns the # of monitor call clients created (>=0) or
    // an error value (<0)
    //

    LONG            lResult;
    BOOL            bInitializedMsgs, bRemote, bConfParent = FALSE;
    DWORD           i, dwAddressID, dwCallID, dwRelatedCallID;
    TPOINTERLIST    lineClients, *pLineClients = &lineClients;
    PTLINE          ptLine;
    PTLINECLIENT    ptLineClientOwner;
    ASYNCEVENTMSG   newCallMsg[2], callStateUnkMsg[2];


    //
    // Determine if this call is conf parent and get a list of line clients
    //

    try
    {
        PTCONFERENCELIST    pConfList;

        if ((pConfList = ptCall->pConfList) &&
            (pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff)) &&
            (pConfList->aptCalls[0] == ptCall))
        {
            bConfParent = TRUE;
        }
        dwAddressID     = ptCall->dwAddressID;
        dwCallID        = ptCall->dwCallID;
        dwRelatedCallID = ptCall->dwRelatedCallID;

        ptLine = (PTLINE) ptCall->ptLine;

        ptLineClientOwner = (ptCall->ptCallClients ?
            ptCall->ptCallClients->ptLineClient : NULL);
    }
    myexcept
    {
        return LINEERR_OPERATIONFAILED;
    }

    if ((lResult = GetLineClientListFromLine (ptLine, &pLineClients)))
    {
        return lResult;
    }


    //
    // Look at each line client in the list, and if it has
    // monitor privileges and is not the one associated with
    // the existing owner call client then create a monitor
    // call client
    //
    //

    bInitializedMsgs = FALSE;

    for (i = 0; i < pLineClients->dwNumUsedEntries; i++)
    {
        PTCALLCLIENT    ptCallClientMonitor;
        PTLINECLIENT    ptLineClient = pLineClients->aEntries[i];
        BOOL            fContinue;


        try
        {
            if (!(ptLineClient->dwPrivileges & LINECALLPRIVILEGE_MONITOR) ||
                (ptLineClient == ptLineClientOwner))
            {
                fContinue = TRUE;
            }
            else
            {
                fContinue = FALSE;

                bRemote = IS_REMOTE_CLIENT (ptLineClient->ptClient);
            }
        }
        myexcept
        {
            //
            // If here the tLineClient or tCallClient was destroyed,
            // just continue
            //

            fContinue = TRUE;
        }


        if (fContinue)
        {
            continue;
        }


        //
        // NOTE: If client is remote(sp) then create the call client
        //       with OWNER privileges so client can still do everything.
        //       The remote tapisrv will deal with all the remote
        //       privilege issues.  We'll still send the appropriate
        //       MONITOR privilege flag so the remote tapisrv will know
        //       not to look for an owner app.
        //
        //       This scheme might end up confusing other apps since
        //       a LINE_CALLINFO\NUMOWNERINCR (not NUMMONITORS) msgs
        //       get sent, but it certainly beats what we had in tapi 2.1 -
        //       that is, if a remote client did not initially have owner
        //       privilege then it could *never* get owner privilege.
        //

        if (CreatetCallClient(
                ptCall,
                ptLineClient,
                (bRemote ? LINECALLPRIVILEGE_OWNER :LINECALLPRIVILEGE_MONITOR),
                TRUE,
                FALSE,
                &ptCallClientMonitor,
                bIncoming

                ) == 0)
        {
            //
            // If this is an incoming call simply increment the number of
            // monitor call clients created and continue.
            //
            // Else this is an outgoing call, so send the monitor app
            // LINE_APPNEWCALL & LINE_CALLSTATE\UNKNOWN messages to alert
            // it of new outgoing call
            //

            if (bIncoming)
            {
                lResult++;
                continue;
            }

            if (!bInitializedMsgs)
            {
                
                
                newCallMsg->TotalSize          = sizeof (ASYNCEVENTMSG) +
                                                    3 * sizeof (DWORD);
                newCallMsg->fnPostProcessProcHandle = 0;
                newCallMsg->Msg                = LINE_APPNEWCALL;
                newCallMsg->Param1             = dwAddressID;
                newCallMsg->Param3             = LINECALLPRIVILEGE_MONITOR;

                *(&newCallMsg->Param4 + 1)     = dwCallID;
                *(&newCallMsg->Param4 + 2)     = dwRelatedCallID;
                *(&newCallMsg->Param4 + 3)     = (DWORD)bConfParent;

                callStateUnkMsg->TotalSize     = sizeof(ASYNCEVENTMSG)
                                                    + sizeof (HCALLHUB);

                callStateUnkMsg->fnPostProcessProcHandle = 0;
                callStateUnkMsg->Msg                = LINE_CALLSTATE;
                callStateUnkMsg->Param1             = LINECALLSTATE_UNKNOWN;

                *((LPHCALLHUB)(callStateUnkMsg + 1)) = (HCALLHUB)0;

                bInitializedMsgs = TRUE;
            }

            try
            {
                //
                // We're presenting the app with a new call handle; for
                // 2.0 & newer apps we indicate this with an APPNEWCALL
                // msg, while older apps just get the privilege field
                // set in the call state msg.
                //

                if (ptLineClient->ptLineApp->dwAPIVersion >= TAPI_VERSION2_0)
                {
                    if (!FMsgDisabled(
                        ptLineClient->ptLineApp->dwAPIVersion,
                        ptLineClient->adwEventSubMasks,
                        LINE_APPNEWCALL,
                        0
                        ))
                    {
                        newCallMsg->InitContext =
                            ptLineClient->ptLineApp->InitContext;
                        newCallMsg->hDevice     = ptLineClient->hRemoteLine;
                        newCallMsg->OpenContext = ptLineClient->OpenContext;
                        newCallMsg->Param2      = ptCallClientMonitor->hCall;

                        WriteEventBuffer (ptLineClient->ptClient, newCallMsg);
                    }

                    callStateUnkMsg->Param3 = 0;
                }
                else
                {
                    callStateUnkMsg->Param3 = LINECALLPRIVILEGE_MONITOR;
                }

                if (FMsgDisabled (
                    ptLineClient->ptLineApp->dwAPIVersion,
                    ptLineClient->adwEventSubMasks,
                    LINE_CALLSTATE,
                    LINECALLSTATE_UNKNOWN
                    ))
                {
                    continue;
                }
                    

                callStateUnkMsg->InitContext =
                    ptLineClient->ptLineApp->InitContext;

                callStateUnkMsg->hDevice     = ptCallClientMonitor->hCall;
                callStateUnkMsg->OpenContext = ptLineClient->OpenContext;
                 
                //
                // Indicate hRemoteLine in p4 to make life easier for remotesp
                //

                callStateUnkMsg->Param4 = ptLineClient->hRemoteLine;


                //
                // REMOTESP HACK: See note in LINE_CALLSTATE msg handler
                //

                if (IS_REMOTE_CLIENT (ptLineClient->ptClient))
                {
                    callStateUnkMsg->Param2 = LINECALLPRIVILEGE_MONITOR;
                    callStateUnkMsg->Param3 = LINEMEDIAMODE_UNKNOWN;
                }
                else
                {
                    callStateUnkMsg->Param2 = 0;
                }

                WriteEventBuffer (ptLineClient->ptClient, callStateUnkMsg);

                lResult++;
            }
            myexcept
            {
                // just continue
            }
        }
    }

    if (pLineClients != &lineClients)
    {
        ServerFree (pLineClients);
    }


    //
    // Now safely set the flag that says it's ok for other routines like
    // lineGetNewCalls to create new call handles for apps for this call
    //

    {
        if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
        {
            //
            // Only do call hub hashing if there's >0 call clients,
            // since the (incoming) call will get destroyed otherwise
            //
            //  $$ Per bug 87355
            //  It is necessary to do DoCallHubHashing even if we do 
            //  not have a client now. So we removed the check for 
            //  ptCall->ptCallClients. For incoming call who has got
            //  an owner, we behave the same; for incoming call without
            //  an owner, we would hash the call for now and remove the
            //  call from hash table when destroying the call, so we 
            //  are safe either way.
            //

            DoCallHubHashing (ptCall, 0);

            ptCall->bCreatedInitialMonitors = TRUE;
            UNLOCKTCALL (ptCall);
        }
        else
        {
            lResult = LINEERR_OPERATIONFAILED;
        }
    }

    return lResult;

}


PTREQUESTRECIPIENT
PASCAL
GetHighestPriorityRequestRecipient(
    void
    )
{
    BOOL               bFoundRecipientInPriorityList = FALSE;
    WCHAR             *pszAppInPriorityList,
                      *pszAppInPriorityListPrev = (WCHAR *) LongToPtr(0xffffffff);
    PTREQUESTRECIPIENT pRequestRecipient,
                       pHighestPriorityRequestRecipient = NULL;


    EnterCriticalSection (&gPriorityListCritSec);

    pRequestRecipient = TapiGlobals.pRequestRecipients;

    while (pRequestRecipient)
    {
        if (TapiGlobals.pszReqMakeCallPriList &&

            (pszAppInPriorityList = wcsstr(
                TapiGlobals.pszReqMakeCallPriList,
                pRequestRecipient->ptLineApp->pszModuleName
                )))
        {
            if (pszAppInPriorityList <= pszAppInPriorityListPrev)
            {
                pHighestPriorityRequestRecipient = pRequestRecipient;
                pszAppInPriorityListPrev = pszAppInPriorityList;

                bFoundRecipientInPriorityList = TRUE;
            }
        }
        else if (!bFoundRecipientInPriorityList)
        {
            pHighestPriorityRequestRecipient = pRequestRecipient;
        }

        pRequestRecipient = pRequestRecipient->pNext;
    }

    LeaveCriticalSection (&gPriorityListCritSec);

    return pHighestPriorityRequestRecipient;
}


void
PASCAL
FreetCall(
    PTCALL  ptCall
    )
{
    if (ptCall->pszAppName)
    {
        ServerFree (ptCall->pszAppName);
    }

    if (ptCall->dwDrvCallFlags & DCF_INCOMINGCALL)
    {
        PerfBlock.dwCurrentIncomingCalls--;
    }
    else
    {
        PerfBlock.dwCurrentOutgoingCalls--;
    }

    DereferenceObject (ghHandleTable, ptCall->hCall, 1);
}


void
PASCAL
DestroytCall(
    PTCALL  ptCall
    )
{
    DWORD   dwKey;


    LOG((TL_TRACE, "DestroytCall: enter, ptCall=x%p", ptCall));


    //
    // Safely get the call's current key, then grab the call's lock.
    // The two waits allow us to deal with the case where the tCall's
    // key is either TINCOMPLETECALL_KEY or TCALL_KEY, or changing
    // from the former to the latter (the completion proc was called)
    //

    try
    {
        dwKey = (ptCall->dwKey == TCALL_KEY ? TCALL_KEY : TINCOMPLETECALL_KEY);
    }
    myexcept
    {
        LOG((TL_ERROR, "DestroytCall: excepted looking at key"));
        return;
    }

    if (WaitForExclusivetCallAccess (ptCall, dwKey) ||
        WaitForExclusivetCallAccess (ptCall, TCALL_KEY))

    {
        PTPROVIDER  ptProvider;
        PTCALL      ptCall2;

        ptCall2 = ReferenceObject (
            ghHandleTable,
            ptCall->hCall,
            dwKey
            );
        if (ptCall2)
        {
            DereferenceObject (
                ghHandleTable,
                ptCall2->hCall,
                1
                );
        }
        if (ptCall2 == NULL || ptCall != ptCall2)
        {
            UNLOCKTCALL (ptCall);
            return;
        }


        //
        // Invalidate the tCall
        //

        ptCall->dwKey = TZOMBIECALL_KEY;
        UNLOCKTCALL (ptCall);


        //
        // If the provider has not returned from it's call-creation
        // routine yet (i.e. TSPI_lineMakeCall) wait for it to do so
        //

        while (!(ptCall->dwDrvCallFlags & DCF_SPIRETURNED))
        {
            Sleep (0);
        }

        ptProvider = ptCall->ptProvider;


        //
        // Remove tCall from call hub list if appropriate
        //
        // Note that there's a window of time between which the call IDs
        // are retrieved and the call is inserted in a hash time, and so
        // we need to check for that case as well (CallHubList.Flink == 0)
        //

        if (ptCall->dwCallID != 0)
        {
            PTHASHTABLEENTRY    pStaticEntry, pEntry;


            pStaticEntry = AcquireHashTableEntryLock(
                ptCall->ptProvider,
                ptCall->dwCallID
                );

            if (ptCall->CallHubList.Flink == 0 ||
                ptCall->CallHubList.Blink == 0)
            {
                ReleaseHashTableEntryLock (ptCall->ptProvider, pStaticEntry);
                goto finished_callhubID;
            }

            RemoveEntryList (&ptCall->CallHubList);
            ptCall->CallHubList.Flink = NULL;
            ptCall->CallHubList.Blink = NULL;

            pEntry = (pStaticEntry->dwCallHubID == ptCall->dwCallID ?
                pStaticEntry :
                FindDynamicHashTableEntry (pStaticEntry, ptCall->dwCallID)
                );

            if (IsListEmpty (&pEntry->CallHubList))
            {
                PTCALLHUBCLIENT  ptCallHubClient, pNext;


                ptCallHubClient = pEntry->ptCallHubClients;

                if (pEntry == pStaticEntry)
                {
                    pStaticEntry->dwCallHubID = 0;
                    pStaticEntry->ptCallHubClients = NULL;
                }
                else
                {
                    RemoveDynamicHashTableEntry(
                        ptProvider,
                        pStaticEntry,
                        pEntry
                        );
                }

                ReleaseHashTableEntryLock (ptCall->ptProvider, pStaticEntry);

                if (ptCallHubClient)
                {
                    ASYNCEVENTMSG   msg;


                    pEntry->ptCallHubClients = NULL;

                    msg.TotalSize          = sizeof (ASYNCEVENTMSG);
                    msg.Msg                = LINE_CALLHUBCLOSE;
                    msg.fnPostProcessProcHandle = 0;
                    msg.hDevice            = 0;
                    msg.OpenContext        = 0;
                    msg.Param2             = 0;
                    msg.Param3             = 0;

                    while (ptCallHubClient)
                    {
                        msg.Param1 = ptCallHubClient->hCallHub;

                        try
                        {
                            msg.InitContext =
                                ptCallHubClient->ptLineApp->InitContext;

                            if (ptCallHubClient->ptLineApp->dwKey ==
                                    TLINEAPP_KEY &&
                                (!FMsgDisabled(
                                    ptCallHubClient->ptLineApp->dwAPIVersion,
                                    ptCallHubClient->ptLineApp->adwEventSubMasks,
                                    LINE_CALLHUBCLOSE,
                                    0
                                    )))
                            {
                                WriteEventBuffer(
                                    ptCallHubClient->ptClient,
                                    &msg
                                    );
                            }
                        }
                        except (EXCEPTION_EXECUTE_HANDLER)
                        {
                            // tLineApp is gone, just fall through
                        }

                        pNext = ptCallHubClient->pNext;
                        ptCallHubClient->dwKey = INVAL_KEY;

                        DereferenceObject(
                            ghHandleTable, 
                            ptCallHubClient->hCallHub,
                            1
                            );

                        ptCallHubClient = pNext;
                    }
                }
            }
            else
            {
                ReleaseHashTableEntryLock (ptCall->ptProvider, pStaticEntry);
            }
        }
        
finished_callhubID:

        //
        // Destroy all the tCallClient's
        //

        if (ptCall->ptCallClients)
        {
            while (ptCall->ptCallClients)
            {
                DestroytCallClient (ptCall->ptCallClients);
            }
        }


        //
        // Tell the provider to close the call, but only if the hdCall
        // is valid (we might be destroying a call that
        // LMakeCall_PostProcess would normally destroy in the event
        // of a failed make-call request, and we wouldn't want to pass
        // an invalid hdCall to the driver)
        //

        if (ptCall->dwDrvCallFlags & DCF_DRVCALLVALID)
        {
            if (ptProvider->dwTSPIOptions & LINETSPIOPTION_NONREENTRANT)
            {
                WaitForSingleObject (ptProvider->hMutex, INFINITE);
            }

            if (ptProvider->apfn[SP_LINECLOSECALL])
            {
                CallSP1(
                    ptProvider->apfn[SP_LINECLOSECALL],
                    "lineCloseCall",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) ptCall->hdCall
                    );
            }

            if (ptProvider->dwTSPIOptions & LINETSPIOPTION_NONREENTRANT)
            {
                ReleaseMutex (ptProvider->hMutex);
            }
        }


        //
        // Remove tCall from the tLine's tCall list
        //

        RemoveCallFromLineList (ptCall);


        //
        // Free the resources
        //

        {
            PTCONFERENCELIST    pConfList;


            if ((pConfList = ptCall->pConfList) &&
                (pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff)))
            {
                DWORD   i;

                if (pConfList->aptCalls[0] == ptCall)
                {
                    //
                    // We're destroying a conf parent so we want to zero
                    // out the pConfList field of all the conf children,
                    // essentially removing them from the conference.
                    //

                    TPOINTERLIST    confCallList,
                                    *pConfCallList = &confCallList;


                    if (GetConfCallListFromConf(
                            pConfList,
                            &pConfCallList

                            ) == 0)
                    {
                        for(
                            i = 1;
                            i < pConfCallList->dwNumUsedEntries;
                            i++
                            )
                        {
                            SetCallConfList(
                                pConfCallList->aEntries[i],
                                NULL,
                                FALSE
                                );
                        }

                        if (pConfCallList != &confCallList)
                        {
                            ServerFree (pConfCallList);
                        }
                    }

                    while (pConfList)
                    {
                        PTCONFERENCELIST    pNextConfList =
                                                pConfList->pNext;


                        ServerFree (pConfList);
                        pConfList = pNextConfList;
                    }
                }
                else
                {
                }
            }
        }

        while (ptCall->lActiveFastCallClients != 0)
        {
            Sleep (5);
        }

        FreetCall (ptCall);
    }
    else
    {
        LOG((TL_ERROR, "DestroytCall: two waits failed!"));
    }

}


void
PASCAL
DestroytCallClient(
    PTCALLCLIENT    ptCallClient
    )
{
    BOOL            bUnlock = FALSE,
                    bExit = TRUE,
                    bDestroytCall,
                    bSendCallInfoMsgs,
                    bFastCallClient;
    HCALL           hCall;
    PTCALL          ptCall;
    PTCALLCLIENT    ptCallClient2;


    LOG((TL_TRACE, "DestroytCallClient: enter, ptCallCli=x%p", ptCallClient));

    //
    // Check that this is a valid tCallClient, & if so lock the
    // corresponding tCall (and recheck)
    //

    try
    {
        if (ptCallClient->dwKey == TINCOMPLETECALLCLIENT_KEY ||
            ptCallClient->dwKey == TCALLCLIENT_KEY)
        {
            ptCall = ptCallClient->ptCall;

            LOCKTCALL (ptCall);

            bUnlock = TRUE;

            //
            //  Check to be sure we are working on a valid memory
            //
            
            ptCallClient2 = ReferenceObject (
                ghHandleTable,
                ptCallClient->hCall,
                ptCallClient->dwKey
                );
            if (ptCallClient2 != NULL)
            {
                DereferenceObject (
                    ghHandleTable,
                    ptCallClient2->hCall,
                    1
                    );
            }

            if ((ptCallClient->dwKey == TINCOMPLETECALLCLIENT_KEY ||
                ptCallClient->dwKey == TCALLCLIENT_KEY) &&
                ptCall == ptCallClient->ptCall &&
                ptCallClient2 == ptCallClient)

            {
                //
                // We can continue detroying this tCallClient
                //
                bExit = FALSE;
            }
        }
    }
    myexcept
    {
        LOG((TL_ERROR, "DestroytCallClient: %lx faulted looking at key",
                     ptCallClient ));
    }

    if (bExit)
    {
        if (bUnlock)
        {
            UNLOCKTCALL (ptCall);
        }

        return;
    }


    //
    // Mark tCallClient as bad
    //

    ptCallClient->dwKey = INVAL_KEY;


    //
    // Munge tCall's num owners/monitors fields
    //

    if (ptCallClient->dwPrivilege == LINECALLPRIVILEGE_OWNER)
    {
        ptCall->dwNumOwners--;

        //
        // NOTE: per bug #20545 we're no longer auto-dropping
        //       non-IDLE calls; figured this would be the
        //       wrong thing to do in a distributed system
        //
        //       dankn 02/15/96
        //
    }
    else
    {
        ptCall->dwNumMonitors--;

        //
        // NOTE: per bug #20545 we're no longer auto-dropping
        //       non-IDLE calls; figured this would be the
        //       wrong thing to do in a distributed system
        //
        //       dankn 02/15/96
        //
    }


    //
    // Remove it from the tCall's tCallClient list
    //

    bDestroytCall = FALSE;
    bSendCallInfoMsgs = (ptCall->dwKey == TCALL_KEY ? TRUE : FALSE);

    if (ptCallClient->pNextSametCall)
    {
        ptCallClient->pNextSametCall->pPrevSametCall =
            ptCallClient->pPrevSametCall;
    }

    if (ptCallClient->pPrevSametCall)
    {
        ptCallClient->pPrevSametCall->pNextSametCall =
            ptCallClient->pNextSametCall;
    }
    else if (ptCallClient->pNextSametCall)
    {
        ptCall->ptCallClients = ptCallClient->pNextSametCall;
    }
    else // last call client so destroy the tCall too
    {
        ptCall->ptCallClients = NULL;
        bDestroytCall = TRUE;
    }

    UNLOCKTCALL (ptCall);


    //
    // Remove tCallClient from the tLineClient's tCallClient list
    //

    RemoveCallClientFromLineClientList (ptCallClient);


    //
    // Save the hCall in a local because it won't be safe to access
    // ptCallClient->hCall once we've decremented
    // ptCall->lActiveFastCallClients below
    //

    hCall = ptCallClient->hCall;


    //
    // If this is a fast call client decrement the number of active
    // fast call clients prior to calling DestroytCall
    //

    bFastCallClient = (ptCallClient >= ptCall->aFastCallClients &&
        ptCallClient < (ptCall->aFastCallClients + DEF_NUM_FAST_CALLCLIENTS));

    if (bFastCallClient)
    {
        InterlockedDecrement (&ptCall->lActiveFastCallClients);
    }

    if (bDestroytCall)
    {
        DestroytCall (ptCall);
        bSendCallInfoMsgs = FALSE;
    }


    //
    // Send call info msgs as appropriate
    //

    if (bSendCallInfoMsgs)
    {
        SendMsgToCallClients(
            ptCall,
            NULL,
            LINE_CALLINFO,
            (ptCallClient->dwPrivilege ==
                LINECALLPRIVILEGE_OWNER ?
                LINECALLINFOSTATE_NUMOWNERDECR :
                LINECALLINFOSTATE_NUMMONITORS),
            0,
            0
            );
    }


    //
    // Decrement reference count remove the initial (creation) reference
    //

    DereferenceObject (ghHandleTable, hCall, 1);
}


void
PASCAL
DestroytLine(
    PTLINE  ptLine,
    BOOL    bUnconditional
    )
{
    BOOL    bCloseMutex;
    HANDLE  hMutex;


    LOG((TL_TRACE, 
        "DestroytLine: enter, ptLine=x%p, bUnconditional=%d",
        ptLine,
        bUnconditional
        ));

    if (WaitForExclusivetLineAccess(
            ptLine,
            &hMutex,
            &bCloseMutex,
            INFINITE
            ))
    {
        //
        // If the key is bad another thread is in the process of
        // destroying this widget, so just release the mutex &
        // return. Otherwise, if this is a conditional destroy
        // & there are existing clients (which can happen when
        // one app is closing the last client just as another app
        // is creating one) just release the mutex & return.
        // Otherwise, mark the widget as bad and proceed with
        // the destroy; also, send CLOSE msgs to all the clients
        // (note that we have to do this manually rather than via
        // SendMsgToLineClients since 1) we don't want to hold the
        // mutex when sending msgs [deadlock], and 2) we mark the
        // dwKey as invalid)
        //

        {
            BOOL            bExit;
            TPOINTERLIST    fastClientList, *pClientList = &fastClientList;


            if (ptLine->dwKey == TLINE_KEY &&
                (bUnconditional == TRUE  ||  ptLine->ptLineClients == NULL))
            {
                if (GetLineClientListFromLine (ptLine, &pClientList) != 0)
                {
                    //
                    // If here we know there's at least a few entries
                    // in the fastClientList (DEF_NUM_PTR_LIST_ENTRIES
                    // to be exact), so we'll just work with that list
                    // and at least get msgs out to a few clients
                    //

                    pClientList = &fastClientList;

                    fastClientList.dwNumUsedEntries =
                        DEF_NUM_PTR_LIST_ENTRIES;
                }

                ptLine->dwKey = INVAL_KEY;
                bExit = FALSE;
            }
            else
            {
                bExit = TRUE;
            }

            MyReleaseMutex (hMutex, bCloseMutex);

            if (bExit)
            {
                LOG((TL_TRACE, 
                    "DestroytLine: exit, didn't destroy tLine=x%p",
                    ptLine
                    ));

                return;
            }

            if (pClientList->dwNumUsedEntries)
            {
                DWORD           i;
                PTCLIENT        ptClient;
                PTLINECLIENT    ptLineClient;
                ASYNCEVENTMSG   msg;


                ZeroMemory (&msg, sizeof (msg));

                msg.TotalSize = sizeof (ASYNCEVENTMSG);
                msg.Msg       = LINE_CLOSE;

                for (i = 0; i < pClientList->dwNumUsedEntries; i++)
                {
                    ptLineClient = (PTLINECLIENT) pClientList->aEntries[i];

                    try
                    {
                        msg.InitContext = ptLineClient->ptLineApp->InitContext;
                        msg.hDevice     = ptLineClient->hRemoteLine;
                        msg.OpenContext = ptLineClient->OpenContext;

                        ptClient = ptLineClient->ptClient;

                        if (ptLineClient->dwKey == TLINECLIENT_KEY &&
                            (!FMsgDisabled(
                                ptLineClient->ptLineApp->dwAPIVersion,
                                ptLineClient->adwEventSubMasks,
                                LINE_CLOSE,
                                0
                                )))
                        {
                            WriteEventBuffer (ptClient, &msg);
                        }
                    }
                    myexcept
                    {
                        // do nothing
                    }
                }
            }

            if (pClientList != &fastClientList)
            {
                ServerFree (pClientList);
            }
        }


        //
        // Destroy all the widget's clients.  Note that we want to
        // grab the mutex (and we don't have to dup it, since this
        // thread will be the one to close it) each time we reference
        // the list of clients, since another thread might be
        // destroying a client too.
        //

        {
            HLINE   hLine;


            hMutex = ptLine->hMutex;

destroy_tLineClients:

            WaitForSingleObject (hMutex, INFINITE);

            hLine = (ptLine->ptLineClients ?
                ptLine->ptLineClients->hLine : (HLINE) 0);

            ReleaseMutex (hMutex);

            if (hLine)
            {
                DestroytLineClient (hLine);
                goto destroy_tLineClients;
            }
        }


        //
        // There may yet be some tCall's hanging around, i.e. incoming
        // calls that we have not processed the 1st call state msg for
        // and hence have no associated owner/monitor that would have
        // been destroyed in the loop above, so destroy any of these
        // before proceeding
        //
        //

        {
            PTCALL  ptCall;


destroy_UnownedtCalls:

            WaitForSingleObject (hMutex, INFINITE);

            ptCall = ptLine->ptCalls;

            ReleaseMutex (hMutex);

            if (ptCall)
            {
                DestroytCall (ptCall);
                goto destroy_UnownedtCalls;
            }
        }


        //
        // Tell the provider to close the widget
        //

        {
            PTPROVIDER          ptProvider = ptLine->ptProvider;
            PTLINELOOKUPENTRY   pEntry;
            
            pEntry = GetLineLookupEntry (ptLine->dwDeviceID);

            if (ptProvider->dwTSPIOptions & LINETSPIOPTION_NONREENTRANT)
            {
                WaitForSingleObject (ptProvider->hMutex, INFINITE);
            }

            if ( ptProvider->apfn[SP_LINECLOSE] && 
                 pEntry &&
                 !pEntry->bRemoved
               )
            {
                CallSP1(
                    ptProvider->apfn[SP_LINECLOSE],
                    "lineClose",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) ptLine->hdLine
                    );
            }

            if (ptProvider->dwTSPIOptions & LINETSPIOPTION_NONREENTRANT)
            {
                ReleaseMutex (ptProvider->hMutex);
            }
        }


        //
        // NULLify the ptLine field in the lookup entry, so LOpen will
        // know it has to open the SP's line on the next open request
        //

        {
            PTLINELOOKUPENTRY   pEntry;


            pEntry = GetLineLookupEntry (ptLine->dwDeviceID);
            if (NULL != pEntry)
            {
                pEntry->ptLine = NULL;
            }
        }

        DereferenceObject (ghHandleTable, ptLine->hLine, 1);
    }
    else
    {
        LOG((TL_ERROR, 
        "DestroytLine: WaitForExclusivetLineAccess failed"
        ));
    }

    // PERF

    if (PerfBlock.dwLinesInUse)
    {
        PerfBlock.dwLinesInUse--;
    }
    else
    {
        LOG((TL_INFO, "PERF: dwNumLinesInUse below 0"));
    }

    LOG((TL_TRACE,  "DestroytLine: exit, destroyed line=x%p", ptLine));
}


void
PASCAL
DestroytLineClient(
    HLINE   hLine
    )
{
    BOOL            bDupedMutex;
    HANDLE          hMutex;
    PTLINECLIENT    ptLineClient;


    LOG((TL_TRACE,  "DestroytLineClient: enter, hLine=x%x", hLine));

    if (!(ptLineClient = ReferenceObject (ghHandleTable, hLine, 0)))
    {
        return;
    }


    //
    // If we can get exclusive access to this tLineClient then mark
    // it (the dwKey) as bad & continue with teardown.  Else, another
    // thread is already in the process of destrying this tLineClient
    //
    //

    if (WaitForExclusiveLineClientAccess (ptLineClient))
    {
        BOOL    bSendDevStateCloseMsg = FALSE;
        DWORD   dwProxyCloseMsgs = 0;
        PTLINE  ptLine;
        PTPROVIDER ptProvider = ptLineClient->ptLine->ptProvider;
        HANDLE  hProviderMutex = NULL;


        if (ptProvider->dwTSPIOptions & LINETSPIOPTION_NONREENTRANT)
        {
            hProviderMutex = ptProvider->hMutex;
        }

        ptLineClient->dwKey = INVAL_KEY;

        //
        //  Remove the proxy server publishing if any
        //
        if (ptLineClient->szProxyClsid)
        {
            OnProxyLineClose (ptLineClient->szProxyClsid);
            ServerFree (ptLineClient->szProxyClsid);
        }

        //
        // Destroy all the tCallClients.  Note that we want to grab the
        // lock each time we reference the list of tCallClient's, since
        // another thread might be destroying a tCallClient too.
        //

        {
            PTCALLCLIENT    ptCallClient;


destroy_tCallClients:

            ptCallClient = ptLineClient->ptCallClients;

            UNLOCKTLINECLIENT (ptLineClient);

            if (ptCallClient)
            {
                DestroytCallClient (ptCallClient);
                LOCKTLINECLIENT (ptLineClient);
                goto destroy_tCallClients;
            }
        }


        //
        // Remove tLineClient from tLineApp's list.  Note that we don't
        // have to worry validating the tLineApp here, since we know
        // it's valid (another thread trying to destroy the tLineApp
        // will be spinning until the tLineClient we're destroying here
        // is removed from the tLineApp's list)
        //

        {
            PTLINEAPP   ptLineApp = (PTLINEAPP) ptLineClient->ptLineApp;


            LOCKTLINEAPP (ptLineApp);

            if (ptLineClient->pNextSametLineApp)
            {
                ptLineClient->pNextSametLineApp->pPrevSametLineApp =
                    ptLineClient->pPrevSametLineApp;
            }

            if (ptLineClient->pPrevSametLineApp)
            {
                ptLineClient->pPrevSametLineApp->pNextSametLineApp =
                    ptLineClient->pNextSametLineApp;
            }
            else
            {
                ptLineApp->ptLineClients = ptLineClient->pNextSametLineApp;
            }

            UNLOCKTLINEAPP (ptLineApp);
        }


        //
        // Grab the tLine's mutex & start munging.  Note that we don't
        // have to worry about dup-ing the mutex here because we know
        // it's valid & won't get closed before we release it.
        //

        ptLine = ptLineClient->ptLine;
        hMutex = ptLine->hMutex;
        WaitForSingleObject (hMutex, INFINITE);


        //
        // If client had call hub tracking enabled then adjust tLine
        //

        if (ptLineClient->dwCurrentTracking)
        {
            --ptLine->dwNumCallHubTrackers;

            if ((ptLineClient->dwCurrentTracking &
                    LINECALLHUBTRACKING_PROVIDERLEVEL) &&
                (--ptLine->dwNumCallHubTrackersSPLevel == 0))
            {
                const LINECALLHUBTRACKINGINFO trackingInfo =
                {
                    sizeof (LINECALLHUBTRACKINGINFO),
                    sizeof (LINECALLHUBTRACKINGINFO),
                    sizeof (LINECALLHUBTRACKINGINFO),
                    0,
                    LINECALLHUBTRACKING_NONE
                };


                if (hProviderMutex)
                {
                    WaitForSingleObject (hProviderMutex, INFINITE);
                }
                if (ptLine->ptProvider->apfn[SP_LINESETCALLHUBTRACKING])
                {
                    CallSP2(
                        ptLine->ptProvider->apfn[SP_LINESETCALLHUBTRACKING],
                        "lineSetCallHubTracking",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptLine->hdLine,
                        (ULONG_PTR) &trackingInfo
                        );
                }
                if (hProviderMutex)
                {
                    ReleaseMutex (hProviderMutex);
                }
            }
        }


        //
        // If client registered as a proxy then unregister it
        //

        if (ptLineClient->dwPrivileges & LINEOPENOPTION_PROXY)
        {
            DWORD i;

            for(
                i = LINEPROXYREQUEST_SETAGENTGROUP;
                i <= LINEPROXYREQUEST_LASTVALUE;
                i++
                )
            {
                if (ptLine->apProxys[i] == ptLineClient)
                {
                    //
                    // Alert other clients that a proxy close has occured
                    //

                    LOG((TL_INFO, "tell clients proxy %02X closed", i));

                    dwProxyCloseMsgs |= (1 << (i - 1));

                    ptLine->apProxys[i] = NULL;
                }
            }
        }


        //
        //
        //

        if (ptLineClient->dwExtVersion)
        {
            if ((--ptLine->dwExtVersionCount) == 0)
            {
                if (hProviderMutex)
                {
                    WaitForSingleObject (hProviderMutex, INFINITE);
                }
                if (ptLine->ptProvider->apfn[SP_LINESELECTEXTVERSION])
                {
                    CallSP2(
                        ptLine->ptProvider->apfn[SP_LINESELECTEXTVERSION],
                        "lineSelectExtVersion",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptLine->hdLine,
                        (DWORD) 0
                        );
                }
                if (hProviderMutex)
                {
                    ReleaseMutex (hProviderMutex);
                }

                ptLine->dwExtVersion = 0;
            }
        }


        //
        // Remove the tLineClient from the tLine's list & decrement
        // the number of opens
        //

        if (ptLineClient->pNextSametLine)
        {
            ptLineClient->pNextSametLine->pPrevSametLine =
                ptLineClient->pPrevSametLine;
        }

        if (ptLineClient->pPrevSametLine)
        {
            ptLineClient->pPrevSametLine->pNextSametLine =
                ptLineClient->pNextSametLine;
        }
        else
        {
            ptLine->ptLineClients = ptLineClient->pNextSametLine;
        }

        ptLine->dwNumOpens--;


        //
        // See if we need to reset the monitored media modes or close
        // the tLine (still hanging on the the mutex)
        //

        if (ptLine->dwKey == TLINE_KEY)
        {
            LOG((TL_INFO, "It's a line_key"));
            if (ptLine->ptLineClients)
            {
                LOG((TL_INFO, "...and there are still clients"));
                if (ptLine->dwOpenMediaModes && ptLineClient->dwMediaModes)
                {
                    DWORD           dwUnionMediaModes = 0;
                    PTLINECLIENT    ptLineClientTmp =
                                        ptLine->ptLineClients;


                    while (ptLineClientTmp)
                    {
                        if (ptLineClientTmp->dwPrivileges &
                                LINECALLPRIVILEGE_OWNER)
                        {
                            dwUnionMediaModes |=
                                ptLineClientTmp->dwMediaModes;
                        }

                        ptLineClientTmp = ptLineClientTmp->pNextSametLine;
                    }

                    if (dwUnionMediaModes != ptLine->dwOpenMediaModes)
                    {
                        LONG        lResult;

                        if (hProviderMutex)
                        {
                            WaitForSingleObject (hProviderMutex, INFINITE);
                        }
                        if (ptLine->ptProvider->apfn
                                [SP_LINESETDEFAULTMEDIADETECTION])
                        {
                            lResult = CallSP2(
                                ptLine->ptProvider->apfn
                                    [SP_LINESETDEFAULTMEDIADETECTION],
                                "lineSetDefaultMediaDetection",
                                SP_FUNC_SYNC,
                                (ULONG_PTR) ptLine->hdLine,
                                (DWORD) dwUnionMediaModes
                                );
                        }
                        else
                        {
                            lResult = LINEERR_OPERATIONUNAVAIL;
                        }
                        if (hProviderMutex)
                        {
                            ReleaseMutex (hProviderMutex);
                        }

                        ptLine->dwOpenMediaModes = dwUnionMediaModes;
                    }
                }

                bSendDevStateCloseMsg = TRUE;


                //
                // See if we need to reset the status msgs (if so, make
                // sure to check/set the busy flag & not to hold the
                // mutex while calling down to provider - see comments
                // in LSetStatusMessages)
                //

                if ((ptLineClient->dwLineStates & ~LINEDEVSTATE_REINIT) ||
                    ptLineClient->dwAddressStates)
                {
                    DWORD           dwUnionLineStates = 0,
                                    dwUnionAddressStates = 0;
                    PTLINECLIENT    ptLC;


                    while (ptLine->dwBusy)
                    {
                        BOOL    bClosed = TRUE;


                        ReleaseMutex (hMutex);
                        Sleep (50);
                        WaitForSingleObject (hMutex, INFINITE);

                        try
                        {
                            if (ptLine->dwKey == TLINE_KEY)
                            {
                                bClosed = FALSE;
                            }
                        }
                        myexcept
                        {
                            // do nothing
                        }

                        if (bClosed)
                        {
                            goto releasMutex;
                        }
                    }

                    for(
                        ptLC = ptLine->ptLineClients;
                        ptLC;
                        ptLC = ptLC->pNextSametLine
                        )
                    {
                        dwUnionLineStates    |= ptLC->dwLineStates;
                        dwUnionAddressStates |= ptLC->dwAddressStates;
                    }

                    if ((dwUnionLineStates != ptLine->dwUnionLineStates)  ||
                        (dwUnionAddressStates != ptLine->dwUnionAddressStates))
                    {
                        if (ptLine->ptProvider->apfn[SP_LINESETSTATUSMESSAGES])
                        {
                            LONG        lResult;
                            TSPIPROC    pfn;
                            HDRVLINE    hdLine = ptLine->hdLine;


                            pfn = ptLine->ptProvider->
                                apfn[SP_LINESETSTATUSMESSAGES];

                            ptLine->dwBusy = 1;

                            ReleaseMutex (hMutex);

                            if (hProviderMutex)
                            {
                                WaitForSingleObject (hProviderMutex, INFINITE);
                            }
                            lResult = CallSP3(
                                pfn,
                                "lineSetStatusMessages",
                                SP_FUNC_SYNC,
                                (ULONG_PTR) hdLine,
                                (DWORD) dwUnionLineStates,
                                (DWORD) dwUnionAddressStates
                                );
                                if (hProviderMutex)
                                {
                                    ReleaseMutex (hProviderMutex);
                                }

                            WaitForSingleObject (hMutex, INFINITE);

                            try
                            {
                                if (ptLine->dwKey == TLINE_KEY)
                                {
                                    ptLine->dwBusy = 0;

                                    if (lResult == 0)
                                    {
                                        ptLine->dwUnionLineStates =
                                            dwUnionLineStates;
                                        ptLine->dwUnionAddressStates =
                                            dwUnionAddressStates;
                                    }
                                }
                            }
                            myexcept
                            {
                                // do nothing
                            }
                        }
                    }
                }
            }
            else
            {
                //
                // This was the last client so destroy the tLine too
                //

                LOG((TL_INFO, "...and it's the last one out"));

                ReleaseMutex (hMutex);
                hMutex = NULL;
                DestroytLine (ptLine, FALSE); // conditional destroy
            }
        }

releasMutex:

        if (hMutex)
        {
            ReleaseMutex (hMutex);
        }


        //
        // Now that the mutex is released send any necessary msgs
        //

        if (bSendDevStateCloseMsg)
        {
            DWORD   dwOrdinal, dwBitFlag;


            SendMsgToLineClients(
                ptLine,
                NULL,
                LINE_LINEDEVSTATE,
                LINEDEVSTATE_CLOSE,
                0,
                0
                );

            for(
                dwOrdinal = LINEPROXYREQUEST_SETAGENTGROUP, dwBitFlag = 1;
                dwProxyCloseMsgs != 0;
                dwOrdinal++, dwBitFlag <<= 1)
            {
                if (dwProxyCloseMsgs & dwBitFlag)
                {
                    SendMsgToLineClients(
                        ptLine,
                        NULL,
                        LINE_PROXYSTATUS,
                        LINEPROXYSTATUS_CLOSE,
                        dwOrdinal,              // LINEPROXYREQUEST_xx
                        0
                        );

                    dwProxyCloseMsgs ^= dwBitFlag;
                }
            }
        }


        //
        // Complete any remaining
        // proxy requests
        //

        if (ptLineClient->dwPrivileges & LINEOPENOPTION_PROXY)
        {
            PASYNCREQUESTINFO   pAsyncRequestInfo =
                                    ptLineClient->pPendingProxyRequests,
                                pNextAsyncRequestInfo;


            while (pAsyncRequestInfo)
            {
                pNextAsyncRequestInfo = (PASYNCREQUESTINFO)
                    pAsyncRequestInfo->dwParam5;

                pAsyncRequestInfo->dwKey = TASYNC_KEY;

                CompletionProc (pAsyncRequestInfo, LINEERR_OPERATIONUNAVAIL);

                DereferenceObject(
                    ghHandleTable,
                    pAsyncRequestInfo->dwLocalRequestID,
                    1
                    );

                pAsyncRequestInfo = pNextAsyncRequestInfo;
            }
        }


        //
        // Free resources
        //

        if (ptLineClient->aNumRings)
        {
            ServerFree (ptLineClient->aNumRings);
        }


        //
        // Decrement reference count by two to remove the initial
        // reference & the reference above
        //

        DereferenceObject (ghHandleTable, hLine, 2);
    }
    else
    {
        DereferenceObject (ghHandleTable, hLine, 1);

        LOG((TL_ERROR, "DestroytLineClient: WaitForExclLineClientAccess failed!"));
    }

}


LONG
PASCAL
DestroytLineApp(
    HLINEAPP    hLineApp
    )
{
    PTCLIENT    ptClient;
    PTLINEAPP   ptLineApp;


    LOG((TL_TRACE,  "DestroytLineApp: enter, hLineApp=x%x", hLineApp));


    if (!(ptLineApp = ReferenceObject (ghHandleTable, hLineApp, TLINEAPP_KEY)))
    {
        return (TapiGlobals.dwNumLineInits ?
                    LINEERR_INVALAPPHANDLE : LINEERR_UNINITIALIZED);
    }


    //
    // See if this this is a valid tLineApp, & if so grab the lock
    // and mark it as bad, then continue teardown.  Else, another
    // thread is in the processing of tearing down this tLineApp,
    // so return.
    //

    LOCKTLINEAPP (ptLineApp);

    if (ptLineApp->dwKey != TLINEAPP_KEY)
    {
        UNLOCKTLINEAPP (ptLineApp);
        DereferenceObject (ghHandleTable, hLineApp, 1);
        return (TapiGlobals.dwNumPhoneInits ?
                    LINEERR_INVALAPPHANDLE : LINEERR_UNINITIALIZED);
    }

    ptLineApp->dwKey = INVAL_KEY;
    ptClient = (PTCLIENT) ptLineApp->ptClient;


    //
    // Destroy all the tLineClients.  Note that we want to grab the
    // lock each time we reference the list of tLineClient's, since
    // another thread might be destroying a tLineClient too.
    //

    {
        HLINE   hLine;


destroy_tLineClients:

        hLine = (ptLineApp->ptLineClients ?
            ptLineApp->ptLineClients->hLine : (HLINE) 0);

        UNLOCKTLINEAPP (ptLineApp);

        if (hLine)
        {
            DestroytLineClient (hLine);
            LOCKTLINEAPP (ptLineApp);
            goto destroy_tLineClients;
        }
    }


    //
    // Remove tLineApp from tClient's list. Note that we don't
    // have to worry about dup-ing the mutex here because we know
    // it's valid & won't get closed before we release it.
    //

    LOCKTCLIENT (ptClient);

    if (ptLineApp->pNext)
    {
        ptLineApp->pNext->pPrev = ptLineApp->pPrev;
    }

    if (ptLineApp->pPrev)
    {
        ptLineApp->pPrev->pNext = ptLineApp->pNext;
    }
    else
    {
        ptClient->ptLineApps = ptLineApp->pNext;
    }


    //
    // Clean up any existing generic dialog instances if this is the
    // last tLineApp on this tClient
    //

    if (ptClient->pGenericDlgInsts && ptClient->ptLineApps == NULL)
    {
        PTAPIDIALOGINSTANCE         pGenericDlgInst =
                                        ptClient->pGenericDlgInsts,
                                    pNextGenericDlgInst;

        TAPI32_MSG                  params;

        while (pGenericDlgInst)
        {
            pNextGenericDlgInst = pGenericDlgInst->pNext;
            params.u.Req_Func = 0;
            params.Params[0] = pGenericDlgInst->htDlgInst;
            params.Params[1] = LINEERR_OPERATIONFAILED;

            FreeDialogInstance(
                ptClient,
                (PFREEDIALOGINSTANCE_PARAMS) &params,
                sizeof (params),
                NULL,
                NULL
                );

            pGenericDlgInst = pNextGenericDlgInst;
        }
    }

    UNLOCKTCLIENT (ptClient);


    //
    // Decrement total num inits & see if we need to go thru shutdown
    //

    TapiEnterCriticalSection (&TapiGlobals.CritSec);

    //assert(TapiGlobals.dwNumLineInits != 0);

    TapiGlobals.dwNumLineInits--;


    if ((TapiGlobals.dwNumLineInits == 0) &&
        (TapiGlobals.dwNumPhoneInits == 0) &&
        !(TapiGlobals.dwFlags & TAPIGLOBALS_SERVER))
    {
        ServerShutdown();
        gbServerInited = FALSE;
    }

    TapiLeaveCriticalSection (&TapiGlobals.CritSec);


    //
    // Check to see if this tLineApp is a registered request
    // recipient, and if so do the appropriate munging
    //

    {
        BOOL               bResetHighestPriorityRequestRecipient;
        PTREQUESTRECIPIENT pRequestRecipient;


        if ((pRequestRecipient = ptLineApp->pRequestRecipient))
        {
            EnterCriticalSection (&gPriorityListCritSec);

            bResetHighestPriorityRequestRecipient =
                (TapiGlobals.pHighestPriorityRequestRecipient ==
                    pRequestRecipient ? TRUE : FALSE);

            if (pRequestRecipient->pNext)
            {
                pRequestRecipient->pNext->pPrev = pRequestRecipient->pPrev;
            }

            if (pRequestRecipient->pPrev)
            {
                pRequestRecipient->pPrev->pNext = pRequestRecipient->pNext;
            }
            else
            {
                TapiGlobals.pRequestRecipients = pRequestRecipient->pNext;
            }

            if (bResetHighestPriorityRequestRecipient)
            {
                TapiGlobals.pHighestPriorityRequestRecipient =
                    GetHighestPriorityRequestRecipient();

                if (TapiGlobals.pRequestMakeCallList)
                {
                    if (TapiGlobals.pHighestPriorityRequestRecipient)
                    {
                        NotifyHighestPriorityRequestRecipient();
                    }

                     else
                    {
                        //
                        // We couldn't start a request recipient so
                        // nuke all pending request make calls
                        //

                        PTREQUESTMAKECALL   pRequestMakeCall,
                                            pNextRequestMakeCall;


                        pRequestMakeCall =
                            TapiGlobals.pRequestMakeCallList;

                        TapiGlobals.pRequestMakeCallList    =
                        TapiGlobals.pRequestMakeCallListEnd = NULL;

                        while (pRequestMakeCall)
                        {
                            pNextRequestMakeCall =
                                pRequestMakeCall->pNext;
                            ServerFree (pRequestMakeCall);
                            pRequestMakeCall =  pNextRequestMakeCall;
                        }

                        LOG((TL_INFO,
                            "DestroytLineApp: deleting pending " \
                                "MakeCall requests"
                            ));
                    }
                }
            }

            LeaveCriticalSection (&gPriorityListCritSec);
            ServerFree (pRequestRecipient);
        }
    }


    //
    // Decrement reference count by two to remove the initial
    // reference & the reference above
    //

    DereferenceObject (ghHandleTable, hLineApp, 2);

    return 0;
}


BOOL
FillupACountryEntry(
    HKEY                hKey,
    PBYTE               pcl,
    LPLINECOUNTRYENTRY  pce,
    PBYTE               *ppVarOffset
    )
{
    PBYTE  pVarOffset = *ppVarOffset;
    DWORD  dwSize;
    DWORD  dwType;
    LONG   lTemp;


    dwSize = sizeof(pce->dwCountryCode);

    lTemp = RegQueryValueEx(
                          hKey,
                          TEXT("CountryCode"),
                          NULL,
                          &dwType,
                          (LPBYTE)&(pce->dwCountryCode),
                          &dwSize
                        );

    //
    // If we failed to get the country code, the rest of this work
    // is meaningless...
    //
    if ( ERROR_SUCCESS == lTemp )
    {
        //
        // Read the country name string resource ID
        //
        dwSize = sizeof(DWORD);
        lTemp = RegQueryValueEx(
                          hKey,
                          gszNameResW,
                          NULL,
                          &dwType,
                          pVarOffset,
                          &dwSize
                        );


        if ( ERROR_SUCCESS == lTemp )
        {
            pce->dwCountryNameOffset = (DWORD) (pVarOffset - pcl);
            pce->dwCountryNameSize = dwSize;

            pVarOffset += dwSize;
        }

        dwSize = MAXLEN_RULE * sizeof(WCHAR);
        // Here we need to read a wide string because this is our packed structure
        // that will eventually get returned to the client and these are WCHAR always.
        lTemp = TAPIRegQueryValueExW(
                          hKey,
                          gszSameAreaRuleW,
                          NULL,
                          &dwType,
                          pVarOffset,
                          &dwSize
                        );

        if ( ERROR_SUCCESS == lTemp )
        {
            pce->dwSameAreaRuleOffset = (DWORD) (pVarOffset - pcl);
            pce->dwSameAreaRuleSize = dwSize;

            pVarOffset += dwSize;
        }


        dwSize = MAXLEN_RULE * sizeof(WCHAR);
        // Here we need to read a wide string because this is our packed structure
        // that will eventually get returned to the client and these are WCHAR always.
        lTemp = TAPIRegQueryValueExW(
                          hKey,
                          gszLongDistanceRuleW,
                          NULL,
                          &dwType,
                          pVarOffset,
                          &dwSize
                        );

        if ( ERROR_SUCCESS == lTemp )
        {
            pce->dwLongDistanceRuleOffset = (DWORD) (pVarOffset - pcl);
            pce->dwLongDistanceRuleSize = dwSize;

            pVarOffset += dwSize;
        }


        dwSize = MAXLEN_RULE * sizeof(WCHAR);
        // Here we need to read a wide string because this is our packed structure
        // that will eventually get returned to the client and these are WCHAR always.
        lTemp = TAPIRegQueryValueExW(
                          hKey,
                          gszInternationalRuleW,
                          NULL,
                          &dwType,
                          pVarOffset,
                          &dwSize
                        );

        if ( ERROR_SUCCESS == lTemp )
        {
            pce->dwInternationalRuleOffset =  (DWORD) (pVarOffset - pcl);
            pce->dwInternationalRuleSize = dwSize;

            pVarOffset += dwSize;
        }


        *ppVarOffset = pVarOffset;
    }

    return TRUE;
}


BOOL
BuildCountryRegistryListFromRCW(
    void
    )
{
    HKEY        hKey = NULL, hKey2;
    DWORD       dwDisposition, dwNextCountryID, dw, dwNextCountryGroup, dwCountryGroupID;
    TCHAR       sz[256];
    TCHAR       sz1[256];
    LONG        err;


    if (RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        gszRegKeyTelephony,
        0,
        KEY_READ,
        &hKey2
        ) != ERROR_SUCCESS)
    {
        goto ExitHere;
    }

    err = RegCreateKeyEx(
        hKey2,
        TEXT("Country List"),
        0,
        NULL,
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        NULL,
        &hKey,
        &dwDisposition
        );


    RegCloseKey (hKey2);

    if (err != ERROR_SUCCESS)
    {
        goto ExitHere;
    }

    dwNextCountryID = 1;

    while (dwNextCountryID)
    {
        if (LoadString(
                ghInstance,
                RC_COUNTRY_ID_BASE + dwNextCountryID,
                sz,
                ARRAYSIZE(sz)
                )  > 0 &&

            LoadString(
                ghInstance,
                RC_COUNTRY_NAME_BASE + dwNextCountryID,
                sz1,
                ARRAYSIZE(sz1) 
                ) > 0
             )
        {
            TCHAR szCountryKey[20];
            PTSTR p;
            PTSTR p2;


            wsprintf( szCountryKey, TEXT("%ld"), dwNextCountryID );

            if (RegCreateKeyEx(
                hKey,
                szCountryKey,
                0,
                NULL,
                REG_OPTION_NON_VOLATILE,
                KEY_ALL_ACCESS,
                NULL,
                &hKey2,
                &dwDisposition
                ) != ERROR_SUCCESS)
            {
                goto ExitHere;
            }

            //
            // Set the country name and resource ID in registry
            //
            RegSetValueEx(
                hKey2,
                gszNameW,
                0,
                REG_SZ,
                (LPBYTE) sz1,
                (DWORD) ( (_tcslen(sz1) + 1) * sizeof(TCHAR) )
                );


            dw = RC_COUNTRY_NAME_BASE + dwNextCountryID;
            RegSetValueEx(
                hKey2,
                gszNameResW,
                0,
                REG_DWORD,
                (LPBYTE) &dw,
                sizeof(DWORD)
                );

//RC_COUNTRY_ID_BASE + 1 "1,101,""G"","" 1FG"",""011EFG"""


            p = sz;

            //
            // Get the countryID
            //

            dw = _ttol (p);

            RegSetValueEx(
                hKey2,
                TEXT("CountryCode"),
                0,
                REG_DWORD,
                (LPBYTE)&dw,
                sizeof(DWORD)
                );


            p = _tcschr( p, TEXT(',') ) + 1;
            dwNextCountryID = _ttol( p );

            p  = _tcschr( p, TEXT('"') ) + 1;  // Point to start of rule
            p2 = _tcschr( p, TEXT('"') );           // Point to end of rule
            *p2 = TEXT('\0');

            RegSetValueEx(
                hKey2,
                gszSameAreaRuleW,
                0,
                REG_SZ,
                (LPBYTE) p,
                (DWORD) ((PBYTE) p2 - (PBYTE) p) + sizeof(TCHAR)
                );

            p  = _tcschr( p2 + 1, TEXT('"') ) + 1;  // Point to start of rule
            p2 = _tcschr( p, TEXT('"') );           // Point to end of rule
            *p2 = TEXT('\0');

            RegSetValueEx(
                hKey2,
                gszLongDistanceRuleW,
                0,
                REG_SZ,
                (LPBYTE) p,
                (DWORD) ((PBYTE) p2 - (PBYTE) p) + sizeof(TCHAR)
                );

            p  = _tcschr( p2 + 1, TEXT('"') ) + 1;  // Point to start of rule
            p2 = _tcschr( p, TEXT('"') );           // Point to end of rule
            *p2 = TEXT('\0');

            RegSetValueEx(
                hKey2,
                gszInternationalRuleW,
                0,
                REG_SZ,
                (LPBYTE) p,
                (DWORD) ((PBYTE) p2 - (PBYTE) p) + sizeof(TCHAR)
                );


            RegCloseKey(hKey2);
        }
        else
        {
            dwNextCountryID = 0;
        }

    }

    //
    // Get the Country Groups
    // 

//RC_COUNTRY_GROUP_BASE + 1 "1,0,""594,590,596,262,33"""

    dwNextCountryGroup = 1;
    while (dwNextCountryGroup)
    {
        if (LoadString(
                ghInstance,
                RC_COUNTRY_GROUP_BASE + dwNextCountryGroup,
                sz,
                ARRAYSIZE(sz)

                )  > 0)
        {
            TCHAR szCountryKey[20];
            PTSTR p;
            PTSTR p2;

            p = sz;

            //
            // Get the country group ID
            //

            dwCountryGroupID = _ttol (p);

            p = _tcschr( p, TEXT(',') ) + 1;
            dwNextCountryGroup = _ttol( p );


            p = _tcschr( p, TEXT('"') );
            p2 = _tcschr( p+1, TEXT('"') );
            *p2 = TEXT('\0');

            while( NULL != p && p+1 < p2)
            {
                wsprintf( szCountryKey, TEXT("%ld"), _ttol (p+1) );

                if (RegOpenKeyEx(
                    hKey,
                    szCountryKey,
                    0,
                    KEY_ALL_ACCESS,
                    &hKey2
                    ) != ERROR_SUCCESS)
                {
                    goto ExitHere;
                }


                RegSetValueEx(
                    hKey2,
                    gszCountryGroupW,
                    0,
                    REG_DWORD,
                    (LPBYTE)&dwCountryGroupID,
                    sizeof(DWORD)
                    );

                RegCloseKey(hKey2);
                
                p = _tcschr( p+1, TEXT(',') );
            }
        }
        else
        {
            dwNextCountryGroup = 0;
        }
    }

    //
    // Write the country list version to the registry
    //

    {
        DWORD dwCountryListVersion = TAPI_CURRENT_COUNTRY_LIST_VERSION;

        RegSetValueEx(
            hKey,
            gszCountryListVersionW,
            0,
            REG_DWORD,
            (LPBYTE) &dwCountryListVersion,
            (DWORD) sizeof (dwCountryListVersion)
            );
    }

ExitHere:
    if (hKey)
    {
        RegCloseKey (hKey);
    }

    return TRUE;
}


BOOL
DeleteAllSubkeys(
    HKEY    hKey,
    DWORD   dwRecursionCount
    )
{
    //
    // Build a MULTISZ-style list of all the subkey names,
    // then delete them all.  This is because NT won't
    // let us (do it the easy way and) delete the parent key
    // while subkeys still exist.  Note also that we're not
    // allowed to delete subkeys while enumerating them.
    //

    HKEY    hSubkey;
    DWORD   i, dwTotalChars = 2048, dwUsedChars = 0;
    TCHAR   *p, *p2;


    //
    // If we're nested more than a few levels deep then someone
    // is probably doing some malicious registry munging to
    // see if we blow up - don't recurse any further.
    //

    if (dwRecursionCount > 5)
    {
        return TRUE;
    }


    //
    // Alloc a buffer to store subkey names
    //

    if (!(p = ServerAlloc (dwTotalChars * sizeof (TCHAR))))
    {
        return FALSE;
    }


    //
    // Build the list
    //

    for (i = 0;; i++)
    {
        DWORD       dwNumChars = dwTotalChars - dwUsedChars;
        FILETIME    fileTime;


        //
        // See if we need to grow the buffer first
        //

        if (dwNumChars < 256)
        {
            dwTotalChars *= 2;

            if (!(p2 = LocalAlloc (LPTR, dwTotalChars * sizeof (TCHAR))))
            {
                ServerFree (p);
                return FALSE;
            }

            CopyMemory (p2, p, dwUsedChars * sizeof (TCHAR));

            ServerFree (p);

            p = p2;
        }

        if (RegEnumKeyEx(
                hKey,
                i,
                p + dwUsedChars,
                &dwNumChars,
                NULL,
                NULL,
                NULL,
                &fileTime

                ) != ERROR_SUCCESS)
        {
            p[dwUsedChars] = TEXT('\0');    // the final (double) NULL
            break;
        }


        //
        // Append a terminating NULL if there wasn't one
        //

        if (p[dwUsedChars + dwNumChars - 1] != TEXT('\0'))
        {
            p[dwUsedChars + dwNumChars] = TEXT('\0');
            dwNumChars++;
        }

        dwUsedChars += dwNumChars;
    }


    //
    // Now nuke all the subkeys in the list (make sure to nuke
    // any sub-subkeys first)
    //

    for (p2 = p; *p2 != TEXT('\0'); p2 += lstrlen (p2) + 1)
    {
        if (RegOpenKeyEx(
                hKey,
                p2,
                0,
                KEY_ALL_ACCESS,
                &hSubkey

                ) == ERROR_SUCCESS)
        {
            DeleteAllSubkeys (hSubkey, dwRecursionCount + 1);

            RegCloseKey (hSubkey);
        }

        RegDeleteKey (hKey, p2);
    }

    ServerFree (p);

    return TRUE;
}


BOOL
BuildCountryListCache(
    void
    )
{
    //
    // The following is our "last resort" country list, i.e. the one we
    // use of we get errors trying to build the country list below
    //

    static LINECOUNTRYLIST defCountryList =
    {
        sizeof(LINECOUNTRYLIST),    // dwTotalSize
        sizeof(LINECOUNTRYLIST),    // dwNeededSize
        sizeof(LINECOUNTRYLIST),    // dwUsedSize
        0,                          // dwNumCountries
        0,                          // dwCountryListSize
        0                           // dwCountryListOffset
    };
    BOOL bResult = TRUE;
    UINT i;


    if (!gpCountryList)
    {
        TCHAR sz[256];
        DWORD dwSize;
        DWORD dwListSize;
        DWORD dwCountryId, dwCountryListVersion, dwType;
        PBYTE pTempCountryList;
        LPLINECOUNTRYENTRY pce;
        LPLINECOUNTRYENTRY pcePrev = NULL;
        HKEY hKey;
        HKEY hKeyTemp;
        UINT uNumCountries;
        PBYTE pVarOffset;


        #define INITIAL_COUNTRY_COUNT 256

        dwListSize = sizeof(LINECOUNTRYLIST) +
               INITIAL_COUNTRY_COUNT * (sizeof(LINECOUNTRYENTRY) + 64);

        if ( NULL == (pTempCountryList = ServerAlloc(dwListSize)) )
        {
            bResult = FALSE;
            LOG((TL_ERROR, "Mem alloc failed for country list!1 (0x%lx", dwListSize));
            goto BuildCountryListCache_return;
        }


        //
        // Make sure the list is more-or-less there first
        //

        if (RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                gszRegKeyTelephony,
                0,
                KEY_READ,
                &hKey

                ) != ERROR_SUCCESS)
        {
            bResult = FALSE;
            ServerFree (pTempCountryList);
            goto BuildCountryListCache_return;
        }

        dwCountryListVersion = 0;

        if (RegOpenKeyEx(
                hKey,
                TEXT("Country List"),
                0,
                KEY_READ,
                &hKeyTemp

                ) == ERROR_SUCCESS)
        {
            dwSize = sizeof(DWORD);
            TAPIRegQueryValueExW(
                hKeyTemp,
                gszCountryListVersionW,
                NULL,
                &dwType,
                (LPBYTE) &dwCountryListVersion,
                &dwSize
                );

             RegCloseKey (hKeyTemp);
        }


        //
        // If the country list version is < the version in our resource
        // file  OR
        // if a read on the key for country code 1 (these united states)
        // fails, we'll assume the country list in the registry is toasted
        //

        if ((dwCountryListVersion < TAPI_CURRENT_COUNTRY_LIST_VERSION) ||

             RegOpenKeyEx(
                 hKey,
                 TEXT("Country List\\1"),
                 0,
                 KEY_READ,
                 &hKeyTemp
                 ))
        {
            //
            // Nuke any existing subkeys & (re)create it
            //

            if (RegOpenKeyEx(
                    hKey,
                    TEXT("Country List"),
                    0,
                    KEY_ALL_ACCESS,
                    &hKeyTemp

                    ) == ERROR_SUCCESS)
            {
                 DeleteAllSubkeys (hKeyTemp, 0);

                 RegCloseKey (hKeyTemp);
            }

            BuildCountryRegistryListFromRCW();
        }
        else
        {
            RegCloseKey( hKeyTemp );
        }

        RegCloseKey( hKey );


        //
        // In any case, the list is now good
        //

        if (RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                gszRegKeyTelephony,
                0,
                KEY_READ,
                &hKeyTemp

                ) != ERROR_SUCCESS)
        {
            bResult = FALSE;
            ServerFree (pTempCountryList);
            goto BuildCountryListCache_return;
        }

        if (RegOpenKeyEx(
                hKeyTemp,
                TEXT("Country List"),
                0,
                KEY_READ,
                &hKey

                ) != ERROR_SUCCESS)
        {
            RegCloseKey( hKeyTemp );
            bResult = FALSE;
            ServerFree (pTempCountryList);
            goto BuildCountryListCache_return;
        }

        RegCloseKey( hKeyTemp );


        //
        // Enum through the country keys and make sure there's enough room
        // for all of the LINECOUNTRYENTRYs
        //

        pce = (LPLINECOUNTRYENTRY)(pTempCountryList +
                                     sizeof(LINECOUNTRYLIST));

        //
        // Make pretend we already have a previous linecountryentry so we
        // don't have to do an 'if' in the loop every time just for the
        // special case of the first time.  (The correct number gets put
        // into the field the second time through the loop.)
        //

        pcePrev = pce;

        dwSize = ARRAYSIZE(sz);

        uNumCountries = 0;

        while (RegEnumKeyEx(
                    hKey,
                    uNumCountries,
                    sz,
                    &dwSize,
                    NULL,
                    NULL,
                    NULL,
                    NULL

                    ) == 0)
        {
           if ((sizeof(LINECOUNTRYLIST) +
                   (sizeof(LINECOUNTRYENTRY) * uNumCountries))  >  dwListSize)
           {
               PBYTE p;
               UINT uOldSize;


               uOldSize = dwListSize;

               //
               // alloc a new space
               //

               dwListSize = sizeof(LINECOUNTRYLIST) +
                                (
                                   (sizeof(LINECOUNTRYENTRY) + 64)
                                     * (uNumCountries + 25)
                                );

               p = ServerAlloc( dwListSize );

               if ( NULL == p )
               {
                   bResult = FALSE;
                   LOG((TL_ERROR, "Mem alloc failed for country list!2 (0x%lx", dwListSize));
                   ServerFree( pTempCountryList );
                   RegCloseKey (hKey);
                   goto BuildCountryListCache_return;
               }

               CopyMemory(
                   p,
                   pTempCountryList,
                   (LPBYTE)pce - pTempCountryList
                   );

               ServerFree( pTempCountryList );

               pTempCountryList = p;

               pce = (LPLINECOUNTRYENTRY)((LPBYTE)p + uOldSize);
           }

           dwCountryId = _ttol( sz );

           pce->dwCountryID = dwCountryId;

           pcePrev->dwNextCountryID = dwCountryId;


           // Prepare for next trip through the loop

           pcePrev = pce;

           pce++;

           uNumCountries++;

           dwSize = ARRAYSIZE(sz);  // need to set every time :-(
        }

        // Allocate the country groups global
        gpCountryGroups = (LPDWORD) ServerAlloc( uNumCountries * sizeof (DWORD) );
        if (gpCountryGroups)
            memset(gpCountryGroups, 0, uNumCountries * sizeof (DWORD));

        pcePrev->dwNextCountryID = 0;

        
        //
        // Now go through and get all of the associated strings
        //

        pce = (LPLINECOUNTRYENTRY)
                (pTempCountryList + sizeof(LINECOUNTRYLIST));

        pVarOffset = pTempCountryList +
                                 sizeof(LINECOUNTRYLIST) +
                                 (sizeof(LINECOUNTRYENTRY) * uNumCountries);

        i = 0;

        while ( i < uNumCountries )
        {
            HKEY hKey2;


//-->      if it can't fix MAX_SPACE, realloc it
            if ( ((DWORD)(pVarOffset - pTempCountryList) +
                         ((MAXLEN_NAME +
                         MAXLEN_RULE +
                         MAXLEN_RULE +
                         MAXLEN_RULE +
                         100) * sizeof(WCHAR)))    // mmmm... fudge...
                    > dwListSize )
            {
               PBYTE p;

               //
               // alloc a new space
               //

               dwListSize += 1024;

               p = ServerAlloc( dwListSize );

               if ( NULL == p )
               {
                   bResult = FALSE;
                   LOG((TL_ERROR, "Mem alloc failed for country list!3 (0x%lx", dwListSize));
                   ServerFree( pTempCountryList );
                   RegCloseKey (hKey);
                   goto BuildCountryListCache_return;
               }

               CopyMemory(
                   p,
                   pTempCountryList,
                   (LPBYTE)pce - pTempCountryList
                   );

               pVarOffset = (LPVOID)(p +
                               (UINT)( pVarOffset - pTempCountryList));

               ServerFree( pTempCountryList );

               pTempCountryList = p;

               pce = (LPLINECOUNTRYENTRY)
                     (pTempCountryList + sizeof(LINECOUNTRYLIST) +
                                 ( sizeof(LINECOUNTRYENTRY) * i ));
            }

            wsprintf( sz, TEXT("%ld"), pce->dwCountryID);

            if (RegOpenKeyEx (hKey, sz, 0, KEY_READ, &hKey2) == ERROR_SUCCESS)
            {
                FillupACountryEntry(
                    hKey2,
                    pTempCountryList,
                    pce,
                    &pVarOffset
                    );

                // fill the country group

                if (gpCountryGroups)
                {
                    DWORD dwType;
                    DWORD dwSize = sizeof (DWORD);

                    if (ERROR_SUCCESS != RegQueryValueEx(
                            hKey2,
                            gszCountryGroupW,
                            NULL,
                            &dwType,
                            (LPBYTE)(gpCountryGroups + i),
                            &dwSize
                            )                           ||
                         dwType != REG_DWORD
                        )
                    {
                        gpCountryGroups[ i ] = 0;
                    }
                }


                RegCloseKey (hKey2);
            }
            
            pce++;
            i++;
        }

        RegCloseKey( hKey );

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwTotalSize =
                      (DWORD)(pVarOffset - pTempCountryList);

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwNeededSize =
                      (DWORD)(pVarOffset - pTempCountryList);

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwUsedSize =
                      (DWORD)(pVarOffset - pTempCountryList);

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwNumCountries = uNumCountries;

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwCountryListSize =
                                 uNumCountries * sizeof(LINECOUNTRYENTRY);

        ((LPLINECOUNTRYLIST)pTempCountryList)->dwCountryListOffset =
                                          sizeof(LINECOUNTRYLIST);

        gpCountryList = (LPLINECOUNTRYLIST)pTempCountryList;
    }

BuildCountryListCache_return:

    if (bResult == FALSE)
    {
        gpCountryList = &defCountryList;
        ServerFree( gpCountryGroups );
        gpCountryGroups = NULL;
    }

    return bResult;
}


LPLINECOUNTRYLIST
BuildCountryList(
    void
    )
{
    LPLINECOUNTRYENTRY  pCtryEntry, pCtryEntryGlobal;
    LPLINECOUNTRYLIST   pCtryList;
    DWORD               dwListSize;
    DWORD               dwIdx;
    DWORD               dwResourceId;
    DWORD               dwNameSize;
    DWORD               dwNeededSize;
    DWORD               dwTotalSize;
    LPBYTE              pVarOffset;
    BOOL                bResult = TRUE;
    WCHAR               sz[MAXLEN_NAME];

    if (!gpCountryList)
    {
        return NULL;
    }

    //
    // Allocate memory, make room for country names
    //
    dwTotalSize = gpCountryList->dwUsedSize + 
                  gpCountryList->dwNumCountries * 
                           ( MAXLEN_NAME * sizeof(WCHAR) - sizeof(DWORD) );
    pCtryList = ServerAlloc (dwTotalSize);

    if (!pCtryList)
    {
        return NULL;
    }

    //
    // Fill the buffer
    // 
    pCtryEntry = (LPLINECOUNTRYENTRY)((LPBYTE) pCtryList + sizeof(LINECOUNTRYLIST));
    pCtryEntryGlobal = (LPLINECOUNTRYENTRY)((LPBYTE) gpCountryList + sizeof(LINECOUNTRYLIST));
    pVarOffset = (LPBYTE)pCtryList + sizeof(LINECOUNTRYLIST) + 
                    sizeof(LINECOUNTRYENTRY) * gpCountryList->dwNumCountries;
    dwNeededSize = sizeof(LINECOUNTRYLIST) + 
                    sizeof(LINECOUNTRYENTRY) * gpCountryList->dwNumCountries;

    for( dwIdx = 0; dwIdx < gpCountryList->dwNumCountries; 
            dwIdx++, pCtryEntry++, pCtryEntryGlobal++ )
    {
        pCtryEntry->dwCountryCode = pCtryEntryGlobal->dwCountryCode;
        pCtryEntry->dwCountryID = pCtryEntryGlobal->dwCountryID;
        pCtryEntry->dwNextCountryID = pCtryEntryGlobal->dwNextCountryID;


        //
        // The name field has the resource string ID
        // Need to load the actual string 
        //
            
        CopyMemory(
            &dwResourceId,
            (LPBYTE)gpCountryList + pCtryEntryGlobal->dwCountryNameOffset,
            sizeof(DWORD)
            );

        if (0 == LoadStringW(
                            ghInstance,
                            dwResourceId,
                            sz,
                            ARRAYSIZE(sz)
                            )  
           )
        {
            bResult = FALSE;
            break;
        }
                        
        dwNameSize = (wcslen(sz) + 1) * sizeof(WCHAR);
        CopyMemory(
            pVarOffset,
            (LPBYTE)sz,
            dwNameSize
            );

        pCtryEntry->dwCountryNameSize = dwNameSize;

        pCtryEntry->dwCountryNameOffset = (DWORD)(pVarOffset - (LPBYTE)pCtryList);
        pVarOffset += dwNameSize;
        dwNeededSize += dwNameSize;


        CopyMemory(
            pVarOffset,
            (LPBYTE)gpCountryList + pCtryEntryGlobal->dwSameAreaRuleOffset,
            pCtryEntryGlobal->dwSameAreaRuleSize
            );

        pCtryEntry->dwSameAreaRuleSize = pCtryEntryGlobal->dwSameAreaRuleSize;
        pCtryEntry->dwSameAreaRuleOffset = (DWORD)(pVarOffset - (LPBYTE)pCtryList);
        pVarOffset += pCtryEntryGlobal->dwSameAreaRuleSize;
        dwNeededSize += pCtryEntryGlobal->dwSameAreaRuleSize;


        CopyMemory(
            pVarOffset,
            (LPBYTE)gpCountryList + pCtryEntryGlobal->dwLongDistanceRuleOffset,
            pCtryEntryGlobal->dwLongDistanceRuleSize
            );

        pCtryEntry->dwLongDistanceRuleSize = pCtryEntryGlobal->dwLongDistanceRuleSize;
        pCtryEntry->dwLongDistanceRuleOffset = (DWORD)(pVarOffset - (LPBYTE)pCtryList);
        pVarOffset += pCtryEntryGlobal->dwLongDistanceRuleSize;
        dwNeededSize += pCtryEntryGlobal->dwLongDistanceRuleSize;


        CopyMemory(
            pVarOffset,
            (LPBYTE)gpCountryList + pCtryEntryGlobal->dwInternationalRuleOffset,
            pCtryEntryGlobal->dwInternationalRuleSize
            );

        pCtryEntry->dwInternationalRuleSize = pCtryEntryGlobal->dwInternationalRuleSize;
        pCtryEntry->dwInternationalRuleOffset = (DWORD)(pVarOffset - (LPBYTE)pCtryList);
        pVarOffset += pCtryEntryGlobal->dwInternationalRuleSize;
        dwNeededSize += pCtryEntryGlobal->dwInternationalRuleSize;
        
    }
    
    if (!bResult)
    {
        ServerFree(pCtryList);
        pCtryList = NULL;
    }
    else
    {
        pCtryList->dwNeededSize = dwNeededSize;
        pCtryList->dwTotalSize = dwTotalSize;
        pCtryList->dwUsedSize = dwNeededSize;
        pCtryList->dwNumCountries = gpCountryList->dwNumCountries;
        pCtryList->dwCountryListSize = sizeof(LINECOUNTRYENTRY) * gpCountryList->dwNumCountries;
        pCtryList->dwCountryListOffset = sizeof(LINECOUNTRYLIST);
    }

    return pCtryList;
}

PTLINECLIENT
PASCAL
xxxGetHighestPriorityLineClient(
    TPOINTERLIST    *pLineClientList,
    DWORD           dwMediaModes,
    DWORD           dwAddressID,
    WCHAR          *pszPriorityList
    )
{
    BOOL            bFoundOwnerInPriorityList = FALSE;
    DWORD           i;
    WCHAR           *pszAppInPriorityList = NULL;
    WCHAR           *pszAppInPriorityListPrev = (WCHAR *) LongToPtr(0xffffffff);
    PTLINECLIENT    ptHiPriLineClient = (PTLINECLIENT) NULL;


    for (i = 0; i < pLineClientList->dwNumUsedEntries; i++)
    {
        PTLINECLIENT    ptLineClient = (PTLINECLIENT)
                            pLineClientList->aEntries[i];

        try
        {
            if (ptLineClient->dwPrivileges & LINECALLPRIVILEGE_OWNER)
            {
                BOOL            bMatch;


                bMatch = ((ptLineClient->dwMediaModes & dwMediaModes)
                    == dwMediaModes);

                if ( bMatch &&

                    // most common case, line opened for all addrs

                    ((ptLineClient->dwAddressID == 0xffffffff) ||


                    // line opened for single addr, check if match

                    (ptLineClient->dwAddressID == dwAddressID) ||


                    // called from lineHandoff, addr ID irrelevent

                    (dwAddressID == 0xffffffff)))
                {
                    if (pszPriorityList &&

                        (pszAppInPriorityList = wcsstr(
                            pszPriorityList,
                            ptLineClient->ptLineApp->pszModuleName
                            )))
                    {
                        //
                        // See if this app has higher pri
                        // than the previous app we found,
                        // and if so save the info
                        //

                        if (pszAppInPriorityList <= pszAppInPriorityListPrev)
                        {
                            ptHiPriLineClient = ptLineClient;

                            pszAppInPriorityListPrev  =
                                pszAppInPriorityList;

                            bFoundOwnerInPriorityList = TRUE;
                        }
                    }
                    else if (!bFoundOwnerInPriorityList)
                    {
                        ptHiPriLineClient = ptLineClient;
                    }
                }
            }
        }
        myexcept
        {
            // just continue
        }
    }

    return ptHiPriLineClient;
}


WCHAR *
GetPriorityListForMediaModes(
    DWORD   dwMediaModes
    )
{
    DWORD   dwCount;
    WCHAR   *pszPriorityList = NULL;


    if (TapiGlobals.dwUsedPriorityLists != 0)
    {
        //
        // Safely get a copy of the priority list (if any)
        // for this media mode
        //

        EnterCriticalSection (&gPriorityListCritSec);

        for(
            dwCount = 0;
            dwCount < TapiGlobals.dwUsedPriorityLists;
            dwCount++
            )
        {
            PRILISTSTRUCT PriList = TapiGlobals.pPriLists[dwCount];


            if ((dwMediaModes & PriList.dwMediaModes) == dwMediaModes)
            {
                if (PriList.pszPriList)
                {
                    if ((pszPriorityList = ServerAlloc( sizeof(WCHAR) *
                            (1 + lstrlenW(PriList.pszPriList))
                            )))
                    {
                        wcscpy (pszPriorityList, PriList.pszPriList);
                    }
                }

                break;
            }
        }

        LeaveCriticalSection (&gPriorityListCritSec);
    }

    return pszPriorityList;
}


PTLINECLIENT
PASCAL
GetHighestPriorityLineClient(
    PTLINE  ptLine,
    DWORD   dwMediaModes,
    DWORD   dwAddressID
    )
{
    WCHAR          *pszPriorityList = NULL;
    TPOINTERLIST    lineClientList, *pLineClientList = &lineClientList;
    PTLINECLIENT    ptHiPriLineClient = (PTLINECLIENT) NULL;
    DWORD           dwCount = 0, dwMask;


    if (GetLineClientListFromLine (ptLine, &pLineClientList) != 0)
    {
        return NULL;
    }


    //
    // If >1 media mode is specifed without the UNKNOWN bit being set
    // then we first want to see if there's any exact matches available,
    // that is, if there's an app which has opened the line with OWNER
    // privileges for all the specfied media modes. If so, then we'll
    // give privilege to that app immediately, rather than walking
    // through the media mode bits one-by-one as done below (the original
    // TAPI 1.x priority determination scheme).
    //

    if (!IsOnlyOneBitSetInDWORD (dwMediaModes) &&
        !(dwMediaModes & LINEMEDIAMODE_UNKNOWN))
    {
        pszPriorityList = GetPriorityListForMediaModes (dwMediaModes);

        ptHiPriLineClient = xxxGetHighestPriorityLineClient(
            pLineClientList,
            dwMediaModes,
            dwAddressID,
            pszPriorityList
            );

        if (pszPriorityList)
        {
            ServerFree (pszPriorityList);
        }
    }


    //
    // Step thru the list of line clients (youngest client at head
    // of list, oldest at tail) and look for the oldest & highest
    // priority owner.  Position in pri list takes precedence
    // over "age" of line client.
    //
    // To be considered for ownership a line client must have owner
    // privileges and be registered for (one of) the call's media
    // mode(s).  In addition, if the line client was opened with
    // the SINGLEADDRESS option and the calling function specified
    // a valid address ID (not 0xffffffff), the line client's single
    // address ID must match that which was passed in.
    //

    dwMask = LINEMEDIAMODE_UNKNOWN; // 0x00000002, smallest valid bit

    while (!ptHiPriLineClient  &&  dwMediaModes)
    {
        if (dwMask & dwMediaModes)
        {
            pszPriorityList = GetPriorityListForMediaModes (dwMask);

            ptHiPriLineClient = xxxGetHighestPriorityLineClient(
                pLineClientList,
                dwMask,
                dwAddressID,
                pszPriorityList
                );

            if (pszPriorityList)
            {
                ServerFree (pszPriorityList);
            }
        }

        dwMediaModes &= ~dwMask;
        dwMask <<= 1;
    }


    //
    // Free line client list iff appropriate
    //

    if (pLineClientList != &lineClientList)
    {
        ServerFree (pLineClientList);
    }

    return ptHiPriLineClient;
}


LONG
PASCAL
LineProlog(
    PTCLIENT    ptClient,
    DWORD       dwArgType,
    DWORD       dwArg,
    LPVOID      phdXxx,
    DWORD       dwPrivilege, // can be privilege or device id
    HANDLE     *phMutex,
    BOOL       *pbDupedMutex,
    DWORD       dwTSPIFuncIndex,
    TSPIPROC   *ppfnTSPI_lineXxx,
    PASYNCREQUESTINFO  *ppAsyncRequestInfo,
    DWORD       dwRemoteRequestID,
    DWORD      *pObjectToDereference,
    LPVOID     *pContext
#if DBG
    ,char      *pszFuncName
#endif
    )
{
    LONG        lResult = 0;
    DWORD       initContext;
    DWORD       openContext;
    ULONG_PTR   htXxx;
    PTPROVIDER  ptProvider;

#if DBG
    LOG((TL_TRACE,  "LineProlog: (line%s) enter", pszFuncName));
#else
    LOG((TL_TRACE, "LineProlog:  -- enter"));
#endif

    LOG((TL_INFO, "LineProlog: dwArg %lx", dwArg));

    if (phMutex)
    {
        *phMutex = NULL;
        *pbDupedMutex = FALSE;
    }

    *pObjectToDereference = 0;

    if (ppAsyncRequestInfo)
    {
        *ppAsyncRequestInfo = (PASYNCREQUESTINFO) NULL;
    }

    if (TapiGlobals.dwNumLineInits == 0)
    {
        lResult = LINEERR_UNINITIALIZED;
        goto LineProlog_exit;
    }

    if (ptClient->phContext == (HANDLE) -1)
    {
        lResult = LINEERR_REINIT;
        goto LineProlog_exit;
    }

    switch (dwArgType)
    {
    case ANY_RT_HCALL:
    {
        PTCALLCLIENT    ptCallClient;

        LOG((TL_INFO, "LineProlog: ANY_RT_HCALL "));

        if ((ptCallClient = ReferenceObject(
                ghHandleTable,
                dwArg,
                TCALLCLIENT_KEY
                )))
        {
            LOG((TL_INFO, "LineProlog: ReferenceObject returned ptCallClient %p", ptCallClient));

            if (ptCallClient->ptClient != ptClient)
            {
                lResult = LINEERR_INVALCALLHANDLE;
            }
            else if (ptCallClient->dwPrivilege < dwPrivilege)
            {
                lResult = LINEERR_NOTOWNER;
            }
            else
            {
                *pObjectToDereference = dwArg;
                *pContext = ptCallClient;

                try
                {
                    ptProvider = ptCallClient->ptCall->ptProvider;
                    *((HDRVCALL *) phdXxx) = ptCallClient->ptCall->hdCall;

                    if (ppAsyncRequestInfo)
                    {
                        PTLINECLIENT    ptLineClient =
                                            ptCallClient->ptLineClient;


                        initContext = ptLineClient->ptLineApp->InitContext;
                        openContext = ptLineClient->OpenContext;
                        htXxx       = (ULONG_PTR)ptLineClient->ptLine;
                    }
                }
                myexcept
                {
                    
                    LOG((TL_ERROR, "LineProlog: exception"));
                    lResult = LINEERR_INVALCALLHANDLE;
                }

                if (lResult  ||  ptCallClient->dwKey != TCALLCLIENT_KEY)
                {
                    lResult = LINEERR_INVALCALLHANDLE;
                }
                else if (phMutex &&
                         (ptProvider->dwTSPIOptions &
                            LINETSPIOPTION_NONREENTRANT))
                {
                    if (!WaitForMutex(
                            ptProvider->hMutex,
                            phMutex,
                            pbDupedMutex,
                            ptProvider,
                            TPROVIDER_KEY,
                            INFINITE
                            ))
                    {
                        lResult = LINEERR_OPERATIONFAILED;
                    }
                }
            }
        }
        else
        {
            lResult = LINEERR_INVALCALLHANDLE;
        }

        break;
    }
    case ANY_RT_HLINE:
    {
        PTLINECLIENT    ptLineClient;

        LOG((TL_INFO, "LineProlog: ANY_RT_HLINE"));

        if ((ptLineClient = ReferenceObject(
                ghHandleTable,
                dwArg,
                TLINECLIENT_KEY
                )))
        {
            LOG((TL_INFO, "LineProlog: ReferenceObject returned ptLineClient %p", ptLineClient));

            if (ptLineClient->ptClient != ptClient)
            {

                lResult = LINEERR_INVALLINEHANDLE;
            }
            else
            {
                *pObjectToDereference = dwArg;
                *pContext = ptLineClient;

                try
                {
                    ptProvider = ptLineClient->ptLine->ptProvider;
                    *((HDRVLINE *) phdXxx) = ptLineClient->ptLine->hdLine;

                    if (ppAsyncRequestInfo)
                    {
                        initContext = ptLineClient->ptLineApp->InitContext;
                        openContext = ptLineClient->OpenContext;
                        htXxx       = (ULONG_PTR)ptLineClient->ptLine;
                    }
                }
                myexcept
                {
                    LOG((TL_ERROR, "LineProlog: exception"));

                    lResult = LINEERR_INVALLINEHANDLE;
                }

                if (lResult  ||  ptLineClient->dwKey != TLINECLIENT_KEY)
                {
                    lResult = LINEERR_INVALLINEHANDLE;
                }
                else if (phMutex &&
                         (ptProvider->dwTSPIOptions &
                            LINETSPIOPTION_NONREENTRANT))
                {
                    if (!WaitForMutex(
                            ptProvider->hMutex,
                            phMutex,
                            pbDupedMutex,
                            ptProvider,
                            TPROVIDER_KEY,
                            INFINITE
                            ))
                    {
                        LOG((TL_ERROR, "LineProlog: waitformutex failed"));

                        lResult = LINEERR_OPERATIONFAILED;
                    }
                }
            }
        }
        else
        {
            LOG((TL_ERROR, "LineProlog: ReferenceObject returned NULL"));

            lResult = LINEERR_INVALLINEHANDLE;
        }

        break;
    }
    case DEVICE_ID:
    {
        PTLINEAPP           ptLineApp = NULL;
        PTLINELOOKUPENTRY   pLineLookupEntry;


#if TELE_SERVER

        //
        // If it's a server, map the device id
        //

        if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
            !IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
        {
            try
            {
                if ((dwPrivilege >= ptClient->dwLineDevices) ||
                    (ptClient->pLineDevices[dwPrivilege] == 0xffffffff))
                {
                    lResult = LINEERR_BADDEVICEID;
                    goto LineProlog_exit;
                }

                *((LPDWORD) phdXxx) = ptClient->pLineDevices[dwPrivilege];
            }
            myexcept
            {
                lResult = LINEERR_INVALLINEHANDLE;
                goto LineProlog_exit;
            }
        }
        else
#endif
        {
            *((LPDWORD)phdXxx) = dwPrivilege;
        }


        if (dwArg  &&
            !(ptLineApp = IsValidLineApp ((HLINEAPP) dwArg, ptClient)))
        {
            lResult = LINEERR_INVALAPPHANDLE;
        }

        if (ppAsyncRequestInfo)
        {
            try
            {
                initContext = ptLineApp->InitContext;
                openContext = 0;

                if (ptLineApp->dwKey != TLINEAPP_KEY)
                {
                    lResult = LINEERR_INVALAPPHANDLE;
                }
            }
            myexcept
            {
                lResult = LINEERR_INVALAPPHANDLE;
            }
        }

        if (lResult != 0)
        {
            // do nothing
        }
        else if (!(pLineLookupEntry = GetLineLookupEntry (*(LPDWORD)phdXxx)))
        {
            lResult = LINEERR_BADDEVICEID;
        }
        else if (pLineLookupEntry->bRemoved)
        {
            lResult = LINEERR_NODEVICE;
        }
        else if (!(ptProvider = pLineLookupEntry->ptProvider))
        {
            lResult = LINEERR_NODRIVER;
        }
        else
        {
            *pContext = pLineLookupEntry;

            if (phMutex &&
                (ptProvider->dwTSPIOptions &
                    LINETSPIOPTION_NONREENTRANT))
            {
                if (!WaitForMutex(
                        ptProvider->hMutex,
                        phMutex,
                        pbDupedMutex,
                        ptProvider,
                        TPROVIDER_KEY,
                        INFINITE
                        ))
                {
                    lResult = LINEERR_OPERATIONFAILED;
                }
            }
        }

        break;
    }
    } // switch

    if (lResult)
    {
        goto LineProlog_exit;
    }


    //
    // Make sure that if caller wants a pointer to a TSPI proc that the
    // func is exported by the provider
    //

    if (ppfnTSPI_lineXxx &&
        !(*ppfnTSPI_lineXxx = ptProvider->apfn[dwTSPIFuncIndex]))
    {
        lResult = LINEERR_OPERATIONUNAVAIL;
        goto LineProlog_exit;
    }


    //
    // See if we need to alloc & init an ASYNCREQUESTINFO struct
    //

    if (ppAsyncRequestInfo)
    {
        PASYNCREQUESTINFO   pAsyncRequestInfo;


        if (!(pAsyncRequestInfo = ServerAlloc (sizeof(ASYNCREQUESTINFO))))
        {
            lResult = LINEERR_NOMEM;
            goto LineProlog_exit;
        }

        pAsyncRequestInfo->dwLocalRequestID = (DWORD) NewObject(
            ghHandleTable,
            pAsyncRequestInfo,
            NULL
            );

        if (pAsyncRequestInfo->dwLocalRequestID == 0)
        {
            ServerFree (pAsyncRequestInfo);
            lResult = LINEERR_NOMEM;
            goto LineProlog_exit;
        }

        pAsyncRequestInfo->dwKey    = TASYNC_KEY;
        pAsyncRequestInfo->ptClient = ptClient;

        pAsyncRequestInfo->InitContext = initContext;
        pAsyncRequestInfo->OpenContext = openContext;
        pAsyncRequestInfo->htXxx       = (dwArgType != DEVICE_ID ? htXxx :
            pAsyncRequestInfo->dwLocalRequestID);       // a +/- random #

        LOG((TL_INFO, "LineProlog: OpenContext %p", openContext));

        pAsyncRequestInfo->dwLineFlags = 1;

        if (dwRemoteRequestID)
        {
            lResult = pAsyncRequestInfo->dwRemoteRequestID = dwRemoteRequestID;
        }
        else
        {
            lResult = pAsyncRequestInfo->dwRemoteRequestID =
                pAsyncRequestInfo->dwLocalRequestID;
        }

        *ppAsyncRequestInfo = pAsyncRequestInfo;
    }

LineProlog_exit:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "LineProlog: (line%s) exit, result=%s",
            pszFuncName,
            MapResultCodeToText (lResult, szResult)
            ));
    }
#else
    LOG((TL_TRACE,
        "LienProlog: exit, result = x%lx",
        lResult
        ));
#endif

    return lResult;
}


void
PASCAL
LineEpilogSync(
    LONG       *plResult,
    HANDLE      hMutex,
    BOOL        bCloseMutex,
    DWORD       ObjectToDereference
#if DBG
    ,char *pszFuncName
#endif
    )
{
    DereferenceObject (ghHandleTable, ObjectToDereference, 1);

    if (hMutex)
    {
        MyReleaseMutex (hMutex, bCloseMutex);
    }

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "LineEpilogSync: (line%s) exit, result=%s",
            pszFuncName,
            MapResultCodeToText (*plResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "LineEpilogSync: exit, result=x%x",
            *plResult
            ));
#endif
}


void
PASCAL
LineEpilogAsync(
    LONG               *plResult,
    LONG                lRequestID,
    HANDLE              hMutex,
    BOOL                bCloseMutex,
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    DWORD               ObjectToDereference
#if DBG
    ,char *pszFuncName
#endif
    )
{
    DereferenceObject (ghHandleTable, ObjectToDereference, 1);

    MyReleaseMutex (hMutex, bCloseMutex);

    if (lRequestID > 0)
    {
        if (*plResult <= 0)
        {
            if (*plResult == 0)
            {
                LOG((TL_ERROR, "Error: SP returned 0, not request ID"));
            }

            //
            // If here the service provider returned an error (or 0,
            // which it never should for async requests), so call
            // CompletionProcSP like the service provider normally
            // would, & the worker thread will take care of sending
            // the client a REPLY msg with the request result (we'll
            // return an async request id)
            //

            CompletionProcSP(
                pAsyncRequestInfo->dwLocalRequestID,
                *plResult
                );
        }
    }
    else if (pAsyncRequestInfo != NULL)
    {
        //
        // If here an error occured before we even called the service
        // provider, so just free the async request (the error will
        // be returned to the client synchronously)
        //

        DereferenceObject(
            ghHandleTable,
            pAsyncRequestInfo->dwLocalRequestID,
            1
            );
    }

    *plResult = lRequestID;

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "LineEpilogAsync: (line%s) exit, result=%s",
            pszFuncName,
            MapResultCodeToText (lRequestID, szResult)
            ));
    }
#else
        LOG((TL_TRACE,
            "LineEpilogAsyc: exit, result=x%lx",
            lRequestID
            ));
#endif
}


void
PASCAL
LineEventProc(
    HTAPILINE   htLine,
    HTAPICALL   htCall,
    DWORD       dwMsg,
    ULONG_PTR   Param1,
    ULONG_PTR   Param2,
    ULONG_PTR   Param3
    )
{
    LOG((TL_TRACE,  "LineEventProc"));

    switch (dwMsg)
    {
    case LINE_ADDRESSSTATE:
    case LINE_LINEDEVSTATE:
    case LINE_DEVSPECIFIC:
    case LINE_DEVSPECIFICFEATURE:
    case LINE_PROXYSTATUS:
    case LINE_AGENTSTATUS:
    {
        PTLINE  ptLine = (PTLINE)htLine;

        if (NULL == ptLine)
        {
            return;
        }

        if (dwMsg == LINE_LINEDEVSTATE  &&
                 htLine == 0  &&
                 Param1 & LINEDEVSTATE_REINIT)
        {
            SendReinitMsgToAllXxxApps();
        }
        else
        {
            SendMsgToLineClients(
                ptLine,
                NULL,
                dwMsg,
                DWORD_CAST(Param1,__FILE__,__LINE__),
                DWORD_CAST(Param2,__FILE__,__LINE__),
                DWORD_CAST(Param3,__FILE__,__LINE__)
                );
        }

        break;
    }
    case LINE_AGENTSPECIFIC:

        if (htCall)
        {
            SendMsgToCallClients(
                (PTCALL)htCall,
                NULL,
                LINE_AGENTSPECIFIC,
                DWORD_CAST(Param1,__FILE__,__LINE__),
                DWORD_CAST(Param2,__FILE__,__LINE__),
                DWORD_CAST(Param3,__FILE__,__LINE__)
                );

        }
        else
        {
            PTLINE  ptLine = (PTLINE)htLine;


            SendMsgToLineClients(
                ptLine,
                NULL,
                LINE_AGENTSPECIFIC,
                DWORD_CAST(Param1,__FILE__,__LINE__),
                DWORD_CAST(Param2,__FILE__,__LINE__),
                DWORD_CAST(Param3,__FILE__,__LINE__)
                );
        }

        break;

    case LINE_CLOSE:
    {
        PTLINE  ptLine = (PTLINE)htLine;
        DWORD   dwKey;

        try 
        {
            dwKey = ptLine->dwKey;
        }
        myexcept
        {
            dwKey = 0;
        }

        if (dwKey == TINCOMPLETELINE_KEY)
        {
            //
            // The device is in the process of getting opened but
            // the key has not been set & the Open() func still owns
            // the mutex and has stuff to do, so repost the msg
            // and try again later. (Set Param3 to special value
            // to indicate this repost, so EventProcSP doesn't recurse)
            //

            LineEventProcSP (htLine, 0, LINE_CLOSE, 0, 0, 0xdeadbeef);
        }
        else if (dwKey == TLINE_KEY)
        {
            DestroytLine (ptLine, TRUE); // unconditional destroy
        }

        break;
    }
    case LINE_CALLDEVSPECIFIC:
    case LINE_CALLDEVSPECIFICFEATURE:
    case LINE_CALLINFO:
    {
        PTCALL  ptCall = (PTCALL)htCall;

        if (NULL == htCall)
        {
            return;
        }

        switch (dwMsg)
        {
            case LINE_CALLDEVSPECIFIC:

                dwMsg = LINE_DEVSPECIFIC;
                break;

            case LINE_CALLDEVSPECIFICFEATURE:

                dwMsg = LINE_DEVSPECIFICFEATURE;
                break;

            case LINE_CALLINFO:
            {
                Param2 =
                Param3 = 0;

                if ((Param1 == LINECALLINFOSTATE_CALLID)  ||
                    (Param1 == LINECALLINFOSTATE_RELATEDCALLID))
                {
                    if ((WaitForExclusivetCallAccess (ptCall, TCALL_KEY)))
                    {
                        DWORD dwPreviousCallID = ptCall->dwCallID;


                        GetCallIDs (ptCall);

                        DoCallHubHashing (ptCall, dwPreviousCallID);

                        UNLOCKTCALL(ptCall);
                    }
                }
                break;
            }
        }

        SendMsgToCallClients(
            ptCall,
            NULL,
            dwMsg,
            DWORD_CAST(Param1,__FILE__,__LINE__),
            DWORD_CAST(Param2,__FILE__,__LINE__),
            DWORD_CAST(Param3,__FILE__,__LINE__)
            );

        break;
    }
    case LINE_MONITORDIGITS:
    case LINE_MONITORMEDIA:
    {
        PTCALL  ptCall = (PTCALL)htCall;

        if (NULL == htCall)
        {
            return;
        }

        SendMsgToCallClients(
            ptCall,
            NULL,
            dwMsg,
            DWORD_CAST(Param1,__FILE__,__LINE__),
            DWORD_CAST(Param2,__FILE__,__LINE__),
            (Param3 ? DWORD_CAST(Param3,__FILE__,__LINE__) : GetTickCount())
            );

        break;
    }
    case LINE_CALLSTATE:
    {
        BOOL            fastPrivilegeList[DEF_NUM_PTR_LIST_ENTRIES],
                        *pPrivilegeList = fastPrivilegeList;
        DWORD           i, j, dwNumUsedEntries = 0,
                        dwNumTotalEntries= DEF_NUM_PTR_LIST_ENTRIES;
        PTCALL          ptCall = (PTCALL)htCall;
        TPOINTERLIST    fastCallClientList,
                        *pCallClientList = &fastCallClientList;
        TPOINTERLIST    fastConfCallClientList,
                        *pConfCallClientList = NULL;

        LOG((TL_EVENT,  "LineEventProc: LINE_CALLSTATE event x%lx", Param1));


        if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
        {
            PTCALLCLIENT    ptCallClient = ptCall->ptCallClients;
            ASYNCEVENTMSG   msg[2];


            if (Param1 == LINECALLSTATE_OFFERING)
            {
                ptCall->dwDrvCallFlags |= DCF_INCOMINGCALL;
                PerfBlock.dwCurrentIncomingCalls++;
                PerfBlock.dwTotalIncomingCalls++;
                PerfBlock.dwCurrentOutgoingCalls--;
                PerfBlock.dwTotalOutgoingCalls--;
            }

            if (ptCall->bAlertApps)
            {
                //
                // This is the first state msg we've received for an incoming
                // call.  We need to determine who owns & who monitors it,
                // and create the appropriate tCallClients
                //

                BOOL            bFindOwner;
                DWORD           dwMediaModes = (DWORD) Param3,
                                dwSPIVersion = ptCall->ptLine->dwSPIVersion,
                                dwAddressID;
                PTLINECLIENT    ptLineClientOwner;


                ptCall->bAlertApps = FALSE;


                //
                // If this is a remotesp call then Param2 points at a
                // DWORD array, the 1st entry of which is the "real"
                // Param2 for this message (i.e. the call state mode),
                // the 2nd entry of which is the original privilege for
                // this call, and the 3rd entry of which is htCall
                // (which we use for call verification purposes)
                //

                if (ptCall->ptProvider != pRemoteSP)
                {
                    bFindOwner = TRUE;
                }
                else
                {
                    BOOL        bBreak = FALSE;
                    PULONG_PTR  pdwRealParam2 = (PULONG_PTR) Param2,
                                pdwPrivilege = (((PULONG_PTR) Param2) + 1);
                    LPHTAPICALL phtCall = (LPHTAPICALL)
                                    (((LPDWORD) Param2) + 2);


                    try
                    {
                        Param2 = *pdwRealParam2;

                        bFindOwner = (*pdwPrivilege & LINECALLPRIVILEGE_OWNER ?
                            TRUE : FALSE);

                        bBreak = (*phtCall != htCall ? TRUE : FALSE);
                    }
                    myexcept
                    {
                        bBreak = TRUE;
                    }

                    if (bBreak)
                    {
                        UNLOCKTCALL(ptCall);
                        goto LINE_CALLSTATE_break;
                    }
                }


                //
                // Retrieve call's address id, etc
                //

                GetCallIDs (ptCall);

                dwAddressID = ptCall->dwAddressID;

                UNLOCKTCALL(ptCall);


                //
                // Add the UNKNOWN bit if >1 bit set
                // if version is <= 2.1

                if ( ( (dwSPIVersion <= TAPI_VERSION2_1) &&
                    !IsOnlyOneBitSetInDWORD (dwMediaModes) ) ||
                    dwMediaModes == 0)
                {
                    dwMediaModes |= LINEMEDIAMODE_UNKNOWN;
                }


                //
                // Try to find an owner.  If no owner found then destroy
                // the tCall.
                //

                if (bFindOwner)
                {
                    PTLINE  ptLine = (PTLINE)htLine;


                    if (!ptLine)
                    {
                        //
                        // Line closed
                        //

                        DestroytCall (ptCall);
                        goto LINE_CALLSTATE_break;
                    }

LINE_CALLSTATE_findOwner:

                    if ((ptLineClientOwner = GetHighestPriorityLineClient(
                            ptLine,
                            dwMediaModes,
                            dwAddressID
                            )))
                    {
                        LONG         lResult;
                        PTCALLCLIENT ptCallClientOwner;


                        if ((lResult = CreatetCallClient(
                                ptCall,
                                ptLineClientOwner,
                                LINECALLPRIVILEGE_OWNER,
                                TRUE,
                                FALSE,
                                &ptCallClientOwner,
                                TRUE

                            )) != 0)
                        {
                            if (lResult == LINEERR_INVALLINEHANDLE)
                            {
                                //
                                // The tLineClient was just closed, so jump
                                // up top & try to find another owner
                                //

                                goto LINE_CALLSTATE_findOwner;
                            }
                            else
                            {
                                //
                                // No mem, line closed, etc
                                //

                                DestroytCall (ptCall);
                                goto LINE_CALLSTATE_break;
                            }
                        }
                    }
                }
                else if (Param1 == LINECALLSTATE_UNKNOWN  &&
                         Param2 == 0xa5a5a5a5)
                {
                    //
                    // If here we're being called directly from
                    // remotesp!TSPI_lineGetID, who's being called
                    // by LGetNewCalls.  We're not going to look
                    // for an owner of this call, but if we don't
                    // find any monitors we still don't want to
                    // tear the call down, because we want to give
                    // a handle to the app doing the lineGetNewCalls
                    // (which may not have MONITOR privileges).
                    //
                    // So we do the following to prevent the call
                    // from getting destroyed.
                    //

                    ptLineClientOwner = (PTLINECLIENT) 1;
                    Param2 = 0;
                }
                else
                {
                    //
                    // Set ptLineClientOwner == NULL, becaue if there
                    // aren't any monitors we'll want to destroy this
                    // calls.
                    //

                    ptLineClientOwner = (PTLINECLIENT) NULL;
                }

                if (CreateCallMonitors (ptCall, TRUE) <= 0 &&
                    !ptLineClientOwner)
                {
                    DestroytCall (ptCall);
                    goto LINE_CALLSTATE_break;
                }

                if (!WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
                {
                    goto LINE_CALLSTATE_break;
                }
            }


            //
            // NOTE: per bug #20545 we're no longer auto-dropping
            //       non-IDLE calls; figured this would be the wrong
            //       thing to do in a distributed system
            //
            //       dankn 02/15/96
            //


            //
            // SP-initiated conference
            //

            if (Param1 == LINECALLSTATE_CONFERENCED)
            {
                if (!ptCall->pConfList)
                {
                    PTCALL              ptConfCall = (PTCALL)Param2;
                    PTCONFERENCELIST    pConfList;


                    ptCall->pConfList = (LPVOID) LongToPtr(0xffffffff);

                    UNLOCKTCALL(ptCall);

                    if (WaitForExclusivetCallAccess(
                            ptConfCall,
                            TCALL_KEY
                            ))
                    {
                        if (!ptConfCall->pConfList)
                        {
                            if ((pConfList = ServerAlloc(
                                    sizeof (TCONFERENCELIST) +
                                        sizeof(PTCALL) *
                                        (DEF_NUM_CONF_LIST_ENTRIES - 1)
                                    )))
                            {
                                pConfList->dwKey = TCONFLIST_KEY;
                                pConfList->dwNumTotalEntries =
                                    DEF_NUM_CONF_LIST_ENTRIES;
                                pConfList->dwNumUsedEntries  = 1;

                                pConfList->aptCalls[0] = ptConfCall;

                                ptConfCall->pConfList = pConfList;
                            }
                        }

                        pConfList = ptConfCall->pConfList;

                        pConfCallClientList = &fastConfCallClientList;

                        if (GetCallClientListFromCall(
                                ptConfCall,
                                &pConfCallClientList

                                ) != 0)
                        {
                            pConfCallClientList = NULL;
                        }

                        UNLOCKTCALL(ptConfCall);
                    }
                    else
                    {
                        pConfList = NULL;
                    }

                    SetCallConfList (ptCall, pConfList, TRUE);

                    if (!WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
                    {
                        if (pConfCallClientList  &&
                            pConfCallClientList != &fastConfCallClientList)
                        {
                            ServerFree (pConfCallClientList);
                        }

                        goto LINE_CALLSTATE_break;
                    }
                }
            }


            //
            // If call is a conference child and the call state has
            // changed then remove it from the conference
            //

            else if (ptCall->pConfList  &&
                     ptCall->pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff))
            {
                try
                {
                    if ( ptCall->pConfList->aptCalls[0] != ptCall)
                    {
                        SetCallConfList (ptCall, NULL, FALSE);
                    }
                }
                myexcept
                {
                }
            }


            //
            // Record the call state & mode
            //

            ptCall->dwCallState     = DWORD_CAST(Param1,__FILE__,__LINE__);
            ptCall->dwCallStateMode = (LINECALLSTATE_CONFERENCED==Param1?0:DWORD_CAST(Param2,__FILE__,__LINE__));


            //
            // Build a list of call clients & their bIndicatePrivilege
            // settings
            //

            if (GetCallClientListFromCall (ptCall, &pCallClientList) != 0)
            {
                //
                // If here we know there's at least a few entries
                // in the fastCallClientList (DEF_NUM_PTR_LIST_ENTRIES
                // to be exact), so we'll just work with that list
                // and at least get msgs out to a few clients
                //

                pCallClientList = &fastCallClientList;

                fastCallClientList.dwNumUsedEntries = DEF_NUM_PTR_LIST_ENTRIES;
            }

            dwNumUsedEntries = pCallClientList->dwNumUsedEntries;

            pPrivilegeList = (dwNumUsedEntries <= DEF_NUM_PTR_LIST_ENTRIES ?
                fastPrivilegeList :
                ServerAlloc (pCallClientList->dwNumUsedEntries * sizeof (BOOL))
                );

            if (!pPrivilegeList)
            {
                //
                // Same as above - make due with the stack bufs
                //

                pPrivilegeList = fastPrivilegeList;

                dwNumUsedEntries = DEF_NUM_PTR_LIST_ENTRIES;
            }

            for (i = 0; i < dwNumUsedEntries; i++)
            {
                ptCallClient = (PTCALLCLIENT) pCallClientList->aEntries[i];

                if ((pPrivilegeList[i] =
                        (BOOL) ptCallClient->bIndicatePrivilege))
                {
                    ptCallClient->bIndicatePrivilege = 0;
                }
            }


            //
            // It's now ok to unlock the tCall
            //

            UNLOCKTCALL(ptCall);


            //
            // Send the CALLSTATE msg to all the clients
            //

            msg->TotalSize = sizeof (ASYNCEVENTMSG) + sizeof(HCALLHUB);
            msg->Msg       = dwMsg;
            msg->Param1    = DWORD_CAST(Param1,__FILE__,__LINE__);

            for (i = 0; i < dwNumUsedEntries; i++)
            {
                ptCallClient = (PTCALLCLIENT) pCallClientList->aEntries[i];

                LOG((TL_INFO, "LineEventProc: i = [%d] corresponding ptCallClient [%p]", i, ptCallClient));

                try
                {
                    PTLINECLIENT    ptLineClient;
                    PTLINEAPP       ptLineApp;


                    LOG((TL_INFO, "LineEventProc: ptCallClient->ptLineClient[%p]", ptCallClient->ptLineClient));
                    ptLineClient = ptCallClient->ptLineClient;

                    LOG((TL_INFO, "LineEventProc: ptLineClient->ptLineApp[%p]", ptLineClient->ptLineApp));
                    ptLineApp    = ptLineClient->ptLineApp;

                    LOG((TL_INFO, "LineEventProc: setting msg->InitContext to ptLineApp[%p]->InitContext of [%p]", ptLineApp, ptLineApp->InitContext));
                    msg->InitContext = ptLineApp->InitContext;

                    msg->hDevice     = ptCallClient->hCall;

                    LOG((TL_INFO, "LineEventProc: setting msg->OpenContext to [%p]", ptLineClient->OpenContext));

                    msg->OpenContext = ptLineClient->OpenContext;

                    //
                    // REMOTESP HACK: indicate the hRemoteLine in p4
                    //

                    msg->Param4 = ptLineClient->hRemoteLine;

                    *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                        (ptCallClient->ptCallHubClient)?
                            ptCallClient->ptCallHubClient->hCallHub : 
                            (HCALLHUB)0;

                    if (pPrivilegeList[i])
                    {
                        //
                        // We're presenting the app with a new call handle;
                        // for 2.0 & newer apps we indicate this with an
                        // APPNEWCALL msg, while older apps just get the
                        // privilege field set in the call state msg.
                        //

                        if (ptLineApp->dwAPIVersion >= TAPI_VERSION2_0)
                        {
                            ASYNCEVENTMSG   newCallMsg[2],
                                            *pNewCallMsg = newCallMsg;
                            PTCONFERENCELIST    pConfList;
                            BOOL                bConfParent = FALSE;

                            if (!FMsgDisabled(
                                ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                                ptCallClient->adwEventSubMasks,
                                LINE_APPNEWCALL,
                                0
                                ))
                            {
                                pNewCallMsg->TotalSize   =
                                    sizeof (ASYNCEVENTMSG) + 3 * sizeof (DWORD);
                                pNewCallMsg->InitContext = msg->InitContext;
                                pNewCallMsg->hDevice     =
                                    ptLineClient->hRemoteLine;
                                pNewCallMsg->OpenContext = msg->OpenContext;
                                pNewCallMsg->fnPostProcessProcHandle = 0;
                                pNewCallMsg->Msg    = LINE_APPNEWCALL;
                                pNewCallMsg->Param1 = ptCall->dwAddressID;
                                pNewCallMsg->Param2 = ptCallClient->hCall;
                                pNewCallMsg->Param3 = ptCallClient->dwPrivilege;
                                *(&pNewCallMsg->Param4 + 1) = ptCall->dwCallID;
                                *(&pNewCallMsg->Param4 + 2) =
                                    ptCall->dwRelatedCallID;
                                if ((pConfList = ptCall->pConfList) &&
                                    (pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff)) &&
                                    (pConfList->aptCalls[0] == ptCall))
                                {
                                    bConfParent = TRUE;
                                }
                                *(&pNewCallMsg->Param4 + 3) = (DWORD) bConfParent;

                                if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                                {
                                    LOG((TL_INFO, "LineEventProc: sending LINE_APPNEWCALL, ptClient[%p]", ptCallClient->ptClient));
                                    WriteEventBuffer(
                                        ptCallClient->ptClient,
                                        pNewCallMsg
                                        );
                                }
                            }

                            msg->Param3 = 0;
                        }
                        else
                        {
                            msg->Param3 = ptCallClient->dwPrivilege;
                        }
                    }
                    else
                    {
                        msg->Param3 = 0;
                    }

                    if (FMsgDisabled (
                        ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                        ptCallClient->adwEventSubMasks,
                        (DWORD) msg->Msg,
                        (DWORD) msg->Param1
                        ))
                    {
                        continue;
                    }

                    //
                    // Another special case for LINECALLSTATE_CONFERENCED -
                    // try to find the corresponding hConfCall (on same
                    // tLineClient) so we can set Param2 per spec
                    //

                    if (Param1 == LINECALLSTATE_CONFERENCED)
                    {
                        BOOL    bDone = (pConfCallClientList ? FALSE : TRUE);


                        Param2 = 0;

                        while (!bDone)
                        {
                            try
                            {
                                for(
                                    j = 0;
                                    j < pConfCallClientList->dwNumUsedEntries;
                                    j++
                                    )
                                {
                                    PTCALLCLIENT    pConfCallClient;


                                    pConfCallClient = (PTCALLCLIENT)
                                        pConfCallClientList->aEntries[j];

                                    if (pConfCallClient  &&
                                        pConfCallClient->ptLineClient ==
                                            ptLineClient)
                                    {
                                        pConfCallClientList->aEntries[j] =
                                            NULL;

                                        Param2 = pConfCallClient->hCall;

                                        break;
                                    }
                                }

                                bDone = TRUE;
                            }
                            myexcept
                            {
                                //
                                // If here we presumbly blew up because
                                // an entry in the confCallClientList was
                                // bad.  So we'll zero this entry & try
                                // again.
                                //

                                pConfCallClientList->aEntries[j] = NULL;
                            }
                        }
                    }


                    //
                    // REMOTESP HACK: If the client is remote(sp), then pass
                    //                on the media mode the SP passed us in p3
                    //                We also need the privilege - in p2
                    //
                    //                Should have originally put the privilege
                    //                in msg.pfnPostProcess (since it's not
                    //                used for this msg in rmeotesp), because
                    //                in tapi 2.1 we ended up losing Param2
                    //                (the call state mode).  So now we stick
                    //                the original Param2 (the call state
                    //                mode) in msg.pfnPostProcess to maintain
                    //                compatibility.
                    //

                    if (IS_REMOTE_CLIENT (ptLineApp->ptClient))
                    {
                        msg->Param2 = ptCallClient->dwPrivilege;
                        msg->Param3 = DWORD_CAST(Param3,__FILE__,__LINE__);

                        msg->fnPostProcessProcHandle = DWORD_CAST(Param2,__FILE__,__LINE__);
                    }
                    else
                    {
                        msg->Param2 = DWORD_CAST(Param2,__FILE__,__LINE__);

                        msg->fnPostProcessProcHandle = 0;
                    }

                    if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                    {
                        WriteEventBuffer (ptCallClient->ptClient, msg);
                    }
                }
                myexcept
                {
                    // do nothing, just fall thru
                }
            }

            if (pCallClientList != &fastCallClientList)
            {
                ServerFree (pCallClientList);
            }

            if (pPrivilegeList != fastPrivilegeList)
            {
                ServerFree (pPrivilegeList);
            }

            if (pConfCallClientList  &&
                pConfCallClientList != &fastConfCallClientList)
            {
                ServerFree (pConfCallClientList);
            }

        } // if ((ptCall = WaitForExclusivetCallAccess(
        else
        {
           LOG((TL_ERROR,
               "LINECALLSTATE: Failed call access for call= x%p",
               ptCall
               ));

           LOG((TL_INFO,
               "  Line=x%lx  p1=x%lx p2=x%lx p3=x%lx",
               htLine,
               Param1,
               Param2,
               Param3
               ));
        }

LINE_CALLSTATE_break:

        break;
    }
    case LINE_GATHERDIGITS:
    {
        PASYNCREQUESTINFO pAsyncRequestInfo;


        if (Param2 == 0) // dwEndToEndID
        {
            //
            // The SP is notifying us of the completion of a cancel
            // request (not a _canceled_ request), so we can just blow
            // this off and not bother passing it on to the client
            //

            break;
        }

        if ((pAsyncRequestInfo = ReferenceObject(
                ghHandleTable,
                DWORD_CAST(Param2,__FILE__,__LINE__),
                TASYNC_KEY
                )))
        {
            LPWSTR          lpsDigitsSrv = (LPWSTR)
                                (((LPBYTE) pAsyncRequestInfo) +
                                    pAsyncRequestInfo->dwParam1);

            DWORD           hpsDigitsCli = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__);

            DWORD           dwNumDigits = DWORD_CAST(pAsyncRequestInfo->dwParam3,__FILE__,__LINE__),
                            dwNumDigitsTmp;
            HCALL           hCall = (HCALL) pAsyncRequestInfo->dwParam4;
            DWORD           dwEndToEndIDRemote = DWORD_CAST(
                                pAsyncRequestInfo->dwParam5,__FILE__,__LINE__);
            PTCALLCLIENT    ptCallClient;
            ASYNCEVENTMSG   *pMsg;


            if (!(ptCallClient = (PTCALLCLIENT) ReferenceObject(
                    ghHandleTable,
                    hCall,
                    TCALLCLIENT_KEY
                    )))
            {
                goto LINE_GATHERDIGITS_dereferenceAsyncReqInfo;
                break;
            }


            //
            // 2 * sizeof ULONG_PTR is adding space to include the
            // dwEndToEndID and hRemoteLine, both for remotesp
            //

            if (!(pMsg = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + (2 * sizeof(DWORD)) +
                        (dwNumDigits + 1) * sizeof (WCHAR) + TALIGN_COUNT
                    )))
            {
                goto LINE_GATHERDIGITS_dereferenceCall;
                break;
            }


            //
            // Note: We either have < dwNumDigits digits in the buffer,
            //       and they are null-terminated, or we have dwNumDigits
            //       digits in the buffer and they are NOT null-terminated
            //       (such is the implementation given the spec)
            //

            {
                DWORD   *pDW = (DWORD *) (pMsg + 1);
                WCHAR   *pBuf = (WCHAR *) (pDW + 2);


                pDW[0] = dwEndToEndIDRemote;

                try
                {
                    pDW[1] = ptCallClient->ptLineClient->hRemoteLine;
                }
                myexcept
                {
                }

                wcsncpy (pBuf, lpsDigitsSrv, dwNumDigits);

                if ((dwNumDigitsTmp = lstrlenW (pBuf)) < dwNumDigits)
                {
                    dwNumDigits = dwNumDigitsTmp + 1;
                }
            }


            //
            // Make sure total size is DWORD-aligned so client side doesn't
            // incur an alignment fault
            //
            //  sizeof(ULONG_PTR) is added to put the dwEndToEndID in the buf
            //

            pMsg->TotalSize        = (sizeof (ASYNCEVENTMSG) +
                2 * sizeof(DWORD) +
                dwNumDigits * sizeof (WCHAR) + TALIGN_COUNT) & TALIGN_MASK;
            pMsg->InitContext        = pAsyncRequestInfo->InitContext;
            
            pMsg->fnPostProcessProcHandle = 
                pAsyncRequestInfo->hfnClientPostProcessProc;
            
            pMsg->hDevice            = hCall;
            pMsg->Msg                = LINE_GATHERDIGITS;
            pMsg->OpenContext        = pAsyncRequestInfo->OpenContext;
            pMsg->Param1             = DWORD_CAST(Param1,__FILE__,__LINE__);
            
            pMsg->Param2             = hpsDigitsCli;

            pMsg->Param3             = (Param3 ? DWORD_CAST(Param3,__FILE__,__LINE__) : GetTickCount());
            pMsg->Param4             = dwNumDigits;

            WriteEventBuffer (pAsyncRequestInfo->ptClient, pMsg);

            ServerFree (pMsg);

LINE_GATHERDIGITS_dereferenceCall:

            DereferenceObject (ghHandleTable, hCall, 1);

LINE_GATHERDIGITS_dereferenceAsyncReqInfo:

            DereferenceObject (ghHandleTable, DWORD_CAST(Param2,__FILE__,__LINE__), 2); // by 2 to free
        }
        else
        {
           LOG((TL_ERROR,
               "Bad Param2=x%lx in LINE_GATHERDIGITS msg!",
               Param2
               ));
        }

        break;
    }
    case LINE_MONITORTONE:
    {
        //
        // Note: Param2 (the dwToneListID) is really a ptCallClient
        //
        // Hack Alert!! : In the case of remotesp we'll get a special
        //                bogus Param2, in which case we really don't
        //                know who the appropriate call client is.  So
        //                we'll call SendMsgtoCallClients() and let it
        //                figure out which apps have done tone monitoring
        //                on this call, and we'll forward to all of them.
        //                It's cheesey, but the alternative is keeping a
        //                bunch of context around in the client/server
        //                case, and i really don't want to deal with that.
        //                (Plus, i doubt there will be many, if any, cases
        //                of >1 app doing remote monitor on the same call.)
        //
        //                DanKn, 06/06/98
        //

        PTCALLCLIENT    ptCallClient;


        if (Param2 == 0) // special remotesp hack
        {
            PTCALL  ptCall = (PTCALL)htCall;


            SendMsgToCallClients(
                ptCall,
                NULL,
                dwMsg,
                DWORD_CAST(Param1,__FILE__,__LINE__),
                0,
                DWORD_CAST(Param3,__FILE__,__LINE__)
                );

            break;
        }

        if (!(ptCallClient = (PTCALLCLIENT) ReferenceObject(
                ghHandleTable,
                DWORD_CAST(Param2,__FILE__,__LINE__),         // dwToneListID == hCall
                TCALLCLIENT_KEY
                )))
        {
            break;
        }

        try
        {
            ASYNCEVENTMSG   msg[2];
            PTLINECLIENT    ptLineClient = ptCallClient->ptLineClient;


            ptLineClient = ptCallClient->ptLineClient;

            msg->TotalSize          = sizeof (ASYNCEVENTMSG) +
                sizeof (HCALLHUB);
            msg->InitContext        = ptLineClient->ptLineApp->InitContext;
            msg->fnPostProcessProcHandle = 0;
            msg->hDevice            = ptCallClient->hCall;
            msg->Msg                = dwMsg;
            msg->OpenContext        = ptLineClient->OpenContext;
            msg->Param1             = DWORD_CAST(Param1,__FILE__,__LINE__);
            msg->Param2             = 0;
            msg->Param3             = (Param3 ? DWORD_CAST(Param3,__FILE__,__LINE__) : GetTickCount());

            msg->Param4 = ptLineClient->hRemoteLine; // for RemoteSP

            *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                        (ptCallClient->ptCallHubClient)?
                            ptCallClient->ptCallHubClient->hCallHub : 
                            (HCALLHUB)0;


            //
            // Now a final check to make sure all the
            // params are valid before sending the msg
            //

            {
                PTCLIENT    ptClient = ptCallClient->ptClient;


                if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                {
                    WriteEventBuffer (ptClient, msg);
                }
            }
        }
        myexcept
        {
            // do nothing
        }

        DereferenceObject (ghHandleTable, DWORD_CAST(Param2,__FILE__,__LINE__), 1);

        break;
    }
    case LINE_GENERATE:
    {
        //
        // Note: Param2 id really a pointer to instance data containing
        //       ([0]) the hCall & ([1]) the dwEndToEndID or dwToneListID,
        //       the latter of which is only useful to remotesp
        //

        HCALL           hCall;
        DWORD           dwEndToEndID;
        LPDWORD         pInstData;
        PTCALLCLIENT    ptCallClient;


        if (!(pInstData = (LPDWORD) ReferenceObject(
                ghHandleTable,
                DWORD_CAST(Param2,__FILE__,__LINE__),
                TASYNC_KEY
                )))
        {
            break;
        }

        hCall = pInstData[1];
        dwEndToEndID = pInstData[2];

        DereferenceObject (ghHandleTable, DWORD_CAST(Param2,__FILE__,__LINE__), 2); // by 2 to free it

        if (!(ptCallClient = (PTCALLCLIENT) ReferenceObject(
                ghHandleTable,
                hCall,
                TCALLCLIENT_KEY
                )))
        {
            break;
        }

        try
        {
            ASYNCEVENTMSG   msg[2];
            PTLINECLIENT    ptLineClient = ptCallClient->ptLineClient;


            msg->TotalSize          = sizeof (ASYNCEVENTMSG) +
                sizeof (HCALLHUB);
            msg->InitContext        = ptLineClient->ptLineApp->InitContext;
            msg->fnPostProcessProcHandle = 0;
            msg->hDevice            = hCall;
            msg->Msg                = dwMsg;
            msg->OpenContext        = ptLineClient->OpenContext;
            msg->Param1             = DWORD_CAST(Param1,__FILE__,__LINE__);

            LOG((TL_INFO,
                "LineEventProc: LINE_GENERATE  OpenContext %p InitContext %p",
                msg->OpenContext, msg->InitContext ));


            //
            // Indicate the endToEndID/toneListID for remotesp, and the
            // hRemoteLine in p4 to make life easier for remotesp
            //

            msg->Param2 = dwEndToEndID;
            msg->Param3 = (Param3 ? DWORD_CAST(Param3,__FILE__,__LINE__) : GetTickCount());

            msg->Param4 = ptLineClient->hRemoteLine;

            *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                        (ptCallClient->ptCallHubClient)?
                            ptCallClient->ptCallHubClient->hCallHub : 
                            (HCALLHUB)0;


            //
            // Now a final check to make sure all the
            // params are valid before sending the msg
            //

            {
                PTCLIENT    ptClient = ptCallClient->ptClient;


                if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                {
                    WriteEventBuffer (ptClient, msg);
                }
            }
        }
        myexcept
        {
            // do nothing
        }

        DereferenceObject (ghHandleTable, hCall, 1);

        break;
    }
    case LINE_NEWCALL:
    {
        //
        // Create a tCall & set the bAlertApps field so we create the
        // appropriate tCallClients on the first call state msg
        //

        PTCALL      ptCall;
        PTLINE      ptLine = (PTLINE)htLine;


        if (NULL != ptLine)
        {
            if (CreatetCall(
                    ptLine,
                    TRUE,
                    (HDRVCALL) Param1,
                    &ptCall,
                    NULL,
                    NULL

                    ) != 0)
            {
                ptCall = NULL;
            }
        }
        else
        {
            ptCall = NULL;
        }

        *((LPHTAPICALL) Param2) = (HTAPICALL)ptCall;

        break;
    }
    case LINE_CREATE:
    {
        LONG                lResult;
        DWORD               dwDeviceID;
        TSPIPROC            pfnTSPI_providerCreateLineDevice;
        PTPROVIDER          ptProvider = (PTPROVIDER) Param1;
        PTLINELOOKUPTABLE   pTable, pPrevTable;
        PTLINELOOKUPENTRY   pEntry;
        PTPROVIDER          ptProvider2;


        TapiEnterCriticalSection (&TapiGlobals.CritSec);

        //
        //  Check to see if ptProvider(Param1) is still valid, LINE_CREATE
        //  might got processed after the TSP has been removed
        //
        if (NULL == ptProvider)
        {
            TapiLeaveCriticalSection (&TapiGlobals.CritSec);
            return;
        }

        ptProvider2 = TapiGlobals.ptProviders;
        while (ptProvider2 && ptProvider2 != ptProvider)
        {
            ptProvider2 = ptProvider2->pNext;
        }

        if (ptProvider2 != ptProvider)
        {
            TapiLeaveCriticalSection (&TapiGlobals.CritSec);
            return;
        }

        pfnTSPI_providerCreateLineDevice =
            ptProvider->apfn[SP_PROVIDERCREATELINEDEVICE];

        assert (pfnTSPI_providerCreateLineDevice != NULL);


        //
        // Search for a table entry (create a new table if we can't find
        // a free entry in an existing table)
        //

        if (!gbQueueSPEvents)
        {
            //
            // We're shutting down, so bail out
            //

            TapiLeaveCriticalSection (&TapiGlobals.CritSec);

            return;
        }

        pTable = TapiGlobals.pLineLookup;

        while (pTable &&
               !(pTable->dwNumUsedEntries < pTable->dwNumTotalEntries))
        {
            pPrevTable = pTable;

            pTable = pTable->pNext;
        }

        if (!pTable)
        {
            if (!(pTable = ServerAlloc(
                    sizeof (TLINELOOKUPTABLE) +
                        (2 * pPrevTable->dwNumTotalEntries - 1) *
                        sizeof (TLINELOOKUPENTRY)
                    )))
            {
                TapiLeaveCriticalSection (&TapiGlobals.CritSec);
                break;
            }

            pPrevTable->pNext = pTable;

            pTable->dwNumTotalEntries = 2 * pPrevTable->dwNumTotalEntries;
        }


        //
        // Initialize the table entry
        //

        pEntry = pTable->aEntries + pTable->dwNumUsedEntries;

        dwDeviceID = TapiGlobals.dwNumLines;

        if ((pEntry->hMutex = MyCreateMutex()))
        {
            pEntry->ptProvider = (PTPROVIDER) Param1;


            //
            // Now call the creation & negotiation entrypoints, and if all
            // goes well increment the counts & send msgs to the clients
            //

            if ((lResult = CallSP2(
                    pfnTSPI_providerCreateLineDevice,
                    "providerCreateLineDevice",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) Param2,
                    (DWORD) dwDeviceID

                    )) == 0)
            {
                TSPIPROC    pfnTSPI_lineNegotiateTSPIVersion =
                                ptProvider->apfn[SP_LINENEGOTIATETSPIVERSION];
                TPOINTERLIST    clientList, *pClientList = &clientList;


                if ((lResult = CallSP4(
                        pfnTSPI_lineNegotiateTSPIVersion,
                        "",
                        SP_FUNC_SYNC,
                        (DWORD) dwDeviceID,
                        (DWORD) TAPI_VERSION1_0,
                        (DWORD) TAPI_VERSION_CURRENT,
                        (ULONG_PTR) &pEntry->dwSPIVersion

                        )) == 0)
                {
                    PTCLIENT        ptClient;
                    ASYNCEVENTMSG   msg;


                    GetPermLineIDAndInsertInTable(
                        ptProvider,
                        dwDeviceID,
                        pEntry->dwSPIVersion
                        );

                    pTable->dwNumUsedEntries++;

                    TapiGlobals.dwNumLines++;

                    TapiLeaveCriticalSection (&TapiGlobals.CritSec);
                    AppendNewDeviceInfo (TRUE, dwDeviceID);
                    TapiEnterCriticalSection (&TapiGlobals.CritSec);

                    // PERF ** Number of lines
                    PerfBlock.dwLines = TapiGlobals.dwNumLines;

                    msg.TotalSize          = sizeof (ASYNCEVENTMSG);
                    msg.fnPostProcessProcHandle = 0;
                    msg.hDevice            = 0;
                    msg.OpenContext        = 0;
                    msg.Param2             = 0;
                    msg.Param3             = 0;

                    //
                    // Only send the message if the client is an
                    // admin or we're not a telephony server
                    // we don't want to send the message to non-admin
                    // clients, because their lines have not changed.
                    //
                    if (TapiGlobals.dwFlags & TAPIGLOBALS_SERVER)
                    {
                        lResult = GetClientList (TRUE, &pClientList);
                    }
                    else
                    {
                        lResult = GetClientList (FALSE, &pClientList);
                    }
                    if (lResult == S_OK)
                    {
                        DWORD           i;
                        PTLINEAPP       ptLineApp;
                    
                        for (i = 0; i < pClientList->dwNumUsedEntries; ++i)
                        {
                            ptClient = (PTCLIENT) pClientList->aEntries[i];
                            if (!WaitForExclusiveClientAccess (ptClient))
                            {
                                continue;
                            }

                            ptLineApp = ptClient->ptLineApps;

                            while (ptLineApp)
                            {
                                if (ptLineApp->dwAPIVersion == TAPI_VERSION1_0)
                                {
                                    msg.Msg    = LINE_LINEDEVSTATE;
                                    msg.Param1 = LINEDEVSTATE_REINIT;
                                }
                                else
                                {
                                    msg.Msg    = LINE_CREATE;
                                    msg.Param1 = dwDeviceID;
                                }

                                msg.InitContext = ptLineApp->InitContext;

                                if (!FMsgDisabled (
                                    ptLineApp->dwAPIVersion,
                                    ptLineApp->adwEventSubMasks,
                                    (DWORD) msg.Msg,
                                    (DWORD) msg.Param1
                                    ))
                                {
                                    WriteEventBuffer (ptClient, &msg);
                                }

                                ptLineApp = ptLineApp->pNext;
                            }

                            UNLOCKTCLIENT (ptClient);
                        }
                    }
                }
                
                if (pClientList != &clientList)
                {
                    ServerFree (pClientList);
                }
            }

            if (lResult)
            {
                MyCloseMutex (pEntry->hMutex);
            }
        }

        TapiLeaveCriticalSection (&TapiGlobals.CritSec);
        break;
    }
    case LINE_CREATEDIALOGINSTANCE:
    {
        DWORD                               dwDataSize, dwAlignedDataSize,
                                            dwAlignedUIDllNameSize,
                                            dwTotalSize;
        PTCLIENT                            ptClient;
        PASYNCEVENTMSG                      pMsg;
        PASYNCREQUESTINFO                   pAsyncReqInfo;
        PTAPIDIALOGINSTANCE                 ptDlgInst;
        LPTUISPICREATEDIALOGINSTANCEPARAMS  pParams;


        pParams = (LPTUISPICREATEDIALOGINSTANCEPARAMS) Param1;


        //
        // Verify the async request info struct
        //

        if (!(pAsyncReqInfo = (PASYNCREQUESTINFO) ReferenceObject(
                ghHandleTable,
                pParams->dwRequestID,
                TASYNC_KEY
                )))
        {
            pParams->htDlgInst = 0;
            return;
        }

        ptClient = pAsyncReqInfo->ptClient;

        DereferenceObject (ghHandleTable, pParams->dwRequestID, 1);


        //
        // Alloc bufs for the msg & dlg instance, careful to keep offsets
        // & total msg size on 64-bit boundaries
        //

        dwDataSize             = pParams->dwSize;
        dwAlignedDataSize      = (dwDataSize + 7) & 0xfffffff8;
        dwAlignedUIDllNameSize = 0xfffffff8 & (7 +
            ((lstrlenW ((PWSTR) pParams->lpszUIDLLName) + 1)*sizeof (WCHAR)));

        dwTotalSize = sizeof (ASYNCEVENTMSG) + dwAlignedDataSize +
            dwAlignedUIDllNameSize;

        if (!(pMsg = ServerAlloc (dwTotalSize)))
        {
            pParams->htDlgInst = 0;
            return;
        }

        if (!(ptDlgInst = ServerAlloc (sizeof (TAPIDIALOGINSTANCE))))
        {
            ServerFree (pMsg);
            pParams->htDlgInst = 0;
            return;
        }
        ptDlgInst->htDlgInst  = NewObject(ghHandleTable, ptDlgInst, NULL);
        if (0 == ptDlgInst->htDlgInst)
        {
            ServerFree (pMsg);
            ServerFree (ptDlgInst);
            pParams->htDlgInst = 0;
            return;
        }


        //
        // Add the dlg inst to the tClient's list
        //

        LOCKTCLIENT (ptClient);
        if ((ptDlgInst->pNext = ptClient->pGenericDlgInsts))
        {
            ptDlgInst->pNext->pPrev = ptDlgInst;
        }

        ptClient->pGenericDlgInsts = ptDlgInst;
        UNLOCKTCLIENT (ptClient);


        //
        // Init dlg inst struct & send msg to client
        //
        ptDlgInst->dwKey      = TDLGINST_KEY;
        ptDlgInst->hdDlgInst  = pParams->hdDlgInst;
        ptDlgInst->ptClient   = ptClient;
        ptDlgInst->ptProvider = (PTPROVIDER) htLine;

        pMsg->TotalSize = dwTotalSize;
        pMsg->hDevice   = ptDlgInst->htDlgInst;
        try
        {
            if (ptClient->ptLineApps)
            {
                pMsg->InitContext = ptClient->ptLineApps->InitContext;
            }
        }
        myexcept
        {
            pMsg->InitContext = 0;
        }
        pMsg->Msg       = LINE_CREATEDIALOGINSTANCE;
        pMsg->Param1    = sizeof (ASYNCEVENTMSG);           // data offset
        pMsg->Param2    = dwDataSize;                       // data size
        pMsg->Param3    = sizeof (ASYNCEVENTMSG) + dwAlignedDataSize;
                                                            // name offset

        CopyMemory ((LPBYTE)(pMsg + 1), pParams->lpParams, dwDataSize);

        wcscpy(
            (PWSTR) ((LPBYTE)(pMsg + 1) + dwAlignedDataSize),
            (PWSTR) pParams->lpszUIDLLName
            );

        pParams->htDlgInst = ptDlgInst->htDlgInst;

        WriteEventBuffer (ptClient, pMsg);

        ServerFree (pMsg);

        break;
    }
    case LINE_SENDDIALOGINSTANCEDATA:
    {
        DWORD               dwDataSize, dwAlignedDataSize, dwTotalSize;
        PTCLIENT            ptClient;
        PASYNCEVENTMSG      pMsg;
        PTAPIDIALOGINSTANCE ptDlgInst = ReferenceObject (ghHandleTable, DWORD_CAST((ULONG_PTR)htLine,__FILE__,__LINE__), TDLGINST_KEY);


        //
        // Verify the dlg inst
        //

        try
        {
            ptClient = ptDlgInst->ptClient;

            if (ptDlgInst->dwKey != TDLGINST_KEY)
            {
                return;
            }
        }
        myexcept
        {
            return;
        }


        DereferenceObject (ghHandleTable, DWORD_CAST((ULONG_PTR)htLine,__FILE__,__LINE__), 1);

        //
        // Careful to keep offsets & total msg size on 64-bit boundaries
        //

        dwDataSize        = (DWORD) Param2;
        dwAlignedDataSize = (dwDataSize + 7) & 0xfffffff8;
        dwTotalSize       = sizeof (ASYNCEVENTMSG) + dwAlignedDataSize;

        if (!(pMsg = ServerAlloc (dwTotalSize)))
        {
            return;
        }


        //
        // Send the msg to the client
        //

        pMsg->TotalSize = dwTotalSize;
        pMsg->hDevice   = ptDlgInst->htDlgInst;
        try
        {
            if (ptClient->ptLineApps)
            {
                pMsg->InitContext = ptClient->ptLineApps->InitContext;
            }
        }
        myexcept
        {
            pMsg->InitContext = 0;
        }
        pMsg->Msg       = LINE_SENDDIALOGINSTANCEDATA;
        pMsg->Param1    = sizeof (ASYNCEVENTMSG); // data offset
        pMsg->Param2    = dwDataSize;             // data size

        CopyMemory ((LPBYTE)(pMsg + 1), (LPBYTE) Param1, dwDataSize);

        WriteEventBuffer (ptClient, pMsg);

        ServerFree (pMsg);

        break;
    }
    case LINE_REMOVE:
    {
        PTLINELOOKUPENTRY   pLookupEntry;
        HANDLE              hLookupEntryMutex = NULL;
        BOOL                bOK = FALSE;

        TapiEnterCriticalSection (&TapiGlobals.CritSec);

        if (!(pLookupEntry = GetLineLookupEntry ((DWORD) Param1)) || 
            pLookupEntry->bRemoved)
        {
            TapiLeaveCriticalSection(&TapiGlobals.CritSec);
            return;
        }

        if ( pLookupEntry->hMutex )
        {
            bOK = DuplicateHandle(
                        TapiGlobals.hProcess,
                        pLookupEntry->hMutex,
                        TapiGlobals.hProcess,
                        &hLookupEntryMutex,
                        0,
                        FALSE,
                        DUPLICATE_SAME_ACCESS
                        );
        }

        TapiLeaveCriticalSection(&TapiGlobals.CritSec); 

        if ( !bOK )
        {
            return;
        }

        //
        // Wait for the LookupEntry's mutex on the duplicate handle
        //
        if (WaitForSingleObject (hLookupEntryMutex, INFINITE)
                    != WAIT_OBJECT_0)
        {
            return;
        }

        //
        // Mark the lookup table entry as removed
        //

        pLookupEntry->bRemoved = 1;

        //
        // Release the mutex and close the duplicate handle
        //
        ReleaseMutex (hLookupEntryMutex);
        CloseHandle (hLookupEntryMutex);
        hLookupEntryMutex = NULL;

        if (pLookupEntry->ptLine)
        {
            DestroytLine (pLookupEntry->ptLine, TRUE); // unconditional destroy
        }

        TapiEnterCriticalSection (&TapiGlobals.CritSec);

        //
        // Close the mutex to reduce overall handle count
        //
        MyCloseMutex (pLookupEntry->hMutex);
        pLookupEntry->hMutex = NULL;

        RemoveDeviceInfoEntry (TRUE, (DWORD)Param1);

        TapiLeaveCriticalSection(&TapiGlobals.CritSec); 

        SendAMsgToAllLineApps(
            TAPI_VERSION2_0 | 0x80000000,
            LINE_REMOVE,
            DWORD_CAST(Param1,__FILE__,__LINE__),
            0,
            0
            );

        break;
    }
    case LINE_APPNEWCALLHUB:
    case LINE_CALLHUBCLOSE:
    {
        //
        // This msg gets queued/sent by our own internal
        // DoCallHubHashing func.  See comments there for
        // more info.
        //

        ASYNCEVENTMSG   msg;
        
        PTCLIENT ptClient = NULL;

        msg.TotalSize          = sizeof (ASYNCEVENTMSG);
        
        msg.InitContext        = DWORD_CAST(Param2,__FILE__,__LINE__);
        msg.fnPostProcessProcHandle = 0;
        msg.hDevice            = 0;
        msg.Msg                = dwMsg;
        msg.OpenContext        = 0;
        msg.Param1             = DWORD_CAST(Param1,__FILE__,__LINE__);
        msg.Param2             =
        msg.Param3             = 0;

        
        //
        // try to recover the pointer to tClient from the 32-bit handle value
        // 

        ptClient = (PTCLIENT) Param3;

        if (NULL != ptClient)
        {
            WriteEventBuffer (ptClient, &msg);
        }
        else
        {
            LOG((TL_ERROR, "LineEventProc: LINE_APPNEWCALLHUB/LINE_CALLHUBCLOSE failed to recover ptClient"));
        }

        break;
    }

    case LINE_SENDMSPDATA:
    {
        PASYNCEVENTMSG          pmsg;
        PTLINECLIENT            ptLineClient = NULL;
        PTCALLCLIENT            ptCallClient = NULL;
        DWORD                   dwSize = (sizeof (ASYNCEVENTMSG) +
                                    DWORD_CAST(Param3,__FILE__,__LINE__) + TALIGN_COUNT) &
                                    TALIGN_MASK;
        DWORD                   initContext;
        PTCLIENT                ptClient = NULL;
        DWORD                   dwCount;
        BOOL                    bFound = FALSE;
        TPOINTERLIST            clientList, *pClientList = &clientList;
        PTCALL                  ptCall = (PTCALL)htCall;
        DWORD                   hLine = 0, hCall = 0;


        if ( (0 == Param1) && (0 == htCall) )
        {
            return;
        }
        
        if ( 0 != Param1 )
        {
            if (!(ptLineClient = (PTLINECLIENT) ReferenceObject(
                    ghHandleTable,
                    DWORD_CAST(Param1,__FILE__,__LINE__),
                    TLINECLIENT_KEY
                    )))
            {
                return;
            }

            ptClient = ptLineClient->ptClient;
            
            initContext = ptLineClient->ptLineApp->InitContext;
        }


        //
        // If ptCall isn't NULL, try to find the call client
        // corresponding to this line client
        //

        if (0 != htCall)
        {
            if ( NULL == ptLineClient )
            {
                SendBufferMsgToCallClients(
                    ptCall,
                    NULL,
                    LINE_SENDMSPDATA,
                    0,
                    DWORD_CAST(Param3,__FILE__,__LINE__),
                    (LPBYTE)Param2
                    );

                return;
            }

            if (GetCallClientListFromCall (ptCall, &pClientList) == 0)
            {
                for(
                    dwCount = 0;
                    dwCount < pClientList->dwNumUsedEntries;
                    dwCount++
                    )
                {
                    ptCallClient = pClientList->aEntries[dwCount];

                    try
                    {
                        if ( ptCallClient->ptLineClient == ptLineClient )
                        {
                            bFound = TRUE;
                            break;
                        }
                    }
                    myexcept
                    {
                        // do nothing
                    }
                }

                if ( pClientList != &clientList )
                {
                    ServerFree( pClientList );
                }
            }

            if ( !bFound )
            {
                //
                // Didn't find it for some reason
                //

                DereferenceObject( ghHandleTable, DWORD_CAST(Param1,__FILE__,__LINE__), 1 );

                return;
            }
        }

        if ( NULL != ptLineClient )
        {
            hLine = ptLineClient->hLine;
            DereferenceObject( ghHandleTable, DWORD_CAST(Param1,__FILE__,__LINE__), 1 );
        }

        if ( NULL != ptCallClient )
        {
            hCall = ptCallClient->hCall;
        }
        
        pmsg = ( PASYNCEVENTMSG )ServerAlloc( dwSize );

        if (NULL == pmsg)
        {
            LOG((TL_ERROR, "Alloc failed in LINE_SENDMSPDATA"));

            return;
        }

        CopyMemory ((LPBYTE) (pmsg+1), (LPBYTE) Param2, Param3);

        pmsg->TotalSize             = dwSize;
        pmsg->InitContext           = initContext;
        pmsg->fnPostProcessProcHandle    = 0;
        pmsg->hDevice               = hLine;
        pmsg->Msg                   = LINE_SENDMSPDATA;
        pmsg->OpenContext           = 0;
        pmsg->Param1                = hCall;
        pmsg->Param2                = DWORD_CAST(Param3,__FILE__,__LINE__);
        pmsg->Param3                = 0;

        WriteEventBuffer ( ptClient, pmsg );

        ServerFree( pmsg );

        break;
    }

    case LINE_QOSINFO:
    {
        ASYNCEVENTMSG   msg;
        PTCALL          ptCall = (PTCALL)htCall;
        TPOINTERLIST    clientList, *pclientList = &clientList;
        int             i;

        if (0 == htCall)
        {
            break;
        }

        clientList.dwNumUsedEntries = 0;
        if (GetCallClientListFromCall (ptCall, &pclientList) == 0)
        {
            PTCALLCLIENT    ptCallClient;
            
            msg.TotalSize          = sizeof (ASYNCEVENTMSG);
            msg.fnPostProcessProcHandle = 0;
            msg.Msg                = dwMsg;
            msg.OpenContext        = 0;
            msg.Param1             = DWORD_CAST(Param1,__FILE__,__LINE__);
            msg.Param2             = DWORD_CAST(Param2,__FILE__,__LINE__);
            msg.Param3             = 0;

            for (i = 0; i < (int)pclientList->dwNumUsedEntries; ++ i)
            {
                ptCallClient = (PTCALLCLIENT) pclientList->aEntries[i];
                if (WaitForExclusivetCallAccess (ptCallClient->ptCall, TCALL_KEY))
                {
                    BOOL    b = FMsgDisabled(
                        ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                        ptCallClient->adwEventSubMasks,
                        LINE_QOSINFO,
                        0
                        );
                    UNLOCKTCALL (ptCallClient->ptCall);
                    if (b)
                    {
                        continue;
                    }
                }
                msg.InitContext        = 
                    ptCallClient->ptLineClient->ptLineApp->InitContext;
                msg.hDevice = ptCallClient->hCall;
                WriteEventBuffer (ptCallClient->ptClient, &msg);
            }

            if (pclientList && (pclientList != &clientList))
            {
                ServerFree (pclientList);
            }
        }

        break;
    }

    case LINE_DEVSPECIFICEX:
    {
        PTLINE  ptLine = (PTLINE)htLine;


        SendBufferMsgToLineClients(
            ptLine,
            NULL,
            LINE_DEVSPECIFICEX,
            DWORD_CAST(Param1,__FILE__,__LINE__),
            DWORD_CAST(Param2,__FILE__,__LINE__),
            (LPBYTE) Param3
            );

        break;
    }
    default:

        // if DBG assert (unrecognized dwMsg)

        break;
    }
}


void
CALLBACK
LineEventProcSP(
    HTAPILINE   htLine,
    HTAPICALL   htCall,
    DWORD       dwMsg,
    ULONG_PTR   Param1,
    ULONG_PTR   Param2,
    ULONG_PTR   Param3
    )
{
    PSPEVENT    pSPEvent;


#if DBG
    if (gdwDebugLevel >= 3)
    {
        char           *pszMsg;
        static char     szInvalMsgVal[] = "<inval msg value>";
        static char    *aszMsgs[] =
        {
            "LINE_ADDRESSSTATE",
            "LINE_CALLINFO",
            "LINE_CALLSTATE",
            "LINE_CLOSE",
            "LINE_DEVSPECIFIC",
            "LINE_DEVSPECIFICFEATURE",
            "LINE_GATHERDIGITS",
            "LINE_GENERATE",
            "LINE_LINEDEVSTATE",
            "LINE_MONITORDIGITS",
            "LINE_MONITORMEDIA",
            "LINE_MONITORTONE",
            szInvalMsgVal,              // LINE_REPLY
            szInvalMsgVal,              // LINE_REQUEST
            szInvalMsgVal,              // PHONE_BUTTON
            szInvalMsgVal,              // PHONE_CLOSE
            szInvalMsgVal,              // PHONE_DEVSPECIFIC
            szInvalMsgVal,              // PHONE_REPLY
            szInvalMsgVal,              // PHONE_STATE
            "LINE_CREATE",
            szInvalMsgVal,              // PHONE_CREATE
            "LINE_AGENTSPECIFIC",
            "LINE_AGENTSTATUS",
            szInvalMsgVal,              // LINE_APPNEWCALL
            "LINE_PROXYREQUEST",
            "LINE_REMOVE",
            szInvalMsgVal,              // PHONE_REMOVE

            "LINE_NEWCALL",
            "LINE_CALLDEVSPECIFIC",
            "LINE_CALLDEVSPECIFICFEATURE",
            "LINE_CREATEDIALOGINSTANCE",
            "LINE_SENDDIALOGINSTANCEDATA"
        };


        if (dwMsg <= PHONE_REMOVE)
        {
            pszMsg = aszMsgs[dwMsg];
        }
        else if (dwMsg >= LINE_NEWCALL && dwMsg <= LINE_SENDDIALOGINSTANCEDATA)
        {
            pszMsg = aszMsgs[27 + dwMsg - TSPI_MESSAGE_BASE];
        }
        else
        {
            pszMsg = szInvalMsgVal;
        }

        LOG((TL_TRACE, 
            "LineEventProcSP: enter\n" \
                "\t   Msg=%s (x%x), htLine=x%x, htCall=x%x",
            pszMsg,
            dwMsg,
            htLine,
            htCall
            ));

        if (dwMsg == LINE_CALLSTATE)
        {
            char           *pszCallState;
            static char     szInvalCallStateVal[] = "<inval callstate value>";
            static char    *aszCallStates[] =
            {
                "IDLE",
                "OFFERING",
                "ACCEPTED",
                "DIALTONE",
                "DIALING",
                "RINGBACK",
                "BUSY",
                "SPECIALINFO",
                "CONNECTED",
                "PROCEEDING",
                "ONHOLD",
                "CONFERENCED",
                "ONHOLDPENDCONF",
                "ONHOLDPENDTRANSFER",
                "DISCONNECTED",
                "UNKNOWN"
            };


            if (!IsOnlyOneBitSetInDWORD(Param1) ||
                Param1 > LINECALLSTATE_UNKNOWN)
            {
                pszCallState = szInvalCallStateVal;
            }
            else
            {
                DWORD   i, dwBitMask;

                for(
                    i = 0, dwBitMask = 1;
                    Param1 != dwBitMask;
                    i++, dwBitMask <<= 1
                    );


                pszCallState = aszCallStates[i];
            }

            LOG((TL_INFO,
                "  P1=%s (x%x), P2=x%x, P3=x%x",
                pszCallState,
                Param1,
                Param2,
                Param3
                ));
        }
        else
        {
            LOG((TL_INFO,
                "  P1=x%x, P2=x%x, P3=x%x",
                Param1,
                Param2,
                Param3
                ));
        }
    }
#else
    LOG((TL_TRACE, "LineEventProcSP - enter"));
    LOG((TL_INFO, 
        "HTapiLine=%p, HTapiCall=%p, msg=%lx, P1=x%x, P2=x%x, P3=x%x",
        htLine,
        htCall,
        dwMsg,
        Param1,
        Param2,
        Param3
    ));

#endif


    switch (dwMsg)
    {
    case LINE_NEWCALL:
    case LINE_CREATEDIALOGINSTANCE:
    case LINE_SENDDIALOGINSTANCEDATA:
    case LINE_DEVSPECIFICEX:
        //
        // These msgs need immediate attention, since they contain
        // pointers that we need to play with which may not be
        // available during async processing later
        //

        LineEventProc (htLine, htCall, dwMsg, Param1, Param2, Param3);
        break;

    case LINE_SENDMSPDATA:
        if ((pSPEvent = (PSPEVENT) ServerAlloc ( sizeof (SPEVENT) + (DWORD)Param3)))
        {
            CopyMemory(
                       (LPBYTE)(pSPEvent+1),
                       (LPBYTE)Param2,
                       Param3
                      );
            
            pSPEvent->dwType   = SP_LINE_EVENT;
            pSPEvent->htLine   = htLine;
            pSPEvent->htCall   = htCall;
            pSPEvent->dwMsg    = dwMsg;
            pSPEvent->dwParam1 = Param1;
            pSPEvent->dwParam2 = (ULONG_PTR)(LPBYTE)(pSPEvent+1);
            pSPEvent->dwParam3 = Param3;

            if (!QueueSPEvent (pSPEvent))
            {
                ServerFree (pSPEvent);
            }

            break;
        }

    default:

        if ((pSPEvent = (PSPEVENT) ServerAlloc (sizeof (SPEVENT))))
        {
            pSPEvent->dwType   = SP_LINE_EVENT;
            pSPEvent->htLine   = htLine;
            pSPEvent->htCall   = htCall;
            pSPEvent->dwMsg    = dwMsg;
            pSPEvent->dwParam1 = Param1;
            pSPEvent->dwParam2 = Param2;
            pSPEvent->dwParam3 = Param3;

            if (!QueueSPEvent (pSPEvent))
            {
                ServerFree (pSPEvent);
            }
        }
        else if (dwMsg != LINE_CLOSE  ||  Param3 != 0xdeadbeef)
        {
            //
            // Alloc failed, so call the event proc within the SP's context
            // (but not if it's  CLOSE msg and Param3 == 0xdeadbeef,
            // which means the real EventProc() is calling us directly &
            // we don't want to recurse)
            //

            LOG((TL_ERROR,
                "LineEventProcSP: alloc failed, calling EventProc inline"
                ));

            LineEventProc (htLine, htCall, dwMsg, Param1, Param2, Param3);
        }

        break;
    }

}


void
WINAPI
LAccept(
	PTCLIENT			ptClient,
    PLINEACCEPT_PARAMS  pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;
    TSPIPROC            pfnTSPI_lineAccept;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwUserUserInfoOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSize,
            pParams->dwUserUserInfoOffset,
            sizeof(DWORD),
            "LAccept",
            "pParams->UserUserInfo"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEACCEPT,              // provider func index
            &pfnTSPI_lineAccept,        // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Accept"                    // func name

            )) > 0)
    {
        DWORD   dwAppNameSize;
        LPVOID  pszAppName = NULL;
        PTCALL  ptCall;


        //
        // Safely check to see if the app name associated with this call is
        // NULL (meaning this is the first client to accept/answer the call),
        // and if so save the app name
        //

        try
        {
            ptCall = (PTCALL) ptCallClient->ptCall;

            if (ptCall->pszAppName == NULL)
            {
                PTLINEAPP   ptLineApp;


                ptLineApp = ptCallClient->ptLineClient->ptLineApp;

                dwAppNameSize = ptLineApp->dwFriendlyNameSize;

                if (ptLineApp->dwKey != TLINEAPP_KEY)
                {
                    lRequestID = LINEERR_INVALCALLHANDLE;
                    goto LAccept_epilog;
                }

                if ((pszAppName = ServerAlloc (dwAppNameSize)))
                {
                    CopyMemory(
                        pszAppName,
                        ptLineApp->pszFriendlyName,
                        dwAppNameSize
                        );
                }
            }
        }
        myexcept
        {
            ServerFree (pszAppName);

            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LAccept_epilog;
        }


        if (pszAppName)
        {
            if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
            {
                if (!ptCall->pszAppName)
                {
                    ptCall->pszAppName = pszAppName;
                    ptCall->dwAppNameSize = dwAppNameSize;

                    pszAppName = NULL;
                }

                UNLOCKTCALL (ptCall);

                ServerFree (pszAppName);
            }
            else
            {
                ServerFree (pszAppName);

                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LAccept_epilog;
            }
        }

        pParams->lResult = CallSP4(
            pfnTSPI_lineAccept,
            "lineAccept",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwUserUserInfoOffset),
            (DWORD) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? 0 :
                pParams->dwSize)
                );
    }

LAccept_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Accept"
        );
}


void
LAddToConference_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PTCALL  ptConsultCall = (PTCALL) pAsyncRequestInfo->dwParam1;


    if (pAsyncEventMsg->Param2 == 0)
    {
        PTCONFERENCELIST    pConfList = (PTCONFERENCELIST)
                                pAsyncRequestInfo->dwParam2;


        SetCallConfList (ptConsultCall, pConfList, TRUE);
    }
    else
    {
        SetCallConfList (ptConsultCall, NULL, TRUE);
    }
}


void
WINAPI
LAddToConference(
	PTCLIENT					ptClient,
    PLINEADDTOCONFERENCE_PARAMS pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdConfCall;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptConsultCallClient, ptConfCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;
    TSPIPROC            pfnTSPI_lineAddToConference;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hConfCall, // client widget handle
            (LPVOID) &hdConfCall,       // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEADDTOCONFERENCE,     // provider func index
            &pfnTSPI_lineAddToConference,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptConfCallClient,          // context
            "AddToConference"           // func name

            )) > 0)
    {
        PTCALL              ptConfCall, ptConsultCall;
        HDRVCALL            hdConsultCall;
        PTCONFERENCELIST    pConfList;


        //
        // Safely make sure that the conf call is really a conf parent
        //

        try
        {
            ptConfCall = ptConfCallClient->ptCall;

            if (!(pConfList = ptConfCall->pConfList) ||
                (pConfList->aptCalls[0] != ptConfCall))
            {
                lRequestID = LINEERR_INVALCONFCALLHANDLE;
                goto LAddToConference_return;
            }
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCONFCALLHANDLE;
            goto LAddToConference_return;
        }

        //
        // Verify hConsultCall
        //

        if (!(ptConsultCallClient = ReferenceCall(
                pParams->hConsultCall,
                ptClient
                )))
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LAddToConference_return;
        }


        //
        // Safely make sure calls are on same tLineClient, that client has
        // owner privilege for consult call, and that the consult call
        // is neither a conf parent or child (call SetCallConfList
        // with an inval list to temporarily mark the call as conf'd)
        //

        try
        {
            ptConsultCall = ptConsultCallClient->ptCall;

            if (ptConsultCallClient->ptLineClient !=
                    ptConfCallClient->ptLineClient)
            {
                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LAddToConference_dereference;
            }

            if (!(ptConsultCallClient->dwPrivilege & LINECALLPRIVILEGE_OWNER))
            {
                lRequestID = LINEERR_NOTOWNER;
                goto LAddToConference_dereference;
            }

            if (SetCallConfList(
                    ptConsultCall,
                    (PTCONFERENCELIST) LongToPtr(0xffffffff),
                    FALSE
                    ))
            {
                lRequestID = (pConfList->aptCalls[0] == ptConsultCall ?
                     LINEERR_INVALCALLHANDLE : LINEERR_INVALCALLSTATE);

                goto LAddToConference_dereference;
            }

            hdConsultCall = ptConsultCall->hdCall;
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LAddToConference_dereference;
        }


        //
        // Set up the async request struct & call the SP
        //

        pAsyncRequestInfo->pfnPostProcess = LAddToConference_PostProcess;
        pAsyncRequestInfo->dwParam1       = (ULONG_PTR) ptConsultCall;
        pAsyncRequestInfo->dwParam2       = (ULONG_PTR) pConfList;

        pParams->lResult = CallSP3(
            pfnTSPI_lineAddToConference,
            "lineAddToConference",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdConfCall,
            (ULONG_PTR) hdConsultCall
            );

LAddToConference_dereference:

        DereferenceObject (ghHandleTable, pParams->hConsultCall, 1);

    }


LAddToConference_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "AddToConference"
        );
}


void
WINAPI
LAgentSpecific(
	PTCLIENT					ptClient,
    PLINEAGENTSPECIFIC_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwParamsSize,
            pParams->dwParamsOffset,
            sizeof(DWORD),
            "LAgentSpecific",
            "pParams->Params"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "AgentSpecific"             // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID, dwParamsSize = pParams->dwParamsSize;
        PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                pParams->dwAddressID,
                LINEPROXYREQUEST_AGENTSPECIFIC,
                &pProxy,
                &dwDeviceID,
                0               // API ver wasn't checked in 2.0
                )))
        {
            lRequestID = lResult;
            goto LAgentSpecific_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpParams;
        pAsyncRequestInfo->dwParam2 = dwParamsSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_AGENTSPECIFIC,
                    3 * sizeof (DWORD) + dwParamsSize,
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LAgentSpecific_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.AgentSpecific.dwAddressID  =
                pParams->dwAddressID;
            pProxyRequestWrapper->ProxyRequest.AgentSpecific.
                dwAgentExtensionIDIndex  = pParams->dwAgentExtensionIDIndex;
            pProxyRequestWrapper->ProxyRequest.AgentSpecific.dwSize  =
                dwParamsSize;

            CopyMemory(
                pProxyRequestWrapper->ProxyRequest.AgentSpecific.Params,
                pDataBuf + pParams->dwParamsOffset,
                dwParamsSize
                );

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LAgentSpecific_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE  pBuf;


            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwParamsSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LAgentSpecific_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess = LDevSpecific_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            CopyMemory(
                pBuf + sizeof (ASYNCEVENTMSG),
                pDataBuf + pParams->dwParamsOffset,
                dwParamsSize
                );

            pParams->lResult = CallSP6(
                pRemoteSP->apfn[SP_LINEAGENTSPECIFIC],
                "lineAgentSpecific",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwAddressID,
                (DWORD) pParams->dwAgentExtensionIDIndex,
                (ULONG_PTR) (pBuf + sizeof (ASYNCEVENTMSG)),
                (DWORD) dwParamsSize
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LAgentSpecific_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "AgentSpecific"
        );
}

void
WINAPI
LAnswer(
	PTCLIENT			ptClient,
    PLINEANSWER_PARAMS  pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineAnswer;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwUserUserInfoOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwUserUserInfoSize,
            pParams->dwUserUserInfoOffset,
            sizeof(DWORD),
            "LAnswerReal",
            "pParams->UserUserInfo"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEANSWER,              // provider func index
            &pfnTSPI_lineAnswer,        // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Answer"                    // func name

            )) > 0)
    {
        DWORD           dwAppNameSize;
        LPVOID          pszAppName = NULL;
        PTCALL          ptCall;


        //
        // Safely check to see if the app name associated with this call is
        // NULL (meaning this is the first client to accept/answer the call),
        // and if so save the app name
        //

        try
        {
            ptCall = (PTCALL) ptCallClient->ptCall;

            if (ptCall->pszAppName == NULL)
            {
                PTLINEAPP   ptLineApp;


                ptLineApp = ptCallClient->ptLineClient->ptLineApp;

                dwAppNameSize = ptLineApp->dwFriendlyNameSize;

                if (ptLineApp->dwKey != TLINEAPP_KEY)
                {
                    lRequestID = LINEERR_INVALCALLHANDLE;
                    goto LAnswer_epilog;
                }

                if ((pszAppName = ServerAlloc (dwAppNameSize)))
                {
                    CopyMemory(
                        pszAppName,
                        ptLineApp->pszFriendlyName,
                        dwAppNameSize
                        );
                }
            }
        }
        myexcept
        {
            ServerFree (pszAppName);

            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LAnswer_epilog;
        }


        if (pszAppName)
        {
            if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
            {
                if (!ptCall->pszAppName)
                {
                    ptCall->pszAppName = pszAppName;
                    ptCall->dwAppNameSize = dwAppNameSize;

                    pszAppName = NULL;
                }

                UNLOCKTCALL (ptCall);

                ServerFree (pszAppName);
            }
            else
            {
                ServerFree (pszAppName);

                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LAnswer_epilog;
            }
        }

        pParams->lResult = CallSP4(
                pfnTSPI_lineAnswer,
                "lineAnswer",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdCall,
                (ULONG_PTR) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ?
                    NULL : pDataBuf + pParams->dwUserUserInfoOffset),
                (DWORD) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ?
                    0 : pParams->dwUserUserInfoSize)
                );
    }

LAnswer_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Answer"
        );
}


void
WINAPI
LBlindTransfer(
    PTCLIENT                    ptClient,
    PLINEBLINDTRANSFER_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineBlindTransfer;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDestAddressOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEBLINDTRANSFER,       // provider func index
            &pfnTSPI_lineBlindTransfer, // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "BlindTransfer"             // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineBlindTransfer,
            "lineBlindTransfer",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pDataBuf + pParams->dwDestAddressOffset),
            (DWORD) pParams->dwCountryCode
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "BlindTransfer"
        );
}


void
WINAPI
LClose(
    PTCLIENT            ptClient,
    PLINECLOSE_PARAMS   pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            NULL,                       // mutex handle
            NULL,                       // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "Close"                     // func name

            )) == 0)
    {
        if (NULL != ptLineClient)
        {
            pParams->dwCallbackInstance = ptLineClient->OpenContext;
        }
        DestroytLineClient ((HLINE) pParams->hLine);
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        NULL,
        FALSE,
        objectToDereference,
        "Close"
        );

    LOG((TL_TRACE,  "Leaving lineClose"));
}


void
WINAPI
LCloseMSPInstance(
    PTCLIENT                        ptClient,
    PLINECLOSEMSPINSTANCE_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineCloseMSPInstance;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    

    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINECLOSEMSPINSTANCE,    // provider func index
            &pfnTSPI_lineCloseMSPInstance,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID,
            &objectToDereference,
            &ptLineClient,
            "CloseMSPInstance"         // func name

            )) == 0)
    {
        pParams->lResult = CallSP1(
            pfnTSPI_lineCloseMSPInstance,
            "lineCloseMSPInstance",
            SP_FUNC_SYNC,
            (ULONG_PTR) (ptLineClient->hdMSPLine)
            );
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "CloseMSPInstance"
        );
}


void
LCompleteCall_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    pAsyncEventMsg->Param3 = DWORD_CAST(pAsyncRequestInfo->dwParam1,__FILE__,__LINE__);
    pAsyncEventMsg->Param4 = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__);
}


void
WINAPI
LCompleteCall(
    PTCLIENT                    ptClient,
    PLINECOMPLETECALL_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineCompleteCall;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,                   // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINECOMPLETECALL,        // provider func index
            &pfnTSPI_lineCompleteCall,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "CompleteCall"              // func name

            )) > 0)
    {
        if (!IsOnlyOneBitSetInDWORD (pParams->dwCompletionMode) ||
            (pParams->dwCompletionMode & ~AllCallComplModes)
            )
        {
            lRequestID = LINEERR_INVALCALLCOMPLMODE;
            goto LCompleteCall_epilog;
        }

        
        pAsyncRequestInfo->pfnPostProcess = LCompleteCall_PostProcess;
        pAsyncRequestInfo->dwParam2       = pParams->hpdwCompletionID;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP5(
            pfnTSPI_lineCompleteCall,
            "lineCompleteCall",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) &pAsyncRequestInfo->dwParam1,
            (DWORD) pParams->dwCompletionMode,
            (DWORD) pParams->dwMessageID
            );
    }

LCompleteCall_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "CompleteCall"
        );
}


void
LCompleteTransfer_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PTCALL          ptConfCall = (PTCALL) pAsyncRequestInfo->dwParam1;
    DWORD           hpConfCallHandle = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__);
    PTCALLCLIENT    ptConfCallClient;


    if (WaitForExclusivetCallAccess (ptConfCall, TINCOMPLETECALL_KEY))
    {
        PTCALL      ptCall = (PTCALL) pAsyncRequestInfo->dwParam3,
                    ptConsultCall = (PTCALL) pAsyncRequestInfo->dwParam4;
        PTCALL      ptCallThen = (PTCALL) pAsyncRequestInfo->dwParam5;

        //
        // Check to make sure this is the call we think it is (that the
        // pointer wasn't freed by a previous call to lineClose/Shutdown
        // and realloc'd for use as a ptCall again)
        //

        if (ptConfCall != ptCallThen)
        {
            UNLOCKTCALL(ptConfCall);
            goto LCompleteTransfer_PostProcess_bad_ptConfCall;
        }

        ptConfCallClient = ptConfCall->ptCallClients;

        if (pAsyncEventMsg->Param2 == 0)  // success
        {
            //
            // Check to see if the app closed the line & left us with
            // 0 call clients (in which case it'll also be taking care of
            // cleaning up this tCall too)
            //

            if (ptConfCall->ptCallClients == NULL)
            {
                UNLOCKTCALL(ptConfCall);

                ptConfCallClient = (PTCALLCLIENT) NULL;

                if (pAsyncEventMsg->Param2 == 0)
                {
                    pAsyncEventMsg->Param2 = LINEERR_INVALLINEHANDLE;
                }

                goto LCompleteTransfer_PostProcess_initMsgParams;
            }


            //
            // Retrieve the various call IDs, then check if call
            // client was destroyed by another thread (due to
            // lineClose/Shutdown) while we were getting the call ID.
            // If so, we'll need to clean up the tCall, since we know
            // the other thread didn't do it because GetCallIDs marks
            // the call as a zombie.
            //

            GetCallIDs (ptConfCall);

            if (ptConfCall->ptCallClients == NULL)
            {
                goto LCompleteTransfer_PostProcess_cleanupCalls;
            }


            //
            // Stuff the various call IDs in the var data section
            // of the ASYNCEVENTMSG.
            //
            // Make sure to increment the dwTotalSize of the ASYNCEVENTMSG
            // as appropriate.  We rely on the fact that CompletionProc()
            // calls us with a AsyncEventMsg buffer which is big enough to
            // handle a few extra DWORDs.
            //

            pAsyncEventMsg->Param3 = ptConfCallClient->hCall;

            pAsyncEventMsg->TotalSize += 3 * sizeof (pAsyncEventMsg->Param4);

            *(&pAsyncEventMsg->Param4 + 1) = ptConfCall->dwAddressID;
            *(&pAsyncEventMsg->Param4 + 2) = ptConfCall->dwCallID;
            *(&pAsyncEventMsg->Param4 + 3) = ptConfCall->dwRelatedCallID;


            //
            // Mark the calls & conf list as valid, the release the mutex.
            //

            ptConfCall->dwKey       = TCALL_KEY;
            ptConfCallClient->dwKey = TCALLCLIENT_KEY;

            ptConfCall->pConfList->dwKey = TCONFLIST_KEY;

            UNLOCKTCALL(ptConfCall);


            //
            // Create monitor tCallClients
            //

            if (ptCallThen == ptConfCall)
            {
                CreateCallMonitors (ptConfCall, FALSE);
            }
        }
        else    // error
        {

LCompleteTransfer_PostProcess_cleanupCalls:

            //
            // Invalidate the tCall, & if there's still a tCallClient
            // (might have already been destroyed by a lineClose/Shutdown
            // in another thread) invalidate it too. Then unlock the
            // tCall & remove the object(s) from the list(s).
            //

            ptConfCall->dwKey =
                ptConfCall->pConfList->dwKey = INVAL_KEY;

            if (ptConfCall->ptCallClients)
            {
                ptConfCallClient->dwKey = INVAL_KEY;
                ptConfCall->lActiveFastCallClients--;
            }
            else
            {
                ptConfCallClient = NULL;
            }

            UNLOCKTCALL(ptConfCall);

            RemoveCallFromLineList (ptConfCall);

            if (ptConfCallClient)
            {
                DereferenceObject (ghHandleTable, ptConfCallClient->hCall, 1);
                RemoveCallClientFromLineClientList (ptConfCallClient);
            }

            SetCallConfList (ptCall, NULL, FALSE);
            SetCallConfList (ptConsultCall, NULL, FALSE);


            //
            // Make sure all fast call clients cleaned up before free tCall
            //

            while (ptConfCall->lActiveFastCallClients != 0)
            {
                Sleep (5);
            }

            ServerFree  (ptConfCall->pConfList);
            FreetCall  (ptConfCall);
        }
    }
    else
    {
        //
        // If here we can assume that the call was already destroyed
        // and just fail the request
        //

LCompleteTransfer_PostProcess_bad_ptConfCall:

        ptConfCallClient = (PTCALLCLIENT) NULL;

        if (pAsyncEventMsg->Param2 == 0)
        {
            pAsyncEventMsg->Param2 = LINEERR_OPERATIONFAILED;
        }
    }


    //
    // Fill in the params to pass to client (important to remotesp in both
    // the success & fail cases so it can either init or clean up drvCall)
    //

LCompleteTransfer_PostProcess_initMsgParams:

    pAsyncEventMsg->Param4 = hpConfCallHandle;
}


void
WINAPI
LCompleteTransfer(
    PTCLIENT                        ptClient,
    PLINECOMPLETETRANSFER_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex, bDereferenceConsultCall = FALSE;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineCompleteTransfer;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,                   // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINECOMPLETETRANSFER,    // provider func index
            &pfnTSPI_lineCompleteTransfer,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "CompleteTransfer"          // func name

            )) > 0)
    {
        PTCALL          ptConfCall = (PTCALL) NULL, ptCall, ptConsultCall;
        PTCALLCLIENT    ptConfCallClient, ptConsultCallClient;


        //
        // Validate the hConsultCall
        //

        if (!(ptConsultCallClient = ReferenceObject(
                ghHandleTable,
                pParams->hConsultCall,
                TCALLCLIENT_KEY
                )))
        {
            lRequestID = LINEERR_INVALCONSULTCALLHANDLE;
            goto LCompleteTransfer_return;
        }

        bDereferenceConsultCall = TRUE;

        if (ptConsultCallClient->ptClient != ptClient)
        {
            lRequestID = LINEERR_INVALCONSULTCALLHANDLE;
            goto LCompleteTransfer_return;
        }


        //
        // Verify that app has owner privilege for hConsultCall
        //

        if (ptConsultCallClient->dwPrivilege != LINECALLPRIVILEGE_OWNER)
        {
            lRequestID = LINEERR_NOTOWNER;
            goto LCompleteTransfer_return;
        }


        //
        // Safely verify hCall & hConsultCall are not the same call,
        // and that they are on the same tLine
        //

        try
        {
            ptCall        = ptCallClient->ptCall;
            ptConsultCall = ptConsultCallClient->ptCall;

            if ((ptCall == ptConsultCall) ||

                (ptCallClient->ptLineClient->ptLine !=
                    ptConsultCallClient->ptLineClient->ptLine))
            {
                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LCompleteTransfer_return;
            }
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LCompleteTransfer_return;
        }


        if (pParams->dwTransferMode == LINETRANSFERMODE_CONFERENCE)
        {
            LONG                lResult;
            PTCONFERENCELIST    pConfList;


            //
            // Create & init a conf list
            //

            if (!(pConfList = ServerAlloc(
                    sizeof (TCONFERENCELIST) + DEF_NUM_CONF_LIST_ENTRIES *
                        sizeof (PTCALL)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LCompleteTransfer_return;
            }

            pConfList->dwNumTotalEntries = DEF_NUM_CONF_LIST_ENTRIES + 1;
            pConfList->dwNumUsedEntries = 1;


            //
            // Set the tCall & tConsultCall conf list, then create
            // the tConfCall & tConfCallClient
            //

            if ((lResult = SetCallConfList (ptCall, pConfList, FALSE)) == 0)
            {
                if ((lResult = SetCallConfList(
                        ptConsultCall,
                        pConfList,
                        FALSE

                        )) == 0)
                {
                    if ((lResult = CreatetCallAndClient(
                            ptCallClient->ptLineClient,
                            &ptConfCall,
                            &ptConfCallClient,
                            NULL,
                            NULL

                            )) == 0)
                    {
                        //htConfCall = ptConfCall->htCall;
                        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptConfCall;

                        ptConfCall->pConfList = pConfList;

                        pConfList->aptCalls[0] = ptConfCall;

                        pAsyncRequestInfo->htXxx    = (ULONG_PTR) ptConfCallClient->ptLineClient->ptLine;
                        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptConfCall;
                        pAsyncRequestInfo->dwParam2 = (ULONG_PTR) pParams->hpConfCallHandle;
                        pAsyncRequestInfo->dwParam3 = (ULONG_PTR) ptCall;
                        pAsyncRequestInfo->dwParam4 = (ULONG_PTR) ptConsultCall;

                        pAsyncRequestInfo->pfnPostProcess =
                            LCompleteTransfer_PostProcess;

                        goto LCompleteTransfer_callSP;
                    }

                    SetCallConfList (ptConsultCall, NULL, FALSE);
                }

                SetCallConfList (ptCall, NULL, FALSE);
            }


            //
            // If here an error occured
            //

            ServerFree (pConfList);
            lRequestID = lResult;
            goto LCompleteTransfer_return;
        }
        else if (pParams->dwTransferMode != LINETRANSFERMODE_TRANSFER)
        {
            lRequestID = LINEERR_INVALTRANSFERMODE;
            goto LCompleteTransfer_return;
        }

LCompleteTransfer_callSP:

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP6(
            pfnTSPI_lineCompleteTransfer,
            "lineCompleteTransfer",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) ptConsultCallClient->ptCall->hdCall,
            (ULONG_PTR) ptConfCall,
            (ULONG_PTR) (ptConfCall ? &ptConfCall->hdCall : 0),
            (DWORD) pParams->dwTransferMode
            );

        if (ptConfCall)
        {
            SetDrvCallFlags(
                ptConfCall,
                DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                    DCF_DRVCALLVALID : 0)
                );
        }
    }

LCompleteTransfer_return:

    if (bDereferenceConsultCall)
    {
        DereferenceObject (ghHandleTable, pParams->hConsultCall, 1);
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "CompleteTransfer"
        );
}


void
WINAPI
LConditionalMediaDetection(
    PTCLIENT                                ptClient,
    PLINECONDITIONALMEDIADETECTION_PARAMS   pParams,
    DWORD                                   dwParamsBufferSize,
    LPBYTE                                  pDataBuf,
    LPDWORD                                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineConditionalMediaDetection;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStructParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwCallParamsOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINECONDITIONALMEDIADETECTION,        // provider func index
            &pfnTSPI_lineConditionalMediaDetection,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "ConditionalMediaDetection"            // func name
            )) == 0)
    {
        DWORD               dwAPIVersion, dwSPIVersion;
        LPLINECALLPARAMS    pCallParams;


        //
        // This func only gets called by RemoteSP.  Since RemoteSP
        // might be down-level, we need to compare API/SPI vers
        // to see if we need to munge call params (and it's good
        // to validate them anyway).
        //

        pCallParams = (LPLINECALLPARAMS)
            (pDataBuf + pParams->dwCallParamsOffset);

        try
        {
            dwAPIVersion = ptLineClient->dwAPIVersion;
            dwSPIVersion = ptLineClient->ptLine-> dwSPIVersion;

            if (ptLineClient->dwKey != TLINECLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LConditionalMediaDetection_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LConditionalMediaDetection_epilog;
        }

        if ((pParams->lResult = ValidateCallParams(
                pCallParams,
                &pCallParams,
                dwAPIVersion,
                dwSPIVersion,
                pParams->dwAsciiCallParamsCodePage

                )) == 0)
        {
            pParams->lResult = CallSP3(
                pfnTSPI_lineConditionalMediaDetection,
                "lineConditionalMediaDetection",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwMediaModes,
                (ULONG_PTR) pCallParams
                );

            if (pCallParams != (LPLINECALLPARAMS)
                    (pDataBuf + pParams->dwCallParamsOffset))
            {
                ServerFree (pCallParams);
            }
        }
    }

LConditionalMediaDetection_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "ConditionalMediaDetection"
        );
}


void
LCreateAgent_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PASYNCEVENTMSG          pNewAsyncEventMsg = (PASYNCEVENTMSG)
                                pAsyncRequestInfo->dwParam3;


    CopyMemory (pNewAsyncEventMsg, pAsyncEventMsg, sizeof (ASYNCEVENTMSG));

    *ppBuf = pNewAsyncEventMsg;

    if (pAsyncEventMsg->Param2 == 0)  // success
    {
        pNewAsyncEventMsg->TotalSize += ((sizeof(HAGENT) + 7) & 0xFFFFFFF8);


        //
        // param1 must not exceed 32 bits. use DWORD_CAST to enforce this at
        // least in runtime.
        //

        pNewAsyncEventMsg->Param3 = DWORD_CAST(pAsyncRequestInfo->dwParam1,__FILE__,__LINE__);
        pNewAsyncEventMsg->Param4 = sizeof(HAGENT);
    }
}


void
WINAPI
LCreateAgent(
    PTCLIENT                    ptClient,
    PLINECREATEAGENT_PARAMS     pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (((pParams->dwAgentIDOffset != TAPI_NO_DATA)  &&

            IsBadStringParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwAgentIDOffset
                ))  ||

        ((pParams->dwAgentPINOffset != TAPI_NO_DATA)  &&

            IsBadStringParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwAgentPINOffset
                )))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "CreateAgent"               // func name

            )) > 0)

    {
        LONG            lResult;
        DWORD           dwDeviceID;
        PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_CREATEAGENT,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LCreateAgent_epilog;
        }


        //
        // Save client's buffer pointer and post processing proc
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpAgent;
        pAsyncRequestInfo->dwParam2 = sizeof(HAGENT);
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;
            DWORD                   dwAgentIDSize = 0;
            DWORD                   dwAgentPINSize = 0;
            DWORD                   dwTotalSize = 0;
            DWORD                   dwOffset = 0;


            //
            // Figure out the total size of info we are passing to the proxy
            //

            if (TAPI_NO_DATA != pParams->dwAgentIDOffset)
            {
                dwAgentIDSize =
                    (lstrlenW ((PWSTR)(pDataBuf + pParams->dwAgentIDOffset))
                        + 1) * sizeof(WCHAR);

                dwTotalSize += dwAgentIDSize;
            }

            if (TAPI_NO_DATA != pParams->dwAgentPINOffset)
            {
                dwAgentPINSize =
                    (lstrlenW ((PWSTR)(pDataBuf + pParams->dwAgentPINOffset))
                        + 1) *  sizeof(WCHAR);

                dwTotalSize += dwAgentPINSize;
            }


            //
            // Fixed part of union part of structure
            //

            dwTotalSize += 4 * sizeof(DWORD) + sizeof(HAGENT);

            if (lResult = CreateProxyRequest(
                   pProxy,
                   LINEPROXYREQUEST_CREATEAGENT,
                   dwTotalSize,
                   pAsyncRequestInfo,
                   &pProxyRequestWrapper
                   ))
            {
                lRequestID = lResult;
                goto LCreateAgent_epilog;
            }


            //
            // Save the info in the proxy request
            //

            //
            // The offset is after the fixed size of the CreateAgent
            // struct which has 4 dwords and an hAgent.
            //
            // This will require no extra alloc on the client side
            // as the thing to be returned is the hAgent
            //

            dwOffset = 4 * sizeof(DWORD) + sizeof(HAGENT);


            //
            // Copy the id if exists
            //

            if (0 != dwAgentIDSize)
            {
                pProxyRequestWrapper->ProxyRequest.CreateAgent.
                    dwAgentIDSize = dwAgentIDSize;

                pProxyRequestWrapper->ProxyRequest.CreateAgent.
                    dwAgentIDOffset = dwOffset;

                wcscpy(
                    (PWSTR)((LPBYTE)(&(pProxyRequestWrapper->
                        ProxyRequest.CreateAgent)) + dwOffset),
                    (PWSTR)(pDataBuf + pParams->dwAgentIDOffset)
                    );

                dwOffset += dwAgentIDSize;
            }


            //
            // Copy the pin if exists
            //

            if (0 != dwAgentPINSize)
            {
                pProxyRequestWrapper->ProxyRequest.CreateAgent.
                    dwAgentPINSize = dwAgentPINSize;

                pProxyRequestWrapper->ProxyRequest.CreateAgent.
                    dwAgentPINOffset = dwOffset;

                wcscpy(
                    (PWSTR)((LPBYTE)(&(pProxyRequestWrapper->
                        ProxyRequest.CreateAgent)) + dwOffset),
                    (PWSTR)(pDataBuf + pParams->dwAgentPINOffset)
                    );
            }

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LCreateAgent_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }

        }

        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE  pBuf;

            pBuf = ServerAlloc (sizeof (ASYNCEVENTMSG) + sizeof (HAGENT));

            if (!pBuf)
            {
                lRequestID = LINEERR_NOMEM;
                goto LCreateAgent_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LCreateAgent_PostProcess;
            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pParams->lResult = CallSP5(
                pRemoteSP->apfn[SP_LINECREATEAGENT],
                "CreateAgent",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) ((pParams->dwAgentIDOffset) == TAPI_NO_DATA ?
                    NULL : (pDataBuf + pParams->dwAgentIDOffset)),
                (ULONG_PTR) ((pParams->dwAgentPINOffset) == TAPI_NO_DATA ?
                    NULL : (pDataBuf + pParams->dwAgentPINOffset)),
                (ULONG_PTR) (pBuf + sizeof (ASYNCEVENTMSG))
                );
        }


        //
        // No proxy and not remote
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LCreateAgent_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "CreateAgent"
        );
}


void
WINAPI
LCreateAgentSession(
    PTCLIENT                        ptClient,
    PLINECREATEAGENTSESSION_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (((pParams->dwAgentPINOffset != TAPI_NO_DATA)  &&

            IsBadStringParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwAgentPINOffset
                ))  ||

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwGroupIDSize,
            pParams->dwGroupIDOffset,
            sizeof(DWORD),
            "LCreateAgentSession",
            "pParams->GroupID"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "CreateAgentSession"        // func name

            )) > 0)

    {
        LONG            lResult;
        DWORD           dwDeviceID;
        PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                pParams->dwWorkingAddressID,
                LINEPROXYREQUEST_CREATEAGENTSESSION,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LCreateAgentSession_epilog;
        }


        //
        // save client's buffer pointer and
        // post processing proc
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpAgentSessionHandle;
        pAsyncRequestInfo->dwParam2 = sizeof(HAGENTSESSION);
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;
            DWORD                   dwAgentPINSize = 0;
            DWORD                   dwTotalSize = 0;
            DWORD                   dwOffset = 0;


            //
            // figure out the total size of information
            // we are passing to the proxy
            //

            if (TAPI_NO_DATA != pParams->dwAgentPINOffset)
            {
                dwAgentPINSize =
                    (lstrlenW( (PWSTR)(pDataBuf + pParams->dwAgentPINOffset))
                        + 1 ) * sizeof(WCHAR);
            }


            //
            // Add the union part of the CreateAgentSession request
            // which looks like:
            //
            // struct
            // {
            //    HAGENTSESSION           hAgentSession;
            //    DWORD                   dwAgentPINSize;
            //    DWORD                   dwAgentPINOffset;
            //    HAGENT                  hAgent;
            //    GUID                    GroupID;
            //    DWORD                   dwWorkingAddressID;
            //
            // } CreateAgentSession;
            //

            dwOffset = ( 3 * sizeof(DWORD) ) + sizeof(GUID) +
                           sizeof(HAGENTSESSION) + sizeof(HAGENT);

            dwTotalSize = dwOffset + dwAgentPINSize;


            if (lResult = CreateProxyRequest(
                   pProxy,
                   LINEPROXYREQUEST_CREATEAGENTSESSION,
                   dwTotalSize,
                   pAsyncRequestInfo,
                   &pProxyRequestWrapper
                   ))
            {
                lRequestID = lResult;
                goto LCreateAgentSession_epilog;
            }


            //
            // Save the info in the proxy request - copy the pin if exists
            //

            if ( 0 != dwAgentPINSize )
            {
                pProxyRequestWrapper->ProxyRequest.CreateAgentSession.
                         dwAgentPINSize = dwAgentPINSize;

                pProxyRequestWrapper->ProxyRequest.CreateAgentSession.
                        dwAgentPINOffset = dwOffset;

                wcscpy(
                    (PWSTR)((LPBYTE)(&(pProxyRequestWrapper->
                        ProxyRequest.CreateAgentSession)) + dwOffset),
                    (PWSTR)(pDataBuf + pParams->dwAgentPINOffset)
                    );
            }

            CopyMemory(
                &(pProxyRequestWrapper->
                    ProxyRequest.CreateAgentSession.GroupID),
                pDataBuf + pParams->dwGroupIDOffset,
                sizeof( GUID )
                );

            pProxyRequestWrapper->ProxyRequest.CreateAgentSession.
                dwWorkingAddressID = pParams->dwWorkingAddressID;

            pProxyRequestWrapper->ProxyRequest.CreateAgentSession.
                hAgent = pParams->hAgent;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LCreateAgentSession_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE  pBuf;


            pBuf = ServerAlloc(sizeof(ASYNCEVENTMSG) + sizeof (HAGENTSESSION));

            if ( NULL == pBuf )
            {
                lRequestID = LINEERR_NOMEM;
                goto LCreateAgentSession_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LCreateAgent_PostProcess;
            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pParams->lResult = CallSP7(
                pRemoteSP->apfn[SP_LINECREATEAGENTSESSION],
                "CreateAgentSession",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->hAgent,
                (ULONG_PTR) ((pParams->dwAgentPINOffset) == TAPI_NO_DATA ?
                    NULL : (pDataBuf + pParams->dwAgentPINOffset)),
                (DWORD) pParams->dwWorkingAddressID,
                (ULONG_PTR) (pDataBuf + pParams->dwGroupIDOffset),
                (ULONG_PTR) (pBuf + sizeof (ASYNCEVENTMSG))
                );
        }


        //
        // no proxy and not remote
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LCreateAgentSession_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "CreateAgentSession"
        );
}

void
WINAPI
LCreateMSPInstance(
    PTCLIENT                        ptClient,
    PLINECREATEMSPINSTANCE_PARAMS   pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineCreateMSPInstance;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINECREATEMSPINSTANCE,   // provider func index
            &pfnTSPI_lineCreateMSPInstance,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,
            &ptLineClient,
            "CreateMSPInstance"         // func name

            )) == 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineCreateMSPInstance,
            "lineCreateMSPInstance",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwAddressID,
            (DWORD) ptLineClient->hLine,
            (ULONG_PTR) &ptLineClient->hdMSPLine
            );

        if ( 0 == pParams->lResult )
        {
            *pdwNumBytesReturned = sizeof( LINECREATEMSPINSTANCE_PARAMS );
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "CreateMSPInstance"
        );
}


void
WINAPI
LDeallocateCall(
    PTCLIENT                    ptClient,
    PLINEDEALLOCATECALL_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                           // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "DeallocateCall"            // func name

            )) == 0)
    {
        //
        // Per nt bug #20546 we're now allowing the last owner to dealloc
        // a non-IDLE call.  Decided to do this based on distributed call
        // ownership issues.  dankn 02/13/96
        //

        DestroytCallClient (ptCallClient);
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "DeallocateCall"
        );
}


void
LDevSpecific_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PASYNCEVENTMSG  pNewAsyncEventMsg = (PASYNCEVENTMSG)
                        pAsyncRequestInfo->dwParam3;


    CopyMemory (pNewAsyncEventMsg, pAsyncEventMsg, sizeof (ASYNCEVENTMSG));

    *ppBuf = pNewAsyncEventMsg;

    if (pAsyncEventMsg->Param2 == 0)  // success
    {
        //
        // Make sure to keep the total size 64-bit aligned
        //

        pNewAsyncEventMsg->TotalSize +=
            (DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__) + 7) & 0xfffffff8;

        //
        // need to be at most 32-bit. use dword_cast to ensure this in 
        // runtime
        //

        pNewAsyncEventMsg->Param3 = DWORD_CAST(pAsyncRequestInfo->dwParam1,__FILE__,__LINE__); // lpParams
        pNewAsyncEventMsg->Param4 = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__); // dwSize
    }
}


void
WINAPI
LDevSpecific(
    PTCLIENT                ptClient,
    PLINEDEVSPECIFIC_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex, bDereferenceLineClient = FALSE;
    LONG                lRequestID;
    DWORD               dwWidgetType, hWidget;
    DWORD               dwPrivilege = LINECALLPRIVILEGE_MONITOR;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineDevSpecific;
    DWORD               objectToDereference;
    ULONG_PTR           hdWidget;
    PTCALLCLIENT        ptXxxClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwParamsSize,
            pParams->dwParamsOffset,
            sizeof(DWORD),
            "LDevSpecific",
            "pParams->Params"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if (pParams->hCall)
    {
        dwWidgetType = ANY_RT_HCALL;
        hWidget = (DWORD) pParams->hCall;
    }
    else
    {
        dwWidgetType = ANY_RT_HLINE;
        hWidget = (DWORD) pParams->hLine;
    }

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            dwWidgetType,               // widget type
            hWidget,                    // client widget handle
            (LPVOID) &hdWidget,         // provider widget handle
            (pParams->hCall ? (dwPrivilege) : 0),
                                        // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEDEVSPECIFIC,         // provider func index
            &pfnTSPI_lineDevSpecific,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptXxxClient,               // context
            "DevSpecific"               // func name

            )) > 0)
    {
        LPBYTE      pBuf;
        HDRVCALL    hdCall;
        HDRVLINE    hdLine;


        //
        // If an hCall was specified verify the hLine &
        // make sure the call is on the specified hLine
        //

        if (dwWidgetType == ANY_RT_HCALL)
        {
            LONG            lResult;
            PTLINECLIENT    ptLineClient;


            if (!(ptLineClient = ReferenceObject(
                    ghHandleTable,
                    pParams->hLine,
                    TLINECLIENT_KEY
                    )))
            {
                lRequestID = LINEERR_INVALLINEHANDLE;
                goto LDevSpecific_epilog;
            }

            bDereferenceLineClient = TRUE;

            if (ptLineClient->ptClient != ptClient)
            {
                lRequestID = LINEERR_INVALLINEHANDLE;
                goto LDevSpecific_epilog;
            }

            try
            {
                lResult = LINEERR_INVALLINEHANDLE;

                hdLine = ptLineClient->ptLine->hdLine;

                lResult = LINEERR_INVALCALLHANDLE;

                if (ptLineClient != ptXxxClient->ptLineClient)
                {
                    LOG((TL_ERROR,
                        "LDevSpecific: error, hCall=x%x not related " \
                            "to hLine=x%x",
                        pParams->hCall,
                        pParams->hLine
                        ));

                    lRequestID = LINEERR_INVALCALLHANDLE;
                    goto LDevSpecific_epilog;
                }
            }
            myexcept
            {
                lRequestID = lResult;
                goto LDevSpecific_epilog;
            }

            hdCall = (HDRVCALL) hdWidget;
        }
        else
        {
            hdLine = (HDRVLINE) hdWidget;
            hdCall = 0;
        }


        //
        // Alloc a shadow buf that the SP can use until it completes this
        // request.  Make sure there's enough extra space in the buf for
        // an ASYNCEVENTMSG header so we don't have to alloc yet another
        // buf in the post processing proc when preparing the completion
        // msg to send to the client, and that the msg is 64-bit aligned.
        //

        if (!(pBuf = ServerAlloc(
                ((pParams->dwParamsSize + 7) & 0xfffffff8) +
                    sizeof (ASYNCEVENTMSG)
                )))
        {
            lRequestID = LINEERR_NOMEM;
            goto LDevSpecific_epilog;
        }

        CopyMemory(
            pBuf + sizeof (ASYNCEVENTMSG),
            pDataBuf + pParams->dwParamsOffset,
            pParams->dwParamsSize
            );

        pAsyncRequestInfo->pfnPostProcess = LDevSpecific_PostProcess;
        pAsyncRequestInfo->dwParam1       = pParams->hpParams;
        pAsyncRequestInfo->dwParam2       = pParams->dwParamsSize;
        pAsyncRequestInfo->dwParam3       = (ULONG_PTR) pBuf;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP6(
            pfnTSPI_lineDevSpecific,
            "lineDevSpecific",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pParams->dwParamsSize ?
                pBuf + sizeof (ASYNCEVENTMSG) : NULL),
            (DWORD) pParams->dwParamsSize
            );
    }

LDevSpecific_epilog:

    if (bDereferenceLineClient)
    {
        DereferenceObject (ghHandleTable, pParams->hLine, 1);
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "DevSpecific"
        );
}


void
WINAPI
LDevSpecificEx(
    PTCLIENT                    ptClient,
    PLINEDEVSPECIFICEX_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    DWORD               dwWidgetType, hWidget;
    DWORD               dwPrivilege = LINECALLPRIVILEGE_MONITOR;
    DWORD               dwCallHubID = 0;
    HANDLE              hMutex;
    LPVOID              context;
    TSPIPROC            pfnTSPI_lineDevSpecificEx;
    DWORD               objectToDereference;
    ULONG_PTR           hdWidget;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwParamsSize,
            pParams->dwParamsOffset,
            sizeof(DWORD),
            "LDevSpecificEx",
            "pParams->Params"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    switch (pParams->dwSelect)
    {
    case LINECALLSELECT_DEVICEID:
    case LINECALLSELECT_ADDRESS:

        dwWidgetType = DEVICE_ID;
        hWidget = pParams->dwDeviceID;
        break;

    case LINECALLSELECT_CALLID:
    {
        PTCALLHUBCLIENT     ptCallHubClient;

        if (ptCallHubClient = IsValidCallHub(
                pParams->hCallHub,
                ptClient
                ))
        {
            try
            {
                dwCallHubID = ptCallHubClient->dwCallHubID;

                if (ptCallHubClient->dwKey != TCALLHUBCLIENT_KEY)
                {
                    pParams->lResult = LINEERR_INVALCALLHANDLE;
                    return;
                }
            }
            myexcept
            {
                pParams->lResult = LINEERR_INVALCALLSELECT;
                return;
            }
        }
    }
    // fall through
    case LINECALLSELECT_CALL:

        dwWidgetType = ANY_RT_HCALL;
        hWidget = (DWORD) pParams->hCall;
        break;

    case LINECALLSELECT_LINE:
    default:

        pParams->lResult = LINEERR_INVALCALLSELECT;
        return;
    }

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            dwWidgetType,               // widget type
            hWidget,                    // client widget handle
            (LPVOID) &hdWidget,         // provider widget handle
            (pParams->hCall ? (dwPrivilege) : 0),
                                        // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEDEVSPECIFICEX,       // provider func index
            &pfnTSPI_lineDevSpecificEx, // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &context,                   // context
            "DevSpecificEx"             // func name

            )) > 0)

    {
        DWORD       dwDeviceID = 0;
        LPBYTE      pBuf;
        HDRVCALL    hdCall = 0;


        switch (pParams->dwSelect)
        {
        case LINECALLSELECT_DEVICEID:
        case LINECALLSELECT_ADDRESS:

            dwDeviceID = (DWORD) hdWidget;
            break;

        case LINECALLSELECT_CALLID:
        case LINECALLSELECT_CALL:

            hdCall = (HDRVCALL) hdWidget;
            break;

        default:

            lRequestID = LINEERR_INVALCALLSELECT;
            goto LDevSpecificEx_epilog;
        }

        //
        // Alloc a shadow buf that the SP can use until it completes this
        // request.  Make sure there's enough extra space in the buf for
        // an ASYNCEVENTMSG header so we don't have to alloc yet another
        // buf in the post processing proc when preparing the completion
        // msg to send to the client, and that the msg is 64-bit aligned.
        //

        if (!(pBuf = ServerAlloc(
                ((pParams->dwParamsSize + 7) & 0xfffffff8) +
                    sizeof (ASYNCEVENTMSG)
                )))
        {
            lRequestID = LINEERR_NOMEM;
            goto LDevSpecificEx_epilog;
        }

        CopyMemory(
            pBuf + sizeof (ASYNCEVENTMSG),
            pDataBuf + pParams->dwParamsOffset,
            pParams->dwParamsSize
            );

        pAsyncRequestInfo->pfnPostProcess = LDevSpecific_PostProcess;
        pAsyncRequestInfo->dwParam1       = pParams->hpParams;
        pAsyncRequestInfo->dwParam2       = pParams->dwParamsSize;
        pAsyncRequestInfo->dwParam3       = (ULONG_PTR) pBuf;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP8(
            pfnTSPI_lineDevSpecificEx,
            "lineDevSpecificEx",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (DWORD) dwDeviceID,
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) hdCall,
            (DWORD) dwCallHubID,
            (DWORD) pParams->dwSelect,
            (ULONG_PTR) (pParams->dwParamsSize ?
                pBuf + sizeof (ASYNCEVENTMSG) : NULL),
            (DWORD) pParams->dwParamsSize
            );
    }

LDevSpecificEx_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "DevSpecific"
        );
}


void
WINAPI
LDevSpecificFeature(
    PTCLIENT                        ptClient,
    PLINEDEVSPECIFICFEATURE_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineDevSpecificFeature;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwParamsSize,
            pParams->dwParamsOffset,
            sizeof(DWORD),
            "LDevSpecificFeature",
            "pParams->Params"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEDEVSPECIFICFEATURE,  // provider func index
            &pfnTSPI_lineDevSpecificFeature,// provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "DevSpecificFeature"        // func name

            )) > 0)
    {
        LPBYTE pBuf;


        if (pParams->dwFeature > PHONEBUTTONFUNCTION_NONE  &&
            (pParams->dwFeature & 0x80000000) == 0)
        {
            lRequestID = LINEERR_INVALFEATURE;
            goto LDevSpecificFeature_epilog;
        }


        //
        // Alloc a shadow buf that the SP can use until it completes this
        // request.  Make sure there's enough extra space in the buf for
        // an ASYNCEVENTMSG header so we don't have to alloc yet another
        // buf in the post processing proc when preparing the completion
        // msg to send to the client, and that the msg is 64-bit aligned.
        //

        if (!(pBuf = ServerAlloc(
                ((pParams->dwParamsSize + 7) & 0xfffffff8) +
                    sizeof (ASYNCEVENTMSG)
                )))
        {
            lRequestID = LINEERR_NOMEM;
            goto LDevSpecificFeature_epilog;
        }

        CopyMemory(
            pBuf + sizeof (ASYNCEVENTMSG),
            pDataBuf + pParams->dwParamsOffset,
            pParams->dwParamsSize
            );

        pAsyncRequestInfo->pfnPostProcess = LDevSpecific_PostProcess;
        pAsyncRequestInfo->dwParam1       = pParams->hpParams;
        pAsyncRequestInfo->dwParam2       = pParams->dwParamsSize;
        pAsyncRequestInfo->dwParam3       = (ULONG_PTR) pBuf;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP5(
            pfnTSPI_lineDevSpecificFeature,
            "lineDevSpecificFeature",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwFeature,
            (ULONG_PTR) (pParams->dwParamsSize ?
                pBuf + sizeof (ASYNCEVENTMSG) : NULL),
            (DWORD) pParams->dwParamsSize
            );
    }

LDevSpecificFeature_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "DevSpecificFeature"
        );
}


void
WINAPI
LDial(
    PTCLIENT            ptClient,
    PLINEDIAL_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineDial;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDestAddressOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEDIAL,                // provider func index
            &pfnTSPI_lineDial,          // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Dial"                      // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineDial,
            "lineDial",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pDataBuf + pParams->dwDestAddressOffset),
            (DWORD) pParams->dwCountryCode
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Dial"
        );
}


void
WINAPI
LDrop(
    PTCLIENT            ptClient,
    PLINEDROP_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineDrop;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwUserUserInfoOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSize,
            pParams->dwUserUserInfoOffset,
            sizeof(DWORD),
            "LDrop",
            "pParams->UserUserInfo"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEDROP,                // provider func index
            &pfnTSPI_lineDrop,          // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Drop"                      // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineDrop,
            "lineDrop",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwUserUserInfoOffset),
            (DWORD) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? 0 :
                pParams->dwSize)
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Drop"
        );
}

LPBYTE 
NewToOldLineforwardlist( 
    LPLINEFORWARDLIST pFwdList3_1 
    );

LPLINEFORWARDLIST
OldToNewLineforwardlist( 
    LPLINEFORWARDLIST pFwdList3_0 
    );

void
WINAPI
LForward(
    PTCLIENT            ptClient,
    PLINEFORWARD_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineForward;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;
    DWORD               dwSizeofLFwdList = sizeof (LINEFORWARDLIST);
    DWORD               dwSizeofLFwd = sizeof (LINEFORWARD);


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEFORWARD,             // provider func index
            &pfnTSPI_lineForward,       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "Forward"                   // func name

            )) > 0)
    {
        LONG                lResult;
        DWORD               dwAPIVersion, dwSPIVersion;
        PTCALL              ptConsultCall;
        //HTAPICALL           htConsultCall;
        PTCALLCLIENT        ptConsultCallClient;
        LPLINECALLPARAMS    pCallParamsApp, pCallParamsSP;
        LPLINEFORWARDLIST   pFwdList = (LPLINEFORWARDLIST)
                                (pParams->dwForwardListOffset == TAPI_NO_DATA ?
                                NULL :pDataBuf + pParams->dwForwardListOffset),
                            pTmpFwdList = NULL,
                            pTmpFwdList1 = NULL;


        //
        // Verify size/offset/string params given our input buffer/size
        //

        if (((pParams->dwForwardListOffset != TAPI_NO_DATA)  &&

                IsBadStructParam(
                    dwParamsBufferSize,
                    pDataBuf,
                    pParams->dwForwardListOffset
                    ))  ||

            ((pParams->dwCallParamsOffset != TAPI_NO_DATA)  &&

                IsBadStructParam(
                    dwParamsBufferSize,
                    pDataBuf,
                    pParams->dwCallParamsOffset
                    )))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LForward_epilog;
        }


        //
        // Validate the params
        //

        if (GetLineVersions (ptLineClient, &dwAPIVersion, &dwSPIVersion) != 0)
        {
            lRequestID = LINEERR_INVALLINEHANDLE;
            goto LForward_epilog;
        }

        //
        // Check if the client app. is < 3.1 ===> uses old LINEFORWARD structure
        //
        if ( ptLineClient->ptLineApp->dwAPIVersion < TAPI_VERSION3_1 )
        {
            dwSizeofLFwdList -= 2 * sizeof (DWORD);
            dwSizeofLFwd -= 2 * sizeof (DWORD);
        }

        if (pFwdList)
        {
            DWORD           dwTotalSize  = pFwdList->dwTotalSize, dwFixedSize,
                            dwNumEntries, i, dwInvalidForwardModes;
            LPLINEFORWARD   pFwdEntry = pFwdList->ForwardList;


            if (dwTotalSize < dwSizeofLFwdList)
            {
                lRequestID = LINEERR_STRUCTURETOOSMALL;
                goto LForward_epilog;
            }


            //
            // Note: dwNumEntries == 0 is the same as pFwdList == NULL
            //

            dwNumEntries = pFwdList->dwNumEntries;

            if (dwNumEntries & 0xffff0000)
            {
                lRequestID = LINEERR_INVALPARAM;
                goto LForward_epilog;
            }

            dwFixedSize = dwSizeofLFwdList + dwSizeofLFwd *
                (dwNumEntries == 0 ? 0 : dwNumEntries - 1);

            if (dwFixedSize > dwTotalSize)
            {
                lRequestID = LINEERR_INVALPARAM;
                goto LForward_epilog;
            }

            dwInvalidForwardModes = (dwAPIVersion < TAPI_VERSION1_4 ?
                ~AllForwardModes1_0 : ~AllForwardModes1_4);

            for (i = 0; i < dwNumEntries; i++)
            {
                if (!IsOnlyOneBitSetInDWORD (pFwdEntry->dwForwardMode) ||
                    pFwdEntry->dwForwardMode & dwInvalidForwardModes)
                {
                    LOG((TL_ERROR,
                        "LFoward: bad dwForwardMode, x%x",
                        pFwdEntry->dwForwardMode
                        ));

                    lRequestID = LINEERR_INVALPARAM;
                    goto LForward_epilog;
                }

                if (ISBADSIZEOFFSET(
                        dwTotalSize,
                        dwFixedSize,
                        pFwdEntry->dwCallerAddressSize,
                        pFwdEntry->dwCallerAddressOffset,
                        0,
                        "LFoward",
                        "CallerAddress"
                        ) ||

                    ISBADSIZEOFFSET(
                        dwTotalSize,
                        dwFixedSize,
                        pFwdEntry->dwDestAddressSize,
                        pFwdEntry->dwDestAddressOffset,
                        0,
                        "LFoward",
                        "CallerAddress"
                        ))
                {
                    lRequestID = LINEERR_INVALPARAM;
                    goto LForward_epilog;
                }

                // don't bother validating country code right now

                pFwdEntry = (LPLINEFORWARD) ((LPBYTE)pFwdEntry + dwSizeofLFwd);
            }


            //
            // See if we need to convert an ascii fwd list to unicode
            //

            if (pParams->dwAsciiCallParamsCodePage != 0xffffffff  &&
                dwNumEntries != 0)
            {
                DWORD dwXxxOffset;


                //
                // Alloc a temporary buffer for storing the converted
                // data (sizeof(WCHAR) * dwTotalSize to insure buffer
                // is large enough for all ascii->unicode conversions)
                //

                if (!(pTmpFwdList = ServerAlloc (sizeof(WCHAR) * dwTotalSize)))
                {
                    lRequestID = LINEERR_NOMEM;
                    goto LForward_epilog;
                }

                dwXxxOffset = dwSizeofLFwdList +
                    (dwNumEntries - 1) * dwSizeofLFwd;

                pFwdEntry = pTmpFwdList->ForwardList;

                CopyMemory (pTmpFwdList, pFwdList, dwXxxOffset);

                pTmpFwdList->dwTotalSize *= sizeof (WCHAR);

                for (i = 0; i < dwNumEntries; i++)
                {
                    if (pFwdEntry->dwCallerAddressSize)
                    {
                        MultiByteToWideChar(
                            pParams->dwAsciiCallParamsCodePage,
                            MB_PRECOMPOSED,
                            (LPCSTR) (((LPBYTE) pFwdList) +
                                pFwdEntry->dwCallerAddressOffset),
                            pFwdEntry->dwCallerAddressSize,
                            (LPWSTR) (((LPBYTE) pTmpFwdList) + dwXxxOffset),
                            pFwdEntry->dwCallerAddressSize * sizeof (WCHAR)
                            );

                        pFwdEntry->dwCallerAddressOffset = dwXxxOffset;
                        dwXxxOffset += (pFwdEntry->dwCallerAddressSize *=
                            sizeof (WCHAR));
                    }

                    if (pFwdEntry->dwDestAddressSize)
                    {
                        MultiByteToWideChar(
                            pParams->dwAsciiCallParamsCodePage,
                            MB_PRECOMPOSED,
                            (LPCSTR) (((LPBYTE) pFwdList) +
                                pFwdEntry->dwDestAddressOffset),
                            pFwdEntry->dwDestAddressSize,
                            (LPWSTR) (((LPBYTE) pTmpFwdList) + dwXxxOffset),
                            pFwdEntry->dwDestAddressSize * sizeof (WCHAR)
                            );

                        pFwdEntry->dwDestAddressOffset = dwXxxOffset;
                        dwXxxOffset += (pFwdEntry->dwDestAddressSize *=
                            sizeof (WCHAR));
                    }

                    pFwdEntry = (LPLINEFORWARD) ((LPBYTE)pFwdEntry + dwSizeofLFwd);
                
                }

                pFwdList = pTmpFwdList;
            }
        }

        //
        // Check if we need LINEFORWARDLIST conversion new to old
        //    if the TSP is < 3.1 ===> expects old LINEFORWARDLIST structure
        //    and the App is >= 3.1 ===> sent over new LINEFORWARDLIST structure
        //
        if ( pFwdList && 
             dwSPIVersion < TAPI_VERSION3_1 &&
             ptLineClient->ptLineApp->dwAPIVersion >= TAPI_VERSION3_1 )
        {
            if (!(pTmpFwdList1 = ( LPLINEFORWARDLIST ) NewToOldLineforwardlist (pFwdList)))
            {
                lRequestID = LINEERR_NOMEM;
                goto LForward_freeFwdList;
            }
            pFwdList = pTmpFwdList1;
        }

        //
        // Check if we need LINEFORWARDLIST conversion old to new
        //    if the TSP is >= 3.1 ===> expects new LINEFORWARDLIST structure
        //    and the App is < 3.1 ===> sent over old LINEFORWARDLIST structure
        //
        if ( pFwdList && 
             dwSPIVersion >= TAPI_VERSION3_1 &&
             ptLineClient->ptLineApp->dwAPIVersion < TAPI_VERSION3_1 )
        {
            if (!(pTmpFwdList1 = OldToNewLineforwardlist (pFwdList)))
            {
                lRequestID = LINEERR_NOMEM;
                goto LForward_freeFwdList;
            }
            pFwdList = pTmpFwdList1;
        }

        pCallParamsApp = (LPLINECALLPARAMS)
            (pParams->dwCallParamsOffset == TAPI_NO_DATA ? NULL :
            pDataBuf + pParams->dwCallParamsOffset);

        if (pCallParamsApp)
        {
            if ((lResult = ValidateCallParams(
                    pCallParamsApp,
                    &pCallParamsSP,
                    dwAPIVersion,
                    dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage

                    )) != 0)
            {
                lRequestID = lResult;
                goto LForward_freeFwdList1;
            }
        }
        else
        {
            pCallParamsSP = (LPLINECALLPARAMS) NULL;
        }

        if (CreatetCallAndClient(
                ptLineClient,
                &ptConsultCall,
                &ptConsultCallClient,
                pCallParamsSP,
                NULL

                ) != 0)
        {
            lRequestID = LINEERR_NOMEM;
            goto LForward_freeCallParams;
        }

        //htConsultCall = ptConsultCall->htCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptConsultCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptConsultCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpConsultCall;
        pAsyncRequestInfo->dwParam3 = 1; // special case for post-process proc
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptConsultCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP9(
            pfnTSPI_lineForward,
            "lineForward",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->bAllAddresses,
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) pFwdList,
            (DWORD) pParams->dwNumRingsNoAnswer,
            (ULONG_PTR) ptConsultCall,
            (ULONG_PTR) &ptConsultCall->hdCall,
            (ULONG_PTR) pCallParamsSP
            );

        SetDrvCallFlags(
            ptConsultCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

LForward_freeCallParams:

        if (pCallParamsSP != pCallParamsApp)
        {
            ServerFree (pCallParamsSP);
        }

LForward_freeFwdList1:

        if (pTmpFwdList1)
        {
            ServerFree (pTmpFwdList1);
        }

LForward_freeFwdList:

        if (pTmpFwdList)
        {
            ServerFree (pTmpFwdList);
        }
    }

LForward_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Forward"
        );
}


void
WINAPI
LGatherDigits(
    PTCLIENT                    ptClient,
    PLINEGATHERDIGITS_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineGatherDigits;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwTerminationDigitsOffset != TAPI_NO_DATA)  &&

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwTerminationDigitsOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGATHERDIGITS,        // provider func index
            &pfnTSPI_lineGatherDigits,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "GatherDigits"              // func name

            )) == 0)
    {
        DWORD               dwDigitModes = pParams->dwDigitModes;
        LPWSTR              lpsDigits;
        PASYNCREQUESTINFO   pAsyncRequestInfo;


        #define AllGatherDigitsModes (LINEDIGITMODE_PULSE | LINEDIGITMODE_DTMF)

        if (!(dwDigitModes & AllGatherDigitsModes) ||
            (dwDigitModes & ~AllGatherDigitsModes))
        {
            pParams->lResult = LINEERR_INVALDIGITMODE;
            goto LGatherDigits_epilog;
        }

        if (pParams->hpsDigits)
        {
            //
            // The client passed us a non-null digits buffer so we'll
            // alloc an async request info buf with extra space at the
            // end for the temporary digits buf for use by the sp
            // (faster than two two allocs & two frees for separate
            // async request & digits bufs).  Use the pointer as the
            // dwEndToEndID we pass to the sp.
            //

            PTLINECLIENT    ptLineClient;


            if (pParams->dwNumDigits == 0)
            {
                pParams->lResult = LINEERR_INVALPARAM;
                goto LGatherDigits_epilog;
            }

            if (!(pAsyncRequestInfo = ServerAlloc(
                    sizeof (ASYNCREQUESTINFO) +
                        (pParams->dwNumDigits * sizeof (WCHAR))
                    )))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGatherDigits_epilog;
            }

            lpsDigits = (LPWSTR) (pAsyncRequestInfo + 1);

            ptLineClient = ptCallClient->ptLineClient;

            pAsyncRequestInfo->dwKey    = TASYNC_KEY;
            pAsyncRequestInfo->ptClient = ptClient;

            try
            {
                pAsyncRequestInfo->InitContext =
                    ptLineClient->ptLineApp->InitContext;
                pAsyncRequestInfo->OpenContext = ptLineClient->OpenContext;
            }
            myexcept
            {
                ServerFree (pAsyncRequestInfo);
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LGatherDigits_epilog;
            }

            pAsyncRequestInfo->dwParam1 = sizeof (ASYNCREQUESTINFO);
            pAsyncRequestInfo->dwParam2 = DWORD_CAST(pParams->hpsDigits,__FILE__,__LINE__);
            pAsyncRequestInfo->dwParam3 = pParams->dwNumDigits;
            pAsyncRequestInfo->dwParam4 = pParams->hCall;
            pAsyncRequestInfo->dwParam5 = (ULONG_PTR)pParams->dwEndToEndID;

            pAsyncRequestInfo->hfnClientPostProcessProc =
                pParams->hfnPostProcessProc;

            pAsyncRequestInfo->dwLocalRequestID = (DWORD) NewObject(
                ghHandleTable,
                pAsyncRequestInfo,
                NULL
                );
        }
        else
        {
            //
            // Client wants to cancel gathering, so just set these two to null
            //

            lpsDigits = NULL;
            pAsyncRequestInfo = NULL;
        }

        if ((pParams->lResult = CallSP8(
                pfnTSPI_lineGatherDigits,
                "lineGatherDigits",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdCall,
                (DWORD) (pAsyncRequestInfo ?
                    pAsyncRequestInfo->dwLocalRequestID : 0),
                (DWORD) dwDigitModes,
                (ULONG_PTR) lpsDigits,
                (DWORD) pParams->dwNumDigits,
                (ULONG_PTR) (pParams->dwTerminationDigitsOffset ==TAPI_NO_DATA?
                    0 : (pDataBuf + pParams->dwTerminationDigitsOffset)),
                (DWORD) pParams->dwFirstDigitTimeout,
                (DWORD) pParams->dwInterDigitTimeout

                )) != 0)
        {
            if (pAsyncRequestInfo)
            {
                DereferenceObject(
                    ghHandleTable,
                    pAsyncRequestInfo->dwLocalRequestID,
                    1
                    );
            }
        }
    }

LGatherDigits_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GatherDigits"
        );
}


void
WINAPI
LGenerateDigits(
    PTCLIENT                    ptClient,
    PLINEGENERATEDIGITS_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineGenerateDigits;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwDigitsOffset != TAPI_NO_DATA)  &&

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDigitsOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGENERATEDIGITS,      // provider func index
            &pfnTSPI_lineGenerateDigits,// provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "GenerateDigits"            // func name

            )) == 0)
    {
        DWORD   dwDigitMode = pParams->dwDigitMode, *pInstData, dwEndToEndID = 0;


        if (dwDigitMode != LINEDIGITMODE_PULSE  &&
            dwDigitMode != LINEDIGITMODE_DTMF)
        {
            pParams->lResult = LINEERR_INVALDIGITMODE;
            goto LGenerateDigits_epilog;
        }

        if (pParams->dwDigitsOffset != TAPI_NO_DATA)
        {
            if (!(pInstData = ServerAlloc (3 * sizeof (DWORD))))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGenerateDigits_epilog;
            }

            pInstData[0] = TASYNC_KEY;
            pInstData[1] = (DWORD) pParams->hCall;
            pInstData[2] = pParams->dwEndToEndID;

            dwEndToEndID = (DWORD) NewObject (ghHandleTable, pInstData, 0);
        }

        pParams->lResult = CallSP5(
            pfnTSPI_lineGenerateDigits,
            "lineGenerateDigits",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdCall,
            (DWORD) dwEndToEndID,
            (DWORD) dwDigitMode,
            (ULONG_PTR) (pParams->dwDigitsOffset == TAPI_NO_DATA ?
                NULL : pDataBuf + pParams->dwDigitsOffset),
            (DWORD) pParams->dwDuration
            );

        if (pParams->lResult != 0  &&  dwEndToEndID != 0)
        {
            DereferenceObject (ghHandleTable, dwEndToEndID, 1);
        }
    }

LGenerateDigits_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GenerateDigits"
        );
}


void
WINAPI
LGenerateTone(
    PTCLIENT                    ptClient,
    PLINEGENERATETONE_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineGenerateTone;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwToneMode == LINETONEMODE_CUSTOM)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwNumTones * sizeof (LINEGENERATETONE),
            pParams->dwTonesOffset,
            sizeof(DWORD),
            "LGenerateTone",
            "pParams->Tones"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGENERATETONE,        // provider func index
            &pfnTSPI_lineGenerateTone,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "GenerateTone"              // func name

            )) == 0)
    {
        DWORD   dwToneMode = pParams->dwToneMode, *pInstData, dwEndToEndID;


        if (dwToneMode != 0)
        {
            if (!(dwToneMode & AllToneModes) ||
                !IsOnlyOneBitSetInDWORD (dwToneMode))
            {
                pParams->lResult = LINEERR_INVALTONEMODE;
                goto LGenerateTone_epilog;
            }
            else if (!(pInstData = ServerAlloc (3 * sizeof (DWORD))))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGenerateTone_epilog;
            }

            pInstData[0] = TASYNC_KEY;
            pInstData[1] = (DWORD) pParams->hCall;
            pInstData[2] = pParams->dwEndToEndID;

            dwEndToEndID = (DWORD) NewObject (ghHandleTable, pInstData, 0);
        }
        else
        {
            dwEndToEndID = 0;
        }

        pParams->lResult = CallSP6(
            pfnTSPI_lineGenerateTone,
            "lineGenerateTone",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdCall,
            (DWORD) dwEndToEndID,
            (DWORD) pParams->dwToneMode,
            (DWORD) pParams->dwDuration,
            (DWORD) pParams->dwNumTones,
            (ULONG_PTR) (pDataBuf + pParams->dwTonesOffset)
            );

        if (pParams->lResult != 0  &&  dwEndToEndID != 0)
        {
            DereferenceObject (ghHandleTable, dwEndToEndID, 1);
        }
    }

LGenerateTone_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GenerateTone"
        );
}


void
WINAPI
LGetAddressCaps(
    PTCLIENT                    ptClient,
    PLINEGETADDRESSCAPS_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineGetAddressCaps;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwAddressCapsTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETADDRESSCAPS,      // provider func index
            &pfnTSPI_lineGetAddressCaps,// provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetAddressCaps"            // func name

            )) == 0)
    {
        DWORD               dwAPIVersion, dwSPIVersion, dwTotalSize,
                            dwFixedSizeClient, dwFixedSizeSP;
        LPLINEADDRESSCAPS   pAddrCaps = (LPLINEADDRESSCAPS) pDataBuf,
                            pAddrCaps2 = (LPLINEADDRESSCAPS) NULL;


        //
        // Verify API & SPI version compatibility
        //

        dwAPIVersion = pParams->dwAPIVersion;

        dwSPIVersion = pLookupEntry->dwSPIVersion;

        if (!IsAPIVersionInRange (dwAPIVersion, dwSPIVersion))
        {
            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto LGetAddressCaps_epilog;
        }


        //
        // Verify Ext version compatibility
        //

        if (!IsValidLineExtVersion (dwDeviceID, pParams->dwExtVersion))
        {
            pParams->lResult = LINEERR_INCOMPATIBLEEXTVERSION;
            goto LGetAddressCaps_epilog;
        }


        //
        // Determine the fixed siize of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwAddressCapsTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:

            dwFixedSizeClient = 176;    // 44 * sizeof (DWORD);
            break;

        case TAPI_VERSION1_4:

            dwFixedSizeClient = 180;    // 45 * sizeof (DWORD);
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINEADDRESSCAPS);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetAddressCaps_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:

            dwFixedSizeSP = 176;        // 44 * sizeof (DWORD);
            break;

        case TAPI_VERSION1_4:

            dwFixedSizeSP = 180;        // 45 * sizeof (DWORD);
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINEADDRESSCAPS);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pAddrCaps2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetAddressCaps_epilog;
            }

            pAddrCaps   = pAddrCaps2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pAddrCaps,
            dwTotalSize,
            dwFixedSizeSP,
            (pAddrCaps2 == NULL ? TRUE : FALSE)
            );

        if ((pParams->lResult = CallSP5(
                pfnTSPI_lineGetAddressCaps,
                "lineGetAddressCaps",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (DWORD) pParams->dwAddressID,
                (DWORD) dwSPIVersion,
                (DWORD) pParams->dwExtVersion,
                (ULONG_PTR) pAddrCaps

                )) == 0)
        {
#if DBG
            //
            // Verify the info returned by the provider
            //

#endif


            //
            // Add the fields we're responsible for
            //

            pAddrCaps->dwCallInfoStates |= LINECALLINFOSTATE_NUMOWNERINCR |
                                           LINECALLINFOSTATE_NUMOWNERDECR |
                                           LINECALLINFOSTATE_NUMMONITORS;

            pAddrCaps->dwCallStates |= LINECALLSTATE_UNKNOWN;

            pAddrCaps->dwLineDeviceID = pParams->dwDeviceID;


            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //

            if ((dwAPIVersion == TAPI_VERSION1_0) &&
                (pAddrCaps->dwForwardModes &
                    (LINEFORWARDMODE_UNKNOWN | LINEFORWARDMODE_UNAVAIL)))
            {
                pAddrCaps->dwForwardModes &=
                            ~(LINEFORWARDMODE_UNKNOWN |
                            LINEFORWARDMODE_UNAVAIL);

                pAddrCaps->dwForwardModes |= LINEFORWARDMODE_UNCOND;
            }

            if ((dwAPIVersion == TAPI_VERSION2_0)  &&
                (pAddrCaps->dwAvailableMediaModes & LINEMEDIAMODE_VIDEO))
            {
                pAddrCaps->dwAvailableMediaModes = LINEMEDIAMODE_UNKNOWN |
                    (pAddrCaps->dwAvailableMediaModes & ~LINEMEDIAMODE_VIDEO);
            }


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

            if (pAddrCaps == pAddrCaps2)
            {
                pAddrCaps = (LPLINEADDRESSCAPS) pDataBuf;

                CopyMemory (pAddrCaps, pAddrCaps2, dwFixedSizeClient);

                ServerFree (pAddrCaps2);

                pAddrCaps->dwTotalSize = pParams->dwAddressCapsTotalSize;
                pAddrCaps->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            pParams->dwAddressCapsOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pAddrCaps->dwUsedSize;
         }
    }

LGetAddressCaps_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetAddressCaps"
        );
}


void
WINAPI
LGetAddressID(
    PTCLIENT                    ptClient,
    PLINEGETADDRESSID_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineGetAddressID;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSize,
            pParams->dwAddressOffset,
            sizeof(DWORD),
            "LGetAddressID",
            "pParams->Address"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETADDRESSID,        // provider func index
            &pfnTSPI_lineGetAddressID,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetAddressID"              // func name

            )) == 0)
    {
        if (pParams->dwAddressMode == LINEADDRESSMODE_DIALABLEADDR)
        {
            pParams->lResult = CallSP5(
                pfnTSPI_lineGetAddressID,
                "lineGetAddressID",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) &pParams->dwAddressID,
                (DWORD) pParams->dwAddressMode,
                (ULONG_PTR) (pDataBuf + pParams->dwAddressOffset),
                (DWORD) pParams->dwSize
                );

            *pdwNumBytesReturned = sizeof (LINEGETADDRESSID_PARAMS);
        }
        else
        {
            pParams->lResult = LINEERR_INVALADDRESSMODE;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetAddressID"
        );
}


void
WINAPI
LGetAddressStatus(
    PTCLIENT                        ptClient,
    PLINEGETADDRESSSTATUS_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineGetAddressStatus;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwAddressStatusTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETADDRESSSTATUS,    // provider func index
            &pfnTSPI_lineGetAddressStatus,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetAddressStatus"          // func name

            )) == 0)
    {
        DWORD               dwAPIVersion, dwSPIVersion, dwTotalSize,
                            dwFixedSizeClient, dwFixedSizeSP;
        LPLINEADDRESSSTATUS pAddrStatus = (LPLINEADDRESSSTATUS) pDataBuf,
                            pAddrStatus2 = (LPLINEADDRESSSTATUS) NULL;


        //
        // Safely retrieve the API & SPI versions
        //

        if (GetLineVersions (ptLineClient, &dwAPIVersion, &dwSPIVersion) != 0)
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LGetAddressStatus_epilog;
        }


        //
        // Determine the fixed size of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwAddressStatusTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeClient = 64;     // 16 * sizeof (DWORD)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINEADDRESSSTATUS);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetAddressStatus_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeSP = 64;         // 16 * sizeof (DWORD)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINEADDRESSSTATUS);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pAddrStatus2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetAddressStatus_epilog;
            }

            pAddrStatus = pAddrStatus2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pAddrStatus,
            dwTotalSize,
            dwFixedSizeSP,
            (pAddrStatus2 == NULL ? TRUE : FALSE)
            );

        if ((pParams->lResult = CallSP3(
                pfnTSPI_lineGetAddressStatus,
                "lineGetAddressStatus",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwAddressID,
                (ULONG_PTR) pAddrStatus

                )) == 0)
        {
            DWORD   dwForwardNumEntries;


#if DBG
            //
            // Verify the info returned by the provider
            //

#endif


            //
            // Add the fields we're responsible for
            //


            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //

            if ((dwAPIVersion == TAPI_VERSION1_0) &&
                (dwForwardNumEntries = pAddrStatus->dwForwardNumEntries))
            {
                DWORD           i;
                LPLINEFORWARD   pLineForward;


                pLineForward = (LPLINEFORWARD) (((LPBYTE) pAddrStatus) +
                    pAddrStatus->dwForwardOffset);

                for (i = 0; i < dwForwardNumEntries; i++, pLineForward++)
                {
                    if (pLineForward->dwForwardMode &
                        (LINEFORWARDMODE_UNKNOWN | LINEFORWARDMODE_UNAVAIL))
                    {
                        pLineForward->dwForwardMode &=
                            ~(LINEFORWARDMODE_UNKNOWN |
                            LINEFORWARDMODE_UNAVAIL);

                        pLineForward->dwForwardMode |= LINEFORWARDMODE_UNCOND;
                    }
                }
            }


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

            if (pAddrStatus == pAddrStatus2)
            {
                pAddrStatus = (LPLINEADDRESSSTATUS) pDataBuf;

                CopyMemory (pAddrStatus, pAddrStatus2, dwFixedSizeClient);

                ServerFree (pAddrStatus2);

                pAddrStatus->dwTotalSize =
                    pParams->dwAddressStatusTotalSize;
                pAddrStatus->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            pParams->dwAddressStatusOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pAddrStatus->dwUsedSize;
        }
    }

LGetAddressStatus_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetAddressStatus"
        );
}


void
LGetAgentXxx_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PASYNCEVENTMSG          pNewAsyncEventMsg = (PASYNCEVENTMSG)
                                pAsyncRequestInfo->dwParam3;


    CopyMemory (pNewAsyncEventMsg, pAsyncEventMsg, sizeof (ASYNCEVENTMSG));

    *ppBuf = pNewAsyncEventMsg;

    if (pAsyncEventMsg->Param2 == 0)  // success
    {
        LPLINEAGENTACTIVITYLIST pActivityList = (LPLINEAGENTACTIVITYLIST)
                                    (pNewAsyncEventMsg + 1);


        pNewAsyncEventMsg->TotalSize +=
            ((pActivityList->dwUsedSize + 7) & 0xFFFFFFF8);


        //
        // param 1 must not exceed 32-bits. use DWORD_CAST to insure this in 
        // runtime
        //

        pNewAsyncEventMsg->Param3 = DWORD_CAST(pAsyncRequestInfo->dwParam1,__FILE__,__LINE__);
        pNewAsyncEventMsg->Param4 = DWORD_CAST(pActivityList->dwUsedSize,__FILE__,__LINE__);
    }
}


#if DBG
void
PASCAL
LGetAgentXxx(
    PTCLIENT                            ptClient,
    PLINEGETAGENTACTIVITYLIST_PARAMS    pParams,
    DWORD                               dwParamsBufferSize,
    DWORD                               dwRequestType,
    DWORD                               dwSPIOrdinal,
    DWORD                               dwFixedStructSize,
    char                               *pszFuncName
    )
#else
void
PASCAL
LGetAgentXxx(
    PTCLIENT                            ptClient,
    PLINEGETAGENTACTIVITYLIST_PARAMS    pParams,
    DWORD                               dwParamsBufferSize,
    DWORD                               dwRequestType,
    DWORD                               dwSPIOrdinal,
    DWORD                               dwFixedStructSize
    )
#endif
{
    //
    // Since LGetAgentActivityList, LGetAgentGroupList, and LGetAgentStatus
    // all do the same thing (& the params are more or less identical) we
    // can safely condense all the functionality into this one procedure
    //

    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwActivityListTotalSize > 0x40000)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            pszFuncName                 // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID,
                        dwTotalSize = pParams->dwActivityListTotalSize;
        PTLINECLIENT    pProxy;


        if (dwTotalSize < dwFixedStructSize)
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LGetAgentXxx_epilog;
        }

        if ((lResult = FindProxy(
                ptLineClient,
                pParams->dwAddressID,
                dwRequestType,
                &pProxy,
                &dwDeviceID,
                0               // API ver wasn't checked in 2.0
                )))
        {
            lRequestID = lResult;
            goto LGetAgentXxx_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpAgentActivityList;
        pAsyncRequestInfo->dwParam2 = dwTotalSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    dwRequestType,
                    2 * sizeof (DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentXxx_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.GetAgentActivityList.
                dwAddressID = pParams->dwAddressID;
            pProxyRequestWrapper->ProxyRequest.GetAgentActivityList.
                ActivityList.dwTotalSize = dwTotalSize;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentXxx_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE                  pBuf;
            LPLINEAGENTACTIVITYLIST pActivityList;


            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwTotalSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LGetAgentXxx_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LGetAgentXxx_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pActivityList = (LPLINEAGENTACTIVITYLIST)
                (pBuf + sizeof (ASYNCEVENTMSG));

            pActivityList->dwTotalSize = dwTotalSize;

            pParams->lResult = CallSP4(
                pRemoteSP->apfn[dwSPIOrdinal],
                pszFuncName,
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwAddressID,
                (ULONG_PTR) pActivityList
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LGetAgentXxx_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        pszFuncName
        );
}

#if DBG
void
PASCAL
LGetAgentWithoutAddressIDXxx(
    PTCLIENT                            ptClient,
    PLINEGETAGENTINFO_PARAMS            pParams,
    DWORD                               dwParamsBufferSize,
    DWORD                               dwRequestType,
    DWORD                               dwSPIOrdinal,
    DWORD                               dwFixedStructSize,
    char                               *pszFuncName
    )
#else
void
PASCAL
LGetAgentWithoutAddressIDXxx(
    PTCLIENT                            ptClient,
    PLINEGETAGENTINFO_PARAMS            pParams,
    DWORD                               dwParamsBufferSize,
    DWORD                               dwRequestType,
    DWORD                               dwSPIOrdinal,
    DWORD                               dwFixedStructSize
    )
#endif
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwAgentInfoTotalSize > 0x40000)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            pszFuncName                 // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID,
                        dwTotalSize = pParams->dwAgentInfoTotalSize;
        PTLINECLIENT    pProxy;


        if (dwTotalSize < dwFixedStructSize)
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LGetAgentWithoutAddressIDXxx_epilog;
        }

        if ((lResult = FindProxy(
                ptLineClient,
                0,
                dwRequestType,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LGetAgentWithoutAddressIDXxx_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpAgentInfo;
        pAsyncRequestInfo->dwParam2 = dwTotalSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    dwRequestType,
                    2 * sizeof (DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentWithoutAddressIDXxx_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.GetAgentInfo.
                hAgent = pParams->hAgent;
            pProxyRequestWrapper->ProxyRequest.GetAgentInfo.
                AgentInfo.dwTotalSize = dwTotalSize;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentWithoutAddressIDXxx_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE                  pBuf;
            LPLINEAGENTINFO         pAgentInfo;


            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwTotalSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LGetAgentWithoutAddressIDXxx_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LGetAgentXxx_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pAgentInfo = (LPLINEAGENTINFO)
                (pBuf + sizeof (ASYNCEVENTMSG));

            pAgentInfo->dwTotalSize = dwTotalSize;

            pParams->lResult = CallSP4(
                pRemoteSP->apfn[dwSPIOrdinal],
                pszFuncName,
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->hAgent,
                (ULONG_PTR) pAgentInfo
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LGetAgentWithoutAddressIDXxx_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        pszFuncName
        );
}



void
WINAPI
LGetAgentActivityList(
    PTCLIENT                            ptClient,
    PLINEGETAGENTACTIVITYLIST_PARAMS    pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    LGetAgentXxx(
        ptClient,
        pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTACTIVITYLIST,
        SP_LINEGETAGENTACTIVITYLIST,
        sizeof (LINEAGENTACTIVITYLIST)
#if DBG
        ,
        "GetAgentActivityList"
#endif
        );
}


void
WINAPI
LGetAgentCaps(
    PTCLIENT                    ptClient,
    PLINEGETAGENTCAPS_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex, bProxy = FALSE;
    LONG                lRequestID;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwAgentCapsTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetAgentCaps"              // func name

            )) > 0)
    {
        DWORD               dwTotalSize = pParams->dwAgentCapsTotalSize;
        PTLINE              ptLine;
        PTLINECLIENT        pProxy = NULL;
        DWORD               dwFixedSize = 0;


        switch (pParams->dwAppAPIVersion)
        {
        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:

            dwFixedSize = 14 * sizeof(DWORD);
            break;

        case TAPI_VERSION2_2:
        case TAPI_VERSION3_0:
        case TAPI_VERSION3_1:

            dwFixedSize = sizeof(LINEAGENTCAPS);
            break;

        default:

            //
            // Any other version is too low or invalid
            //

            lRequestID = LINEERR_INCOMPATIBLEAPIVERSION;
            goto LGetAgentCaps_epilog;
        }

        if (dwTotalSize < dwFixedSize)
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LGetAgentCaps_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpAgentCaps;
        pAsyncRequestInfo->dwParam2 = dwTotalSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        try
        {
            if (!(ptLine = pLookupEntry->ptLine))
            {
                // If ptLine is NULL, the line has not been
                // open by any app on the local machine; this means
                // that there's no proxy on the local machine; however,
                // if the line is a remote one (i.e., exposed by remotesp),
                // there could be a proxy on another machine. So get out
                // of this try block and continue to check for remote line
                // (pProxy is already initialized to NULL).
                leave;
            }
            pProxy = ptLine->apProxys[LINEPROXYREQUEST_GETAGENTCAPS];
            if (pParams->dwAddressID >= ptLine->dwNumAddresses)
            {
            lRequestID = LINEERR_INVALADDRESSID;
            goto LGetAgentCaps_epilog;
            }

            if (ptLine->dwKey != TLINE_KEY)
            {
                lRequestID = LINEERR_INVALLINEHANDLE;
                goto LGetAgentCaps_epilog;
            }
        }
        myexcept
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
            goto LGetAgentCaps_epilog;
        }

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;

            bProxy = TRUE;

            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_GETAGENTCAPS,
                    2 * sizeof(DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentCaps_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.GetAgentCaps.dwAddressID =
                pParams->dwAddressID;
            pProxyRequestWrapper->ProxyRequest.GetAgentCaps.
                AgentCaps.dwTotalSize = dwTotalSize;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LGetAgentCaps_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG)
                    pAsyncRequestInfo->dwLocalRequestID;
            }
        }
        else if (pLookupEntry->bRemote)
        {
            LPBYTE          pBuf;
            LPLINEAGENTCAPS pCaps;


            bProxy = TRUE;

            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwTotalSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LGetAgentCaps_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LGetAgentXxx_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pCaps = (LPLINEAGENTCAPS) (pBuf + sizeof (ASYNCEVENTMSG));

            pCaps->dwTotalSize = dwTotalSize;

            // Note: RemoteSP comes up with it's own hLineApp

            pParams->lResult = CallSP5(
                pRemoteSP->apfn[SP_LINEGETAGENTCAPS],
                "lineGetAgentCaps",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (DWORD) dwDeviceID,
                (DWORD) pParams->dwAddressID,
                (DWORD) pParams->dwAppAPIVersion,
                (ULONG_PTR) pCaps
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        if (!bProxy)
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LGetAgentCaps_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "GetAgentCaps"
        );
}


void
WINAPI
LGetAgentGroupList(
    PTCLIENT                        ptClient,
    PLINEGETAGENTGROUPLIST_PARAMS   pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    LGetAgentXxx(
        ptClient,
        (PLINEGETAGENTACTIVITYLIST_PARAMS) pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTGROUPLIST,
        SP_LINEGETAGENTGROUPLIST,
        sizeof (LINEAGENTGROUPLIST)
#if DBG
        ,
        "GetAgentGroupList"
#endif
        );
}

void
WINAPI
LGetAgentInfo(
    PTCLIENT                    ptClient,
    PLINEGETAGENTINFO_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    LGetAgentWithoutAddressIDXxx(
        ptClient,
        pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTINFO,
        SP_LINEGETAGENTINFO,
        sizeof (LINEAGENTINFO)
#if DBG
        ,
        "GetAgentInfo"
#endif
        );
}


void
WINAPI
LGetAgentSessionInfo(
    PTCLIENT                        ptClient,
    PLINEGETAGENTSESSIONINFO_PARAMS pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    LGetAgentWithoutAddressIDXxx(
        ptClient,
        (PLINEGETAGENTINFO_PARAMS) pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTSESSIONINFO,
        SP_LINEGETAGENTSESSIONINFO,
        sizeof (LINEAGENTSESSIONINFO)
#if DBG
        ,
        "GetAgentSessionInfo"
#endif
        );
}


void
WINAPI
LGetAgentSessionList(
    PTCLIENT                        ptClient,
    PLINEGETAGENTSESSIONLIST_PARAMS pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    LGetAgentWithoutAddressIDXxx(
        ptClient,
        (PLINEGETAGENTINFO_PARAMS) pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTSESSIONLIST,
        SP_LINEGETAGENTSESSIONLIST,
        sizeof (LINEAGENTSESSIONLIST)
#if DBG
        ,
        "GetAgentSessionList"
#endif
        );
}


void
WINAPI
LGetAgentStatus(
    PTCLIENT                    ptClient,
    PLINEGETAGENTSTATUS_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    LGetAgentXxx(
        ptClient,
        (PLINEGETAGENTACTIVITYLIST_PARAMS) pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETAGENTSTATUS,
        SP_LINEGETAGENTSTATUS,
        sizeof (LINEAGENTSTATUS)
#if DBG
        ,
        "GetAgentStatus"
#endif
        );
}


void
WINAPI
LGetAppPriority(
    PTCLIENT                    ptClient,
    PLINEGETAPPPRIORITY_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    DWORD   dwMediaMode = pParams->dwMediaMode,
            dwRequestMode = pParams->dwRequestMode;
    DWORD   dwCount;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwAppNameOffset
            )  ||

        ((pParams->dwExtensionIDOffset != TAPI_NO_DATA)  &&

            ((pParams->dwExtensionIDOffset + sizeof (LINEEXTENSIONID)) >
                dwParamsBufferSize))  ||

        ((pParams->dwExtensionNameTotalSize != TAPI_NO_DATA)  &&

            (pParams->dwExtensionNameTotalSize  > dwParamsBufferSize)))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    if (dwMediaMode == 0)
    {
        if ((dwRequestMode != LINEREQUESTMODE_MAKECALL) &&
            (dwRequestMode != LINEREQUESTMODE_MEDIACALL))
        {
            pParams->lResult = LINEERR_INVALREQUESTMODE;
            goto LGetAppPriority_return;
        }
    }
    else if ( 0xFF000000 & dwMediaMode )
    {
        // ignore
    }
    else if ( dwMediaMode & ~AllMediaModes2_1 )
    {
        pParams->lResult = LINEERR_INVALMEDIAMODE;
        goto LGetAppPriority_return;
    }

    if ((dwMediaMode & 0x00ffffff) || (dwMediaMode == 0))
    {
        WCHAR   szModuleName[MAX_PATH];
        WCHAR  *pszCurrentPriorityList = NULL;
        WCHAR  *pszLocationInPriorityList;


        szModuleName[0] = '"';
        wcscpy(szModuleName + 1, (PWSTR)(pDataBuf + pParams->dwAppNameOffset));
        _wcsupr( szModuleName + 1 );


        //
        // Enter the pri list critical section before we start looking
        //

        EnterCriticalSection (&gPriorityListCritSec);


        //
        // Determine which of the priority lists we want to look at
        //

        if (dwMediaMode)
        {
            for(
                dwCount = 0;
                dwCount < TapiGlobals.dwUsedPriorityLists;
                dwCount++
                )
            {
                if (dwMediaMode == TapiGlobals.pPriLists[dwCount].dwMediaModes)
                {
                    pszCurrentPriorityList =
                        TapiGlobals.pPriLists[dwCount].pszPriList;
                    break;
                }
            }
        }
        else
        {
            pszCurrentPriorityList = (dwRequestMode == LINEREQUESTMODE_MAKECALL
                ? TapiGlobals.pszReqMakeCallPriList :
                TapiGlobals.pszReqMediaCallPriList);
        }


        if (pszCurrentPriorityList &&

            (pszLocationInPriorityList = wcsstr(
                pszCurrentPriorityList,
                szModuleName
                )))
        {
            //
            // App is in pri list, determine it's position
            //

            WCHAR  *p = pszCurrentPriorityList + 1; // skip first '"'
            DWORD   i;


            for (i = 1; pszLocationInPriorityList > p; i++)
            {
                p = wcschr(p, '"');
                p++;
            }

            pParams->dwPriority = i;
        }
        else
        {
            //
            // App not listed in formal priority list, so just return 0
            //
            // Note: TAPI 1.4 said that if app was in soft pri list
            //       (i.e. had line open with OWNER priv for specified
            //       media mode) then we'd return -1 instead of 0.
            //       But that's a pain to figure out, & we figured no
            //       one was going to use that info anyway, so we settled
            //       for always returning 0.
            //

            pParams->dwPriority = 0;
        }


        //
        // Leave list critical section now that we're done
        //

        LeaveCriticalSection (&gPriorityListCritSec);

        *pdwNumBytesReturned = sizeof (LINEGETAPPPRIORITY_PARAMS);
    }

LGetAppPriority_return:

    LOG((TL_TRACE, 
        "LineEpilogSync (lineGetAppPriority) exit, returning x%x",
        pParams->lResult
        ));
}


void
WINAPI
LGetCallAddressID(
    PTCLIENT                        ptClient,
    PLINEGETCALLADDRESSID_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    PTCALLCLIENT    ptCallClient;


    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto LGetCallAddressID_exit;
    }

    if (!(ptCallClient = ReferenceCall (pParams->hCall, ptClient)))
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
        goto LGetCallAddressID_exit;
    }

    try
    {
        pParams->dwAddressID = ptCallClient->ptCall->dwAddressID;

        if (ptCallClient->dwKey != TCALLCLIENT_KEY)
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
        }
    }
    except (EXCEPTION_EXECUTE_HANDLER)
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
    }

    DereferenceObject (ghHandleTable, pParams->hCall, 1);

    if (pParams->lResult == 0)
    {
        *pdwNumBytesReturned = sizeof (LINEGETCALLADDRESSID_PARAMS);
    }

LGetCallAddressID_exit:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetCallAddressID: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
    LOG((TL_TRACE,
        "lineGetCallAddressID: exit, result=x%x",
        pParams->lResult
        ));
#endif

    return;
}


void
WINAPI
LGetCallHubTracking(
    PTCLIENT                        ptClient,
    PLINEGETCALLHUBTRACKING_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineGetCallHubTracking;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwTrackingInfoTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETCALLHUBTRACKING,  // provider func index
            &pfnTSPI_lineGetCallHubTracking,// provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetCallHubTracking"        // func name

            )) == 0  ||

        (pParams->lResult == LINEERR_OPERATIONUNAVAIL))
    {
        LPLINECALLHUBTRACKINGINFO  pTrackingInfo =
                                       (LPLINECALLHUBTRACKINGINFO) pDataBuf;


        if (pParams->lResult == LINEERR_OPERATIONUNAVAIL)
        {
            pParams->lResult = 0;
            pfnTSPI_lineGetCallHubTracking = (TSPIPROC) NULL;
        }

        if (pParams->dwTrackingInfoTotalSize <
                sizeof (LINECALLHUBTRACKINGINFO))
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetCallHubTracking_epilog;
        }

        InitTapiStruct(
            pTrackingInfo,
            pParams->dwTrackingInfoTotalSize,
            sizeof (LINECALLHUBTRACKINGINFO),
            TRUE
            );

        if (!pfnTSPI_lineGetCallHubTracking ||

            (pParams->lResult = CallSP2(
                pfnTSPI_lineGetCallHubTracking,
                "lineGetCallHubTracking",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) pTrackingInfo

                )) == 0)
        {
            try
            {
                pTrackingInfo->dwCurrentTracking =
                    ptLineClient->dwCurrentTracking;
            }
            except (EXCEPTION_EXECUTE_HANDLER)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LGetCallHubTracking_epilog;
            }

            pTrackingInfo->dwAvailableTracking |=
                LINECALLHUBTRACKING_ALLCALLS;

            pParams->dwTrackingInfoOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pTrackingInfo->dwUsedSize;
        }
    }

LGetCallHubTracking_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetCallHubTracking"
        );
}


void
WINAPI
LGetCallIDs(
    PTCLIENT                ptClient,
    PLINEGETCALLIDS_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    PTCALLCLIENT    ptCallClient;


    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto LGetCallIDs_exit;
    }

    if (!(ptCallClient = ReferenceCall (pParams->hCall, ptClient)))
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
        goto LGetCallIDs_exit;
    }

    try
    {
        pParams->dwAddressID     = ptCallClient->ptCall->dwAddressID;
        pParams->dwCallID        = ptCallClient->ptCall->dwCallID;
        pParams->dwRelatedCallID = ptCallClient->ptCall->dwRelatedCallID;

        if (ptCallClient->dwKey != TCALLCLIENT_KEY)
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
        }
    }
    except (EXCEPTION_EXECUTE_HANDLER)
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
    }

    DereferenceObject (ghHandleTable, pParams->hCall, 1);

    if (pParams->lResult == 0)
    {
        *pdwNumBytesReturned = sizeof (LINEGETCALLIDS_PARAMS);
    }

LGetCallIDs_exit:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetCallIDs: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
    LOG((TL_TRACE,
        "lineGetCallIDs: exit, result=x%x",
        pParams->lResult
        ));
#endif

    return;
}


void
WINAPI
LGetCallInfo(
    PTCLIENT                ptClient,
    PLINEGETCALLINFO_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineGetCallInfo;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCallInfoTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETCALLINFO,         // provider func index
            &pfnTSPI_lineGetCallInfo,   // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "GetCallInfo"               // func name

            )) == 0)
    {
        DWORD           dwAPIVersion, dwSPIVersion, dwTotalSize,
                        dwFixedSizeClient, dwFixedSizeSP;
        PTCALL          ptCall;
        LPLINECALLINFO  pCallInfo = (LPLINECALLINFO) pDataBuf,
                        pCallInfo2 = (LPLINECALLINFO) NULL;


        //
        // Safely retrieve the API & SPI versions, etc
        //

        try
        {
            ptCall = ptCallClient->ptCall;

            dwAPIVersion = ptCallClient->ptLineClient->dwAPIVersion;
            dwSPIVersion = ((PTLINE) ptCallClient->ptLineClient->ptLine)
                ->dwSPIVersion;

            if (ptCallClient->dwKey != TCALLCLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LGetCallInfo_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LGetCallInfo_epilog;
        }


        //
        // Determine the fixed size of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwCallInfoTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeClient = 296;    // 69 * sizeof(DWORD) + sizeof (HLINE)
                                        //     + sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_2:

            dwFixedSizeClient = 324;    // 76 * sizeof(DWORD) + sizeof (HLINE)
                                        //     + sizeof (LINEDIALPARAMS)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINECALLINFO);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetCallInfo_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeSP = 296;        // 69 * sizeof(DWORD) + sizeof (HLINE)
                                        //     + sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_2:

            dwFixedSizeSP = 324;        // 76 * sizeof(DWORD) + sizeof (HLINE)
                                        //     + sizeof (LINEDIALPARAMS)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINECALLINFO);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pCallInfo2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetCallInfo_epilog;
            }

            pCallInfo   = pCallInfo2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pCallInfo,
            dwTotalSize,
            dwFixedSizeSP,
            (pCallInfo2 == NULL ? TRUE : FALSE)
            );

        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineGetCallInfo,
                "lineGetCallInfo",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdCall,
                (ULONG_PTR) pCallInfo

                )) == 0)
        {
            //
            // Safely add the fields we're responsible for
            //

            try
            {
                pCallInfo->hLine = (HLINE) ptCallClient->ptLineClient->hLine;

                pCallInfo->dwMonitorDigitModes =
                    ptCallClient->dwMonitorDigitModes;
                pCallInfo->dwMonitorMediaModes =
                    ptCallClient->dwMonitorMediaModes;

                pCallInfo->dwNumOwners   = ptCall->dwNumOwners;
                pCallInfo->dwNumMonitors = ptCall->dwNumMonitors;

                InsertVarData(
                    pCallInfo,
                    &pCallInfo->dwAppNameSize,
                    ptCall->pszAppName,
                    ptCall->dwAppNameSize
                    );

                InsertVarData(
                    pCallInfo,
                    &pCallInfo->dwDisplayableAddressSize,
                    ptCall->pszDisplayableAddress,
                    ptCall->dwDisplayableAddressSize
                    );

                InsertVarData(
                    pCallInfo,
                    &pCallInfo->dwCalledPartySize,
                    ptCall->pszCalledParty,
                    ptCall->dwCalledPartySize
                    );

                InsertVarData(
                    pCallInfo,
                    &pCallInfo->dwCommentSize,
                    ptCall->pszComment,
                    ptCall->dwCommentSize
                    );

                if (ptCallClient->dwKey != TCALLCLIENT_KEY)
                {
                    pParams->lResult = LINEERR_INVALCALLHANDLE;
                }
            }
            myexcept
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
            }

            pCallInfo->dwCallStates |= LINECALLSTATE_UNKNOWN;


#if TELE_SERVER
            // if it's a server, map the device id
            if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
               !IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
            {
                DWORD dwCount;

                // if we fall out of this for loop, the id just
                // doesn't get updated

                for(
                    dwCount = 0;
                    dwCount < ptClient->dwLineDevices;
                    dwCount++
                    )
                {
                    if (ptClient->pLineDevices[dwCount] ==
                            pCallInfo->dwLineDeviceID)
                    {
                        pCallInfo->dwLineDeviceID = dwCount;
                        break;
                    }
                }
            }
#endif

            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //

            if (dwAPIVersion == TAPI_VERSION1_0)
            {
                if (pCallInfo->dwOrigin & LINECALLORIGIN_INBOUND)
                {
                    pCallInfo->dwOrigin = LINECALLORIGIN_UNAVAIL;
                }

                if ((pCallInfo->dwReason &
                    (LINECALLREASON_INTRUDE | LINECALLREASON_PARKED)))
                {
                    pCallInfo->dwReason = LINECALLREASON_UNAVAIL;
                }
            }


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

            if (pCallInfo == pCallInfo2)
            {
                pCallInfo = (LPLINECALLINFO) pDataBuf;

                CopyMemory (pCallInfo, pCallInfo2, dwFixedSizeClient);

                ServerFree (pCallInfo2);

                pCallInfo->dwTotalSize = pParams->dwCallInfoTotalSize;
                pCallInfo->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            if (pParams->lResult == 0)
            {
                pParams->dwCallInfoOffset = 0;

                *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                    pCallInfo->dwUsedSize;
            }
        }
    }

LGetCallInfo_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetCallInfo"
        );
}


void
WINAPI
LGetCallStatus(
    PTCLIENT                    ptClient,
    PLINEGETCALLSTATUS_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineGetCallStatus;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCallStatusTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETCALLSTATUS,       // provider func index
            &pfnTSPI_lineGetCallStatus, // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "GetCallStatus"             // func name

            )) == 0)
    {
        DWORD               dwAPIVersion, dwSPIVersion, dwTotalSize,
                            dwFixedSizeClient, dwFixedSizeSP, dwPrivilege;
        LPLINECALLSTATUS    pCallStatus = (LPLINECALLSTATUS) pDataBuf,
                            pCallStatus2 = (LPLINECALLSTATUS) NULL;


        //
        // Safely retrieve the API & SPI versions
        //

        try
        {
            dwAPIVersion = ptCallClient->ptLineClient->dwAPIVersion;
            dwSPIVersion = ((PTLINE) ptCallClient->ptLineClient->ptLine)
                ->dwSPIVersion;

            dwPrivilege = ptCallClient->dwPrivilege;

            if (ptCallClient->dwKey != TCALLCLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LGetCallStatus_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LGetCallStatus_epilog;
        }


        //
        // Determine the fixed siize of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwCallStatusTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeClient = 36;     // 9 * sizeof (DWORD)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINECALLSTATUS);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetCallStatus_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeSP = 36;         // 9 * sizeof (DWORD)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINECALLSTATUS);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pCallStatus2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetCallStatus_epilog;
            }

            pCallStatus = pCallStatus2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pCallStatus,
            dwTotalSize,
            dwFixedSizeSP,
            (pCallStatus2 == NULL ? TRUE : FALSE)
            );

        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineGetCallStatus,
                "lineGetCallStatus",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdCall,
                (ULONG_PTR) pCallStatus

                )) == 0)
        {
#if DBG
            //
            // Verify the info returned by the provider
            //

#endif

            //
            // Add the fields we're responsible for
            //

            pCallStatus->dwCallPrivilege = dwPrivilege;

            if (dwSPIVersion < TAPI_VERSION2_0 &&
                dwAPIVersion >= TAPI_VERSION2_0)
            {
                GetSystemTime (&pCallStatus->tStateEntryTime);
            }

            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

            if (pCallStatus == pCallStatus2)
            {
                pCallStatus = (LPLINECALLSTATUS) pDataBuf;

                CopyMemory (pCallStatus, pCallStatus2, dwFixedSizeClient);

                ServerFree (pCallStatus2);

                pCallStatus->dwTotalSize = pParams->dwCallStatusTotalSize;
                pCallStatus->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            pParams->dwCallStatusOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pCallStatus->dwUsedSize;

        }
    }

LGetCallStatus_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetCallStatus"
        );
}


void
WINAPI
LGetConfRelatedCalls(
    PTCLIENT                        ptClient,
    PLINEGETCONFRELATEDCALLS_PARAMS pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    DWORD               dwTotalSize = pParams->dwCallListTotalSize;
    PTCALLCLIENT        ptCallClient;
    PTLINECLIENT        ptLineClient;
    LPLINECALLLIST      pCallList = (LPLINECALLLIST) pDataBuf;
    TPOINTERLIST        confCallList, *pConfCallList = &confCallList;
    PTCONFERENCELIST    pConfList;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCallListTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        return;
    }

    if (dwTotalSize < sizeof (LINECALLLIST))
    {
        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
        return;
    }

    if (!(ptCallClient = ReferenceCall (pParams->hCall, ptClient)))
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
        return;
    }

    try
    {
        ptLineClient = ptCallClient->ptLineClient;

        if (!(pConfList = ptCallClient->ptCall->pConfList) ||
            (pConfList == (PTCONFERENCELIST) LongToPtr(0xffffffff)))
        {
            pParams->lResult = LINEERR_NOCONFERENCE;
        }
    }
    myexcept
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
    }

    DereferenceObject (ghHandleTable, pParams->hCall, 1);

    if (pParams->lResult != 0)
    {
        return;
    }

    if ((pParams->lResult = GetConfCallListFromConf(
            pConfList,
            &pConfCallList

            )) != 0)
    {
        return;
    }

    {
        DWORD   dwNeededSize = sizeof (LINECALLLIST) +
                    pConfCallList->dwNumUsedEntries * sizeof (HCALL);


        if (dwTotalSize < dwNeededSize)
        {
            pCallList->dwNeededSize = dwNeededSize;
            pCallList->dwUsedSize = sizeof (LINECALLLIST);

            FillMemory (&pCallList->dwCallsNumEntries, 3 * sizeof (DWORD), 0);

            goto LGetConfRelatedCalls_fillInList;
        }
    }


    //
    // For each call in the conf list see if the app has a
    // call client (if not create one w/ monitor privileges)
    // and add it to the list
    //

    {
        DWORD   dwNumCallsInList = 0, i;
        LPHCALL lphCallsInList = (LPHCALL) (pCallList + 1);


        for (i = 0; i < pConfCallList->dwNumUsedEntries; i++)
        {
            PTCALL  ptCall = pConfCallList->aEntries[i];


            if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
            {
                ptCallClient = ptCall->ptCallClients;

                while (ptCallClient &&
                        (ptCallClient->ptLineClient != ptLineClient))
                {
                    ptCallClient = ptCallClient->pNextSametCall;
                }

                if (!ptCallClient)
                {
                    LONG    lResult;

                    if ((lResult = CreatetCallClient(
                            ptCall,
                            ptLineClient,
                            LINECALLPRIVILEGE_MONITOR,
                            TRUE,
                            TRUE,
                            &ptCallClient,
                            FALSE
                            )))
                    {
                        // Skip...
                        UNLOCKTCALL(ptCall);
                        continue;
                    }
                }

                *(lphCallsInList++) = ptCallClient->hCall;
                dwNumCallsInList++;

                UNLOCKTCALL(ptCall);
            }
        }

        pCallList->dwUsedSize        =
        pCallList->dwNeededSize      = sizeof (LINECALLLIST) +
                                           dwNumCallsInList * sizeof (HCALL);

        pCallList->dwCallsNumEntries = dwNumCallsInList;
        pCallList->dwCallsSize       = dwNumCallsInList * sizeof (HCALL);
        pCallList->dwCallsOffset     = sizeof (LINECALLLIST);
    }


LGetConfRelatedCalls_fillInList:

    if (pConfCallList != &confCallList)
    {
        ServerFree (pConfCallList);
    }

    pCallList->dwTotalSize = dwTotalSize;

    pParams->dwCallListOffset = 0;

    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pCallList->dwUsedSize;

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetConfRelatedCalls: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetConfRelatedCalls: exit, result=x%x",
            pParams->lResult
            ));
#endif
}


void
WINAPI
LGetCountry(
    PTCLIENT                ptClient,
    PLINEGETCOUNTRY_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    LPLINECOUNTRYLIST pCountryList = (LPLINECOUNTRYLIST) pDataBuf;
    LPLINECOUNTRYLIST pCountries = NULL;

    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCountryListTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if (pParams->dwCountryListTotalSize < sizeof (LINECOUNTRYLIST))
    {
        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
    }
    else
    {

        BuildCountryListCache();

        if (pParams->dwCountryID == 0)
        {
            //
            // Client wants entire country list
            //

            if (RPC_S_OK != RpcImpersonateClient(0))
            {
                pParams->lResult = LINEERR_OPERATIONFAILED;
                return;
            }
            pCountries = BuildCountryList();
            RpcRevertToSelf();

            if (NULL == pCountries)
            {
                pParams->lResult = LINEERR_OPERATIONFAILED;
                return;
            }

            if (pParams->dwCountryListTotalSize >= pCountries->dwNeededSize)
            {
                CopyMemory(
                    pCountryList,
                    pCountries,
                    pCountries->dwUsedSize
                    );
            }
            else
            {
                pCountryList->dwNeededSize = pCountries->dwNeededSize;
                pCountryList->dwUsedSize   = sizeof(LINECOUNTRYLIST);
                pCountryList->dwNumCountries      = 0;
                pCountryList->dwCountryListSize   = 0;
                pCountryList->dwCountryListOffset = 0;
            }

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                 pCountryList->dwUsedSize;

            pCountryList->dwTotalSize = pParams->dwCountryListTotalSize;

            ServerFree(pCountries);
        }
        else
        {
            //
            // Caller wants single country
            //

            LPLINECOUNTRYLIST   pBuildCountryList;


            if ( NULL == ( pBuildCountryList = ServerAlloc( sizeof(LINECOUNTRYLIST) +
                                                       sizeof(LINECOUNTRYENTRY) +
                                                       ((MAXLEN_NAME +
                                                         MAXLEN_RULE +
                                                         MAXLEN_RULE +
                                                         MAXLEN_RULE +
                                                         100) * sizeof(WCHAR))
                                                     ) ) )
            {
                LOG((TL_ERROR, "Alloc failed for countrylist"));
                pParams->lResult = LINEERR_NOMEM;
            }
            else
            {
                LPLINECOUNTRYENTRY  pCountryEntrySource;
                LPLINECOUNTRYENTRY  pCountryEntryDest;


                pCountryEntryDest = (LPLINECOUNTRYENTRY)((PBYTE)pBuildCountryList +
                                            sizeof(LINECOUNTRYLIST));

                //
                // search through the gpCountryList looking for the entry
                //

                pCountryEntrySource = (LPLINECOUNTRYENTRY)((PBYTE)gpCountryList +
                                            sizeof(LINECOUNTRYLIST));

                while (
                         (pCountryEntrySource->dwCountryID != pParams->dwCountryID )
                       &&
                         (pCountryEntrySource->dwNextCountryID)
                      )
                {
                    pCountryEntrySource++;
                }


                if ( pCountryEntrySource->dwCountryID != pParams->dwCountryID )
                {
                    LOG((TL_ERROR, "Invalid Countrycode (%ld) in lineGetCountry",
                                    pParams->dwCountryID));
                    pParams->lResult = LINEERR_INVALCOUNTRYCODE;
                }
                else
                {
                    PBYTE pCountryListToUse;
                    PBYTE pVarOffset;
                    PBYTE pOverrideList = NULL;
                    DWORD dwNeededSize;
                    DWORD dwResourceId;
                    DWORD dwNameSize;
                    WCHAR sz[MAXLEN_NAME];

                    //
                    // Is the caller calling a specific country that there might be
                    // an override for?
                    //

                    if ( pParams->dwDestCountryID != 0 )
                    {
                        HKEY hKey;
                        HKEY hKey2;
                        TCHAR p[256];
                        
                        wsprintf(
                            p,
                            TEXT("Country List\\%ld\\Exceptions\\%ld"),
                            pParams->dwCountryID,
                            pParams->dwDestCountryID
                            );

                        //
                        // Is there an exception?
                        //

                        if (0 == RegOpenKeyEx (HKEY_LOCAL_MACHINE, gszRegKeyTelephony, 0, KEY_READ, &hKey2) &&
                            0 == RegOpenKeyEx (hKey2, p, 0, KEY_READ, &hKey))
                        {
                            PBYTE pVarOffset;

                            pOverrideList = ServerAlloc(
                                sizeof(LINECOUNTRYLIST) +
                                sizeof(LINECOUNTRYENTRY) +
                                ((MAXLEN_NAME +
                                     MAXLEN_RULE +
                                     MAXLEN_RULE +
                                     MAXLEN_RULE +
                                     100) * sizeof(WCHAR))
                                );
                            if (!pOverrideList)
                            {
                                ServerFree (pBuildCountryList);
                                RegCloseKey (hKey2);
                                RegCloseKey (hKey);
                                pParams->lResult = LINEERR_NOMEM;
                                return;
                            }

                            pCountryListToUse = pOverrideList;

                            pCountryEntrySource = (LPLINECOUNTRYENTRY)
                                (pOverrideList +
                                sizeof(LINECOUNTRYLIST));

                            pVarOffset = pOverrideList +
                                sizeof(LINECOUNTRYLIST) +
                                sizeof(LINECOUNTRYENTRY);

                            FillupACountryEntry(
                                hKey,
                                pCountryListToUse,
                                pCountryEntrySource,
                                &pVarOffset
                                );

                            RegCloseKey( hKey );
                        }
                        else
                        {
                            //
                            // No, we tried, but there was no exception.
                            //

                            pCountryListToUse = (PBYTE)gpCountryList;
                        }

                        RegCloseKey( hKey2);
                    }
                    else
                    {
                        pCountryListToUse = (PBYTE)gpCountryList;
                    }


                    //
                    // Fill in the buffer
                    //

                    dwNeededSize = sizeof(LINECOUNTRYLIST) +
                                   sizeof(LINECOUNTRYENTRY);

                    pVarOffset = (LPBYTE)pCountryEntryDest +
                                      sizeof(LINECOUNTRYENTRY);

                    //
                    // The name field has the resource string ID
                    // Need to load the actual string based on current user's language
                    //
                    
                    CopyMemory(
                        &dwResourceId,
                        pCountryListToUse + pCountryEntrySource->dwCountryNameOffset,
                        sizeof(DWORD)
                        );

                    if (RPC_S_OK != RpcImpersonateClient(0))
                    {
                        ServerFree (pBuildCountryList);
                        pParams->lResult = LINEERR_OPERATIONFAILED;
                        return;
                    }
                    if (0 == LoadStringW(
                            ghInstance,
                            dwResourceId,
                            sz,
                            ARRAYSIZE(sz)
                            ) 
                        )
                    {
                        RpcRevertToSelf();
                        ServerFree (pBuildCountryList);
                        pParams->lResult = LINEERR_OPERATIONFAILED;
                        return;
                    }
                        
                    RpcRevertToSelf();

                    dwNameSize = (wcslen(sz) + 1) * sizeof(WCHAR);
                    CopyMemory(
                        pVarOffset,
                        (LPBYTE)sz,
                        dwNameSize
                        );

                    pCountryEntryDest->dwCountryNameSize = dwNameSize;

                    pCountryEntryDest->dwCountryNameOffset =
                                 (DWORD) (pVarOffset - (LPBYTE) pBuildCountryList);
                    pVarOffset += dwNameSize;
                    dwNeededSize += dwNameSize;


                    CopyMemory(
                        pVarOffset,
                        pCountryListToUse +
                            pCountryEntrySource->dwSameAreaRuleOffset,
                        pCountryEntrySource->dwSameAreaRuleSize
                        );

                    pCountryEntryDest->dwSameAreaRuleSize =
                               pCountryEntrySource->dwSameAreaRuleSize;
                    pCountryEntryDest->dwSameAreaRuleOffset =
                                 (DWORD) (pVarOffset - (LPBYTE) pBuildCountryList);
                    pVarOffset += pCountryEntrySource->dwSameAreaRuleSize;
                    dwNeededSize += pCountryEntrySource->dwSameAreaRuleSize;


                    CopyMemory(
                        pVarOffset,
                        pCountryListToUse +
                            pCountryEntrySource->dwLongDistanceRuleOffset,
                        pCountryEntrySource->dwLongDistanceRuleSize
                        );

                    pCountryEntryDest->dwLongDistanceRuleSize =
                               pCountryEntrySource->dwLongDistanceRuleSize;
                    pCountryEntryDest->dwLongDistanceRuleOffset =
                                 (DWORD) (pVarOffset - (LPBYTE) pBuildCountryList);
                    pVarOffset += pCountryEntrySource->dwLongDistanceRuleSize;
                    dwNeededSize += pCountryEntrySource->dwLongDistanceRuleSize;


                    CopyMemory(
                        pVarOffset,
                        pCountryListToUse +
                            pCountryEntrySource->dwInternationalRuleOffset,
                        pCountryEntrySource->dwInternationalRuleSize
                        );

                    pCountryEntryDest->dwInternationalRuleSize =
                               pCountryEntrySource->dwInternationalRuleSize;
                    pCountryEntryDest->dwInternationalRuleOffset =
                                 (DWORD) (pVarOffset - (LPBYTE) pBuildCountryList);
                    pVarOffset += pCountryEntrySource->dwInternationalRuleSize;
                    dwNeededSize += pCountryEntrySource->dwInternationalRuleSize;


                    //
                    // Is there room to put this country's info?
                    //
                    if (pParams->dwCountryListTotalSize >= dwNeededSize)
                    {
                        pCountryList->dwUsedSize          = dwNeededSize;
                        pCountryList->dwNumCountries      = 1;
                        pCountryList->dwCountryListSize   = sizeof(LINECOUNTRYENTRY);
                        pCountryList->dwCountryListOffset = sizeof(LINECOUNTRYLIST);

                        pCountryEntryDest->dwCountryID     = pParams->dwCountryID;
                        pCountryEntryDest->dwCountryCode   =
                                 pCountryEntrySource->dwCountryCode;
                        pCountryEntryDest->dwNextCountryID  =
                                 pCountryEntrySource->dwNextCountryID;

                        CopyMemory(
                            (LPBYTE)pCountryList + sizeof(LINECOUNTRYLIST),
                            (LPBYTE)pBuildCountryList + sizeof(LINECOUNTRYLIST),
                            pCountryList->dwUsedSize - sizeof(LINECOUNTRYLIST)
                            );
                    }
                    else
                    {
                        //
                        // Buffer not large enough
                        //

                        pCountryList->dwUsedSize          = sizeof(LINECOUNTRYLIST);
                        pCountryList->dwNumCountries      = 0;
                        pCountryList->dwCountryListSize   = 0;
                        pCountryList->dwCountryListOffset = 0;
                    }

                    pCountryList->dwNeededSize = dwNeededSize;
                    pCountryList->dwTotalSize = pParams->dwCountryListTotalSize;

                    *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                        pCountryList->dwUsedSize;


                    //
                    // Did we have a "special" case?
                    //
                    if ( pOverrideList )
                    {
                        ServerFree( pOverrideList );
                    }

                }

                ServerFree( pBuildCountryList );
            }
        }
    }
    pParams->dwCountryListOffset = 0;

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetCountry: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetCountry: exit, result=x%x",
            pParams->lResult
            ));
#endif
}

void
WINAPI
LGetCountryGroups(
    PTCLIENT                ptClient,
    PLINEGETCOUNTRYGROUP_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    DWORD           dwCount, dwIdx, dwIdx1;
    BOOL            bFoundAll = TRUE;
    DWORD *         pCountryGroups = NULL;
    DWORD *         pCountryID;
    LPLINECOUNTRYENTRY  pCountryEntry;

    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCountryIdSize > dwParamsBufferSize   ||
        0 == pParams->dwCountryIdSize                   ||
        pParams->dwCountryIdSize > pParams->dwCountryGroupSize
       )
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    dwCount = pParams->dwCountryIdSize / sizeof(DWORD);
    
    BuildCountryListCache();

    if (NULL == gpCountryGroups)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    ASSERT( NULL != gpCountryList );

    pCountryGroups = ServerAlloc (pParams->dwCountryIdSize);
    if (NULL == pCountryGroups)
    {
        pParams->lResult = LINEERR_NOMEM;
        return;
    }


        
    pCountryID = (DWORD*)(pDataBuf + pParams->dwCountryIdOffset);
    for( dwIdx = 0; dwIdx < dwCount; dwIdx++, pCountryID++ )
    {
        // find the country 
        pCountryEntry = (LPLINECOUNTRYENTRY)
            ((LPBYTE) gpCountryList + gpCountryList->dwCountryListOffset);

        for( dwIdx1 = 0; dwIdx1 < gpCountryList->dwNumCountries; dwIdx1++, pCountryEntry++ )
        {
            if (pCountryEntry->dwCountryID == *pCountryID)
            {
                pCountryGroups[ dwIdx ] = gpCountryGroups[ dwIdx1 ];
                break;
            }
        }
        if (dwIdx1 == gpCountryList->dwNumCountries)
        {
            LOG((TL_ERROR, "Invalid CountryID (%ld) in lineGetCountryGroup",
                                pCountryEntry->dwCountryID));

            bFoundAll = FALSE;
            break;
        }
    }

    if (bFoundAll)
    {
        pParams->dwCountryGroupOffset = pParams->dwCountryIdOffset;
        pParams->dwCountryGroupSize = pParams->dwCountryIdSize;
        memset( pDataBuf + pParams->dwCountryGroupOffset, 0, pParams->dwCountryGroupSize );
        CopyMemory(
                    pDataBuf + pParams->dwCountryGroupOffset,
                    pCountryGroups,
                    pParams->dwCountryIdSize
                    );

        *pdwNumBytesReturned = sizeof (TAPI32_MSG) + 
            pParams->dwCountryGroupOffset +
            pParams->dwCountryGroupSize;
    }
    else
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
    }

    ServerFree (pCountryGroups);

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "LGetCountryGroups: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "LGetCountryGroups: exit, result=x%x",
            pParams->lResult
            ));
#endif
}


void
WINAPI
LGetDevCaps(
    PTCLIENT                ptClient,
    PLINEGETDEVCAPS_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineGetDevCaps;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwDevCapsTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETDEVCAPS,          // provider func index
            &pfnTSPI_lineGetDevCaps,    // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetDevCaps"                // func name

            )) == 0)
    {
        DWORD           dwAPIVersion, dwSPIVersion, dwTotalSize,
                        dwFixedSizeClient, dwFixedSizeSP;
        LPLINEDEVCAPS   pDevCaps = (LPLINEDEVCAPS) pDataBuf,
                        pDevCaps2 = (LPLINEDEVCAPS) NULL;


        //
        // Verify API & SPI version compatibility
        //

        dwAPIVersion = pParams->dwAPIVersion;

        dwSPIVersion = pLookupEntry->dwSPIVersion;

        if (!IsAPIVersionInRange (dwAPIVersion, dwSPIVersion))
        {
            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto LGetDevCaps_epilog;
        }


        //
        // Verify Ext version compatibility
        //

        if (!IsValidLineExtVersion (dwDeviceID, pParams->dwExtVersion))
        {
            pParams->lResult = LINEERR_INCOMPATIBLEEXTVERSION;
            goto LGetDevCaps_epilog;
        }


        //
        // Determine the fixed size of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwDevCapsTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:

            dwFixedSizeClient = 236;    // 47 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION1_4:

            dwFixedSizeClient = 240;    // 48 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:

            dwFixedSizeClient = 252;    // 51 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_2:

            dwFixedSizeClient = 268;    // 51 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS) +
                                        //     sizeof (GUID)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINEDEVCAPS);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetDevCaps_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:

            dwFixedSizeSP = 236;        // 47 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION1_4:

            dwFixedSizeSP = 240;        // 48 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:

            dwFixedSizeSP = 252;        // 51 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS)
            break;

        case TAPI_VERSION2_2:

            dwFixedSizeSP = 268;        // 51 * sizeof (DWORD) +
                                        //     3 * sizeof (LINEDIALPARAMS) +
                                        //     sizeof (GUID)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINEDEVCAPS);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pDevCaps2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetDevCaps_epilog;
            }

            pDevCaps    = pDevCaps2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pDevCaps,
            dwTotalSize,
            dwFixedSizeSP,
            (pDevCaps2 == NULL ? TRUE : FALSE)
            );

        if (pLookupEntry->bRemoved)
        {
            ServerFree (pDevCaps2);
            pParams->lResult = LINEERR_NODEVICE;
        }
        else if ((pParams->lResult = CallSP4(
                pfnTSPI_lineGetDevCaps,
                "lineGetDevCaps",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (DWORD) dwSPIVersion,
                (DWORD) pParams->dwExtVersion,
                (ULONG_PTR) pDevCaps

                )) == 0)
        {
#if DBG
            //
            // Verify the info returned by the provider
            //

#endif


            //
            // Add the fields we're responsible for
            //

            pDevCaps->dwLineStates |= LINEDEVSTATE_OPEN |
                                      LINEDEVSTATE_CLOSE |
                                      LINEDEVSTATE_REINIT |
                                      LINEDEVSTATE_TRANSLATECHANGE;


            if (dwAPIVersion >= TAPI_VERSION3_0)
            {
                pDevCaps->dwAvailableTracking |=
                    LINECALLHUBTRACKING_ALLCALLS;
            }


            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //

            if ((dwAPIVersion == TAPI_VERSION1_0) &&
                (pDevCaps->dwMediaModes & LINEMEDIAMODE_VOICEVIEW))
            {
                pDevCaps->dwMediaModes = LINEMEDIAMODE_UNKNOWN |
                    (pDevCaps->dwMediaModes & ~LINEMEDIAMODE_VOICEVIEW);
            }

            if ((dwAPIVersion < TAPI_VERSION2_1) &&
                (pDevCaps->dwMediaModes & LINEMEDIAMODE_VIDEO))
            {
                pDevCaps->dwMediaModes = LINEMEDIAMODE_UNKNOWN |
                    (pDevCaps->dwMediaModes & ~LINEMEDIAMODE_VIDEO);
            }


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

            if (pDevCaps == pDevCaps2)
            {
                pDevCaps = (LPLINEDEVCAPS) pDataBuf;

                CopyMemory (pDevCaps, pDevCaps2, dwFixedSizeClient);

                ServerFree (pDevCaps2);

                pDevCaps->dwTotalSize = pParams->dwDevCapsTotalSize;
                pDevCaps->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            pParams->dwDevCapsOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pDevCaps->dwUsedSize;
        }
        else
        {
            ServerFree (pDevCaps2);
        }
    }

LGetDevCaps_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetDevCaps"
        );
}


void
WINAPI
LGetDevConfig(
    PTCLIENT                    ptClient,
    PLINEGETDEVCONFIG_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineGetDevConfig;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwDeviceConfigTotalSize > dwParamsBufferSize) ||

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDeviceClassOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            0,                          // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,       // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETDEVCONFIG,        // provider func index
            &pfnTSPI_lineGetDevConfig,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetDevConfig"              // func name

            )) == 0)
    {
        WCHAR      *pszDeviceClass;
        LPVARSTRING pConfig = (LPVARSTRING) pDataBuf;


        //
        // Alloc a temporary buf for the dev class, since we'll be using
        // the existing buffer for output
        //

        if (!(pszDeviceClass = (WCHAR *) ServerAlloc( sizeof(WCHAR) * ( 1 +
                lstrlenW((PWSTR)(pDataBuf + pParams->dwDeviceClassOffset)))
                )))
        {
            pParams->lResult = LINEERR_NOMEM;
            goto LGetDevConfig_epilog;
        }

        wcscpy(
            pszDeviceClass,
            (PWSTR)(pDataBuf + pParams->dwDeviceClassOffset)
            );

        if (!InitTapiStruct(
                pConfig,
                pParams->dwDeviceConfigTotalSize,
                sizeof (VARSTRING),
                TRUE
                ))
        {
            ServerFree (pszDeviceClass);
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetDevConfig_epilog;
        }

        if (pLookupEntry->bRemoved)
        {
            ServerFree (pszDeviceClass);
            pParams->lResult = LINEERR_NODEVICE;
            goto LGetDevConfig_epilog;
        }

        if ((pParams->lResult = CallSP3(
                pfnTSPI_lineGetDevConfig,
                "lineGetDevConfig",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (ULONG_PTR) pConfig,
                (ULONG_PTR) pszDeviceClass

                )) == 0)
        {
            //
            // Indicate how many bytes of data we're passing back
            //

            pParams->dwDeviceConfigOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pConfig->dwUsedSize;
        }

        ServerFree (pszDeviceClass);
    }

LGetDevConfig_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetDevConfig"
        );
}


void
WINAPI
LGetGroupList(
    PTCLIENT                    ptClient,
    PLINEGETGROUPLIST_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    //
    // note: can't use lgetagentxxx because
    // pparams don't match
    //

    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetGroupList"              // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID, dwTotalSize =pParams->dwGroupListTotalSize;
        PTLINECLIENT    pProxy;


        if (dwTotalSize < sizeof(LINEAGENTGROUPLIST))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LGetGroupList_epilog;
        }

        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_GETGROUPLIST,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LGetGroupList_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpGroupList;
        pAsyncRequestInfo->dwParam2 = dwTotalSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_GETGROUPLIST,
                    dwTotalSize,
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LGetGroupList_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.GetGroupList.
                GroupList.dwTotalSize = dwTotalSize;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LGetGroupList_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE                  pBuf;
            LPLINEAGENTGROUPLIST    pGroupList;


            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwTotalSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LGetGroupList_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LGetAgentXxx_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pGroupList = (LPLINEAGENTGROUPLIST)
                (pBuf + sizeof (ASYNCEVENTMSG));

            pGroupList->dwTotalSize = dwTotalSize;

            pParams->lResult = CallSP3(
                pRemoteSP->apfn[SP_LINEGETGROUPLIST],
                "GetGroupList",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) pGroupList
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LGetGroupList_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "GetGroupList"
        );

}

void
WINAPI
LGetHubRelatedCalls(
    PTCLIENT                        ptClient,
    PLINEGETHUBRELATEDCALLS_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    DWORD               dwTotalSize = pParams->dwCallListTotalSize,
                        dwNeededSize, dwUsedSize, dwCallHubID, i, j;
    PTCALL              ptCall;
    PTLINEAPP           ptLineApp;
    PTPROVIDER          ptProvider;
    TPOINTERLIST        fastCallList = {0}, *pCallList = &fastCallList;
    PTCALLCLIENT        ptCallClient;
    PTCALLHUBCLIENT     ptCallHubClient;
    PTHASHTABLEENTRY    pEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCallListTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    //
    // State/param validation
    //

    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto LGetHubRelatedCalls_exit;
    }

    if (dwTotalSize < sizeof (LINECALLLIST))
    {
        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
        goto LGetHubRelatedCalls_exit;
    }


    //
    // Determine the associated tProvider, call hub id, & tLineApp
    // (assume failure, resetting lResult to 0 only when we're sure
    // we're ok)
    //

    pParams->lResult = LINEERR_INVALCALLHANDLE;

    if (pParams->hCallHub)
    {
        if (!pParams->hCall  &&

            (ptCallHubClient = IsValidCallHub(
                pParams->hCallHub,
                ptClient
                )))
        {
            try
            {
                ptProvider = ptCallHubClient->ptProvider;
                dwCallHubID = ptCallHubClient->dwCallHubID;
                ptLineApp = ptCallHubClient->ptLineApp;

                if (ptCallHubClient->dwKey == TCALLHUBCLIENT_KEY)
                {
                    pParams->lResult = 0;
                }
            }
            except (EXCEPTION_EXECUTE_HANDLER)
            {
                // do nothing, error handled below
            }
        }
    }
    else
    {
        if ((ptCallClient = ReferenceCall(
                pParams->hCall,
                ptClient
                )))
        {
            try
            {
                if ((dwCallHubID = ptCallClient->ptCall->dwCallID))
                {
                    ptProvider = ptCallClient->ptCall->ptProvider;
                    ptLineApp = ptCallClient->ptLineClient->ptLineApp;

                    if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                    {
                        pParams->lResult = 0;
                        ptCallHubClient = NULL;
                    }
                }
            }
            except (EXCEPTION_EXECUTE_HANDLER)
            {
                // do nothing, error handled below
            }

            DereferenceObject (ghHandleTable, pParams->hCall, 1);
        }
    }

    if (pParams->lResult != 0)
    {
        goto LGetHubRelatedCalls_exit;
    }


    //
    // Get the list of tCall's for this tProvider/CallHubID.  Also, if
    // the tCallHubClient is not already known then try to find it.
    //

    pEntry = AcquireHashTableEntryLock (ptProvider, dwCallHubID);

    if (!pEntry)
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
        goto LGetHubRelatedCalls_exit;
    }

    if (pEntry->dwCallHubID != dwCallHubID)
    {
        ReleaseHashTableEntryLock (ptProvider, pEntry);
        pParams->lResult = LINEERR_INVALCALLHANDLE;
        goto LGetHubRelatedCalls_exit;
    }

    if (!ptCallHubClient)
    {
        ptCallHubClient = pEntry->ptCallHubClients;

        while (ptCallHubClient  &&  ptCallHubClient->ptLineApp != ptLineApp)
        {
            ptCallHubClient = ptCallHubClient->pNext;
        }


        //
        // If there's no tCallHubClient then there's no call hub as far
        // as this app is concerned
        //

        if (!ptCallHubClient)
        {
            ReleaseHashTableEntryLock (ptProvider, pEntry);
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LGetHubRelatedCalls_exit;
        }
    }

    GetList (&pEntry->CallHubList, &pCallList);

    ReleaseHashTableEntryLock (ptProvider, pEntry);


    //
    // Insert the hCallHub at the front of the call list
    //

    dwNeededSize = sizeof (LINECALLLIST) + sizeof (HCALLHUB);

    if (dwNeededSize <= dwTotalSize)
    {
        *((LPHCALLHUB)(pDataBuf + sizeof (LINECALLLIST))) = 
                                            ptCallHubClient->hCallHub;

        dwUsedSize = dwNeededSize;
    }
    else
    {
        dwUsedSize = sizeof (LINECALLLIST);
    }

    //
    // For each of the tCall in the list get the list of tCallClients,
    // then for each tCallClient see if it's on the same tLineApp
    // as the specified call/hub, & if so add it to the list
    //

    for (i = 0; i < pCallList->dwNumUsedEntries; i++)
    {
        TPOINTERLIST    fastCallClientList,
                       *pCallClientList = &fastCallClientList;


        ptCall = CONTAINING_RECORD(
            pCallList->aEntries[i],
            TCALL,
            CallHubList
            );

        if (GetCallClientListFromCall (ptCall, &pCallClientList) != 0)
        {
            continue;
        }

        for (j = 0; j < pCallClientList->dwNumUsedEntries; j++)
        {
            PTCALLCLIENT    ptCallClient = pCallClientList->aEntries[j];

            try
            {
                if ((ptCallClient->ptLineClient->ptLineApp == ptLineApp)  &&
                    (ptCallClient->dwKey == TCALLCLIENT_KEY))
                {
                    if (!ptCallClient->ptCallHubClient)
                    {
                        ptCallClient->ptCallHubClient = ptCallHubClient;
                    }

                    if ((dwUsedSize + sizeof(HCALL)) <= dwTotalSize)
                    {
                        *((LPHCALL)(pDataBuf + dwUsedSize)) =
                            ptCallClient->hCall;

                        dwUsedSize += sizeof(HCALL);
                    }

                    dwNeededSize += sizeof(HCALL);
                }
            }
            except (EXCEPTION_EXECUTE_HANDLER)
            {
                // do nothing, this call not included in list
            }
        }

        if (pCallClientList != &fastCallClientList)
        {
            ServerFree (pCallClientList);
        }
    }

    if (pCallList != &fastCallList)
    {
        ServerFree (pCallList);
    }

    ((LPLINECALLLIST) pDataBuf)->dwTotalSize  = dwTotalSize;
    ((LPLINECALLLIST) pDataBuf)->dwNeededSize = dwNeededSize;
    ((LPLINECALLLIST) pDataBuf)->dwUsedSize   = dwUsedSize;
    ((LPLINECALLLIST) pDataBuf)->dwCallsSize  = dwUsedSize -
        sizeof (LINECALLLIST);
    ((LPLINECALLLIST) pDataBuf)->dwCallsNumEntries =
        ((LPLINECALLLIST) pDataBuf)->dwCallsSize / sizeof (HCALL);
    ((LPLINECALLLIST) pDataBuf)->dwCallsOffset     = sizeof (LINECALLLIST);

    pParams->dwCallListOffset = 0;

    *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
        ((LPLINECALLLIST) pDataBuf)->dwUsedSize;

LGetHubRelatedCalls_exit:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetHubRelatedCalls: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetHubRelatedCalls: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LGetIcon(
    PTCLIENT            ptClient,
    PLINEGETICON_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    //
    // Note: Icons are Windows NT User Objects, so. HICONs are public to
    //       all processes, and do not need to be dup'd.
    //

    BOOL                bCloseMutex;
    WCHAR              *pszDeviceClass;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineGetIcon;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwDeviceClassOffset != TAPI_NO_DATA)  &&

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDeviceClassOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    pszDeviceClass = (WCHAR *) (pParams->dwDeviceClassOffset == TAPI_NO_DATA ?
        NULL : pDataBuf + pParams->dwDeviceClassOffset);

    if ((pParams->lResult = LINEPROLOG(
            ptClient,                   // tClient
            DEVICE_ID,                  // widget type
            0,                          // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETICON,             // provider func index
            &pfnTSPI_lineGetIcon,       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetIcon"                   // func name

            )) == 0)
    {
        if ((pParams->lResult = CallSP3(
                pfnTSPI_lineGetIcon,
                "lineGetIcon",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (ULONG_PTR) pszDeviceClass,
                (ULONG_PTR) &pParams->hIcon

                )) == 0)
        {
            *pdwNumBytesReturned = sizeof (LINEGETICON_PARAMS);
        }
    }
    else if (pParams->lResult == LINEERR_OPERATIONUNAVAIL)
    {
        if ((pszDeviceClass == NULL) ||
            (_wcsicmp(pszDeviceClass, L"tapi/line") == 0))
        {
            pParams->hIcon = TapiGlobals.hLineIcon;
            pParams->lResult = 0;
            *pdwNumBytesReturned = sizeof (LINEGETICON_PARAMS);
        }
        else
        {
            pParams->lResult = LINEERR_INVALDEVICECLASS;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetIcon"
        );
}

void
WINAPI
LGetIDEx(
    PTCLIENT            ptClient,
    PLINEGETID_PARAMS   pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{

    LPBYTE pDeviceClass = pDataBuf + pParams->dwDeviceClassOffset;
    LPWSTR pDeviceClassCopy = NULL;
    LPWSTR szStringId1 = NULL;
    LPWSTR szStringId2 = NULL;
    LPVARSTRING pID = (LPVARSTRING) pDataBuf;
    DWORD  dwAvailSize;

    //
    // Make a copy of the device class
    //
    pDeviceClassCopy = (LPWSTR) ServerAlloc( (1 + wcslen( (LPWSTR)pDeviceClass )) * sizeof(WCHAR));
    if (!pDeviceClassCopy)
    {
        LOG((TL_ERROR, "LGetIDEx: failed to allocate DeviceClassCopy"));
        pParams->lResult = LINEERR_NOMEM;
    }

    wcscpy(pDeviceClassCopy, (LPWSTR)pDeviceClass);

    //
    // First call LGetID
    //
    LGetID( ptClient,
            pParams,
            dwParamsBufferSize,
            pDataBuf,
            pdwNumBytesReturned);

    //
    // if LGetID was successful and the request was for a wave device, 
    // translate the device ID into a string ID 
    //
    if (    (pParams->lResult == 0) &&
            !(pID->dwNeededSize > pID->dwTotalSize)
       ) 
    {
        if (!_wcsicmp((LPWSTR)pDeviceClassCopy, L"wave/in")  ||
            !_wcsicmp((LPWSTR)pDeviceClassCopy, L"wave/out") ||
            !_wcsicmp((LPWSTR)pDeviceClassCopy, L"midi/in")  ||
            !_wcsicmp((LPWSTR)pDeviceClassCopy, L"midi/out") 
           )
        {
            szStringId1 = WaveDeviceIdToStringId (
                            *(DWORD*)((LPBYTE)pID + pID->dwStringOffset), 
                            (LPWSTR)pDeviceClassCopy);
            if ( szStringId1 )
            {
                dwAvailSize = pID->dwTotalSize - pID->dwUsedSize + sizeof(DWORD);
                if ( dwAvailSize >= (wcslen(szStringId1) + 1) * sizeof(WCHAR) )
                {
                    wcscpy( (LPWSTR)((LPBYTE)pID + pID->dwStringOffset), szStringId1 );
                    pID->dwStringSize = (wcslen(szStringId1) + 1) * sizeof(WCHAR);
                    pID->dwUsedSize = pID->dwNeededSize = pID->dwUsedSize + pID->dwStringSize - sizeof(DWORD);
                    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pID->dwUsedSize;
                }
                else
                {
                    pID->dwNeededSize = (wcslen(szStringId1) + 1) * sizeof(WCHAR);
                }

                ServerFree(szStringId1);
            }
            else
            {
                LOG((TL_ERROR, "LGetIDEx:  WaveDeviceIdToStringId failed"));
                pParams->lResult = LINEERR_OPERATIONFAILED;
            }
        } else if (!_wcsicmp((LPWSTR)pDeviceClassCopy, L"wave/in/out"))
        {
            szStringId1 = WaveDeviceIdToStringId (
                            *(DWORD*)((LPBYTE)pID + pID->dwStringOffset), 
                            L"wave/in");
            szStringId2 = WaveDeviceIdToStringId (
                            *( (DWORD*)((LPBYTE)pID + pID->dwStringOffset) + 1 ), 
                            L"wave/out");
            if ( szStringId1 && szStringId2 )
            {
                dwAvailSize = pID->dwTotalSize - pID->dwUsedSize + 2 * sizeof(DWORD);
                if ( dwAvailSize >= (wcslen(szStringId1) + wcslen(szStringId2) + 2) * sizeof(WCHAR) )
                {
                    wcscpy( (LPWSTR)((LPBYTE)pID + pID->dwStringOffset), szStringId1 );
                    wcscpy( (LPWSTR)
                        ((LPBYTE)pID + pID->dwStringOffset + 
                                      (wcslen(szStringId1) + 1) * sizeof(WCHAR)),
                        szStringId2
                        );
                    pID->dwStringSize = (wcslen(szStringId1) + wcslen(szStringId2) + 2) * sizeof(WCHAR);
                    pID->dwUsedSize = pID->dwNeededSize = pID->dwUsedSize + pID->dwStringSize - 2 * sizeof(DWORD);
                    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pID->dwUsedSize;
                }
                else
                {
                    pID->dwNeededSize = (wcslen(szStringId1) + wcslen(szStringId2) + 2) * sizeof(WCHAR);
                }

            }
            else
            {
                LOG((TL_ERROR, "LGetIDEx:  WaveDeviceIdToStringId failed"));
                pParams->lResult = LINEERR_OPERATIONFAILED;
            }
            
            ServerFree(szStringId1);
            ServerFree(szStringId2);
        }
    }

    ServerFree(pDeviceClassCopy);
}

void
WINAPI
LGetID(
    PTCLIENT            ptClient,
    PLINEGETID_PARAMS   pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL        bCloseMutex, bSPITooLow = FALSE;
    DWORD       dwWidgetType, hWidget, dwPrivilege;
    HANDLE      hMutex;
    ULONG_PTR   hdWidget;
    LPVOID      context;
    TSPIPROC    pfnTSPI_lineGetID;
    DWORD       objectToDereference;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwDeviceIDTotalSize > dwParamsBufferSize)  ||

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDeviceClassOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    switch ( pParams->dwSelect )
    {
    case LINECALLSELECT_CALL:
    {
        dwWidgetType = ANY_RT_HCALL;
        hWidget      = pParams->hCall;
        dwPrivilege  = LINECALLPRIVILEGE_MONITOR;

        break;
    }
    case LINECALLSELECT_DEVICEID:
    {
        PTLINEAPP           ptLineApp;
        BOOL                bVersion = FALSE;
        PTLINELOOKUPENTRY   ptLineLookup;

        LOG((TL_INFO, "lineGetID:  LINECALLSELECT_DEVICEID. ptClient %p", ptClient));

        if (WaitForExclusiveClientAccess (ptClient))
        {
            ptLineApp = ptClient->ptLineApps;

            // see if any line app is > version 2.0

            while (ptLineApp)
            {
                LOG((TL_INFO, "lineGetID:  ptLineApp->dwAPIVersion %lx, TAPI_VERSION2_0 %lx", ptLineApp->dwAPIVersion, TAPI_VERSION2_0));

                if (ptLineApp->dwAPIVersion > TAPI_VERSION2_0)
                {
                    bVersion = TRUE;
                    break;
                }

                ptLineApp = ptLineApp->pNext;
            }

            UNLOCKTCLIENT (ptClient);
        }
        else
        {
            pParams->lResult = LINEERR_OPERATIONFAILED;
            return;
        }

        if (!bVersion)
        {
            LOG((TL_ERROR, "lineGetID failed with invalid call select"));
            pParams->lResult = LINEERR_INVALCALLSELECT;
            return;
        }

        // check the spi version

        ptLineLookup = GetLineLookupEntry (pParams->dwAddressID);

        if (!ptLineLookup)
        {
            LOG((TL_ERROR, "lineGetID failed with invalid device id"));

            pParams->lResult = LINEERR_BADDEVICEID;

            return;
        }

        if (ptLineLookup->ptProvider->dwSPIVersion <= TAPI_VERSION2_0)
        {
            bSPITooLow = TRUE;
        }

        dwWidgetType = DEVICE_ID;
        hWidget      = 0;
        dwPrivilege  = pParams->dwAddressID;

        break;
    }
    case LINECALLSELECT_ADDRESS:
    case LINECALLSELECT_LINE:
    {
        dwWidgetType = ANY_RT_HLINE;
        hWidget      = pParams->hLine;
        dwPrivilege  = 0;

        break;
    }
    default:

        LOG((TL_ERROR, "lineGetID failed with invalid call select"));

        pParams->lResult = LINEERR_INVALCALLSELECT;

        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            dwWidgetType,               // widget type
            hWidget,                    // client widget handle
            &hdWidget,                  // provider widget handle
            dwPrivilege,                // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETID,               // provider func index
            &pfnTSPI_lineGetID,         // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &context,                   // context
            "GetID"                     // func name

            )) == 0  ||  pParams->lResult == LINEERR_OPERATIONUNAVAIL)
    {
        WCHAR       *pszDeviceClass;
        LPVARSTRING pID = (LPVARSTRING) pDataBuf;


        //
        // We'll handle the "tapi/line" class right here rather than
        // burden every single driver with having to support it
        //

        if (_wcsicmp(
                (PWSTR)(pDataBuf + pParams->dwDeviceClassOffset),
                L"tapi/line"

                ) == 0)
        {
            if (!InitTapiStruct(
                    pID,
                    pParams->dwDeviceIDTotalSize,
                    sizeof (VARSTRING),
                    TRUE
                    ))
            {
                pParams->lResult = LINEERR_STRUCTURETOOSMALL;
                goto LGetID_epilog;
            }

            pID->dwNeededSize += sizeof (DWORD);

            if (pID->dwTotalSize >= pID->dwNeededSize)
            {
                try
                {
                    switch (pParams->dwSelect)
                    {
                    case LINECALLSELECT_ADDRESS:
                    {
                        if (pParams->dwAddressID >= ((PTLINECLIENT)
                                context)->ptLine->dwNumAddresses)
                        {
                            pParams->lResult = LINEERR_INVALADDRESSID;
                            goto LGetID_epilog;
                        }

                        *((LPDWORD)(pID + 1)) = ((PTLINECLIENT)
                            context)->ptLine->dwDeviceID;

                        break;
                    }

                    case LINECALLSELECT_CALL:
                    {
                        *((LPDWORD)(pID + 1)) = ((PTCALLCLIENT)
                            context)->ptCall->ptLine->dwDeviceID;

                        break;
                    }

                    case LINECALLSELECT_LINE:
                    {
                        *((LPDWORD)(pID + 1)) = ((PTLINECLIENT)
                            context)->ptLine->dwDeviceID;

                        break;
                    }

                    case LINECALLSELECT_DEVICEID:
                    {
                        *((LPDWORD)(pID + 1)) = pParams->dwAddressID;

                        break;
                    }
                    } // switch
                }
                myexcept
                {
                    switch (pParams->dwSelect)
                    {
                    case LINECALLSELECT_CALL:

                        pParams->lResult = LINEERR_INVALCALLHANDLE;
                        break;

                    case LINECALLSELECT_LINE:
                    case LINECALLSELECT_ADDRESS:

                        pParams->lResult = LINEERR_INVALLINEHANDLE;
                        break;

                    case LINECALLSELECT_DEVICEID:

                        pParams->lResult = LINEERR_BADDEVICEID;
                        break;
                    }

                    goto LGetID_epilog;
                }

                pID->dwUsedSize     += sizeof (DWORD);
                pID->dwStringFormat = STRINGFORMAT_BINARY;
                pID->dwStringSize   = sizeof (DWORD);
                pID->dwStringOffset = sizeof (VARSTRING);
            }


            //
            // Indicate offset & how many bytes of data we're passing back
            //

            pParams->lResult = 0;
            pParams->dwDeviceIDOffset = 0;
            *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pID->dwUsedSize;
            goto LGetID_epilog;
        }

        // see if they want the provider id
        if (_wcsicmp(
                (PWSTR)(pDataBuf + pParams->dwDeviceClassOffset),
                L"tapi/providerid"

                ) == 0)
        {
            if (!InitTapiStruct(
                    pID,
                    pParams->dwDeviceIDTotalSize,
                    sizeof (VARSTRING),
                    TRUE
                    ))
            {
                pParams->lResult = LINEERR_STRUCTURETOOSMALL;
                goto LGetID_epilog;
            }

            pID->dwNeededSize += sizeof (DWORD);

            if (pID->dwTotalSize >= pID->dwNeededSize)
            {
                try
                {
                    switch (pParams->dwSelect)
                    {
                    case LINECALLSELECT_ADDRESS:
                    {
                        if (pParams->dwAddressID >= ((PTLINECLIENT)
                                context)->ptLine->dwNumAddresses)
                        {
                            pParams->lResult = LINEERR_INVALADDRESSID;
                            goto LGetID_epilog;
                        }

                        *((LPDWORD)(pID + 1)) = ((PTLINECLIENT)
                            context)->ptLine->ptProvider->
                                dwPermanentProviderID;

                        break;
                    }
                    case LINECALLSELECT_DEVICEID:
                    {
                        *((LPDWORD)(pID + 1)) = ((PTLINELOOKUPENTRY)
                            context)->ptProvider->dwPermanentProviderID;

                        break;
                    }
                    case LINECALLSELECT_CALL:
                    {
                        PTCALLCLIENT    ptCallClient = (PTCALLCLIENT)
                                            context;

                        *((LPDWORD)(pID + 1)) = ptCallClient->ptLineClient->
                            ptLine->ptProvider->dwPermanentProviderID;

                        break;
                    }
                    case LINECALLSELECT_LINE:
                    {
                        *((LPDWORD)(pID + 1)) = ((PTLINECLIENT)
                            context)->ptLine->ptProvider->
                                dwPermanentProviderID;

                        break;
                    }
                    default:

                        // we've already verified the call select flags above.
                        break;
                    }
                }
                myexcept
                {
                    switch (pParams->dwSelect)
                    {
                    case LINECALLSELECT_CALL:

                        pParams->lResult = LINEERR_INVALCALLHANDLE;
                        break;

                    case LINECALLSELECT_LINE:
                    case LINECALLSELECT_ADDRESS:

                        pParams->lResult = LINEERR_INVALLINEHANDLE;
                        break;

                    case LINECALLSELECT_DEVICEID:

                        pParams->lResult = LINEERR_BADDEVICEID;
                        break;
                    }

                    goto LGetID_epilog;
                }

                pID->dwUsedSize     += sizeof (DWORD);
                pID->dwStringFormat = STRINGFORMAT_BINARY;
                pID->dwStringSize   = sizeof (DWORD);
                pID->dwStringOffset = sizeof (VARSTRING);
            }


            //
            // Indicate offset & how many bytes of data we're passing back
            //

            pParams->lResult = 0;
            pParams->dwDeviceIDOffset = 0;
            *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pID->dwUsedSize;
            goto LGetID_epilog;
        }


        if (pParams->lResult ==  LINEERR_OPERATIONUNAVAIL)
        {
            goto LGetID_epilog;
        }


        if (bSPITooLow)
        {
            pParams->lResult = LINEERR_INVALCALLSELECT;
            goto LGetID_epilog;
        }

        //
        // Alloc a temporary buf for the dev class, since we'll be using
        // the existing buffer for output
        //

        {
            UINT nStringSize;

            nStringSize = sizeof(WCHAR) * (1 + wcslen((PWSTR)(pDataBuf +
                                  pParams->dwDeviceClassOffset)));

            if (0 == nStringSize)
            {
                LOG((TL_ERROR, "Bad string size (0) in lineGetID!"));
                pParams->lResult = LINEERR_INVALPARAM;
                goto LGetID_epilog;
            }

            if (!(pszDeviceClass = (WCHAR *) ServerAlloc (nStringSize)))
            {
                LOG((TL_ERROR, "Mem failed in lineGetID!"));
                pParams->lResult = LINEERR_NOMEM;
                goto LGetID_epilog;
            }
        }

        wcscpy(
            pszDeviceClass,
            (PWSTR)(pDataBuf + pParams->dwDeviceClassOffset)
            );

        if (!InitTapiStruct(
                pID,
                pParams->dwDeviceIDTotalSize,
                sizeof (VARSTRING),
                TRUE
                ))
        {
            ServerFree (pszDeviceClass);
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetID_epilog;
        }


        {
            ULONG_PTR   dwCallWidgetHold = 0,
                        dwLineWidgetHold = 0;
            DWORD       dwDeviceIDHold = 0;


            switch (pParams->dwSelect)
            {
            case LINECALLSELECT_ADDRESS:

                dwDeviceIDHold = pParams->dwAddressID;
                //fall through

            case LINECALLSELECT_LINE:

                dwLineWidgetHold = hdWidget;
                break;

            case LINECALLSELECT_DEVICEID:

                // this is the mapped id.
                dwDeviceIDHold = DWORD_CAST(hdWidget,__FILE__,__LINE__);
                break;

            case LINECALLSELECT_CALL:

                dwCallWidgetHold = hdWidget;
                break;

            default:

                break;
            }

            if ((pParams->lResult = CallSP7(
                     pfnTSPI_lineGetID,
                     "lineGetID",
                     SP_FUNC_SYNC,
                     (ULONG_PTR) dwLineWidgetHold,
                     (DWORD) dwDeviceIDHold,
                     (ULONG_PTR) dwCallWidgetHold,
                     (DWORD) pParams->dwSelect,
                     (ULONG_PTR) pID,
                     (ULONG_PTR) pszDeviceClass,
                     (ULONG_PTR) (IS_REMOTE_CLIENT (ptClient) ?
                         (HANDLE) -1 : ptClient->hProcess)

                     )) == 0)
            {

#if TELE_SERVER
                //
                // If
                //     this is a server &
                //     client doesn't have admin privileges &
                //     the specified device class == "tapi/phone" &
                //     the dwUsedSize indicates that a phone id was
                //         (likely) copied to the buffer
                // then
                //     try to map the retrieved phone device id back
                //     to one that makes sense to the client (and
                //     fail the request if there's no mapping)
                //

                if (IS_REMOTE_CLIENT(ptClient)  &&
                    (_wcsicmp (pszDeviceClass, L"tapi/phone") == 0) &&
                    !IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR) &&
                    (pID->dwUsedSize >= (sizeof (*pID) + sizeof (DWORD))))
                {
                    DWORD   i;
                    LPDWORD pdwPhoneID = (LPDWORD)
                                (((LPBYTE) pID) + pID->dwStringOffset);


                    for (i = 0; i < ptClient->dwPhoneDevices; i++)
                    {
                        if (*pdwPhoneID == ptClient->pPhoneDevices[i])
                        {
                            *pdwPhoneID = i;
                            break;
                        }
                    }

                    if (i >= ptClient->dwPhoneDevices)
                    {
                        pParams->lResult = LINEERR_OPERATIONFAILED;
                    }
                }
#endif

                //
                // Indicate offset & how many bytes of data we're passing back
                //

                pParams->dwDeviceIDOffset = 0;

                *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pID->dwUsedSize;
            }

        }

        ServerFree (pszDeviceClass);
    }

LGetID_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetID"
        );
}


void
WINAPI
LGetLineDevStatus(
    PTCLIENT                        ptClient,
    PLINEGETLINEDEVSTATUS_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineGetLineDevStatus;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwLineDevStatusTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEGETLINEDEVSTATUS,    // provider func index
            &pfnTSPI_lineGetLineDevStatus,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetLineDevStatus"          // func name

            )) == 0)
    {
        DWORD           dwAPIVersion, dwSPIVersion, dwTotalSize,
                        dwFixedSizeClient, dwFixedSizeSP, dwNumOpens,
                        dwOpenMediaModes;
        PTLINE          ptLine;
        LPLINEDEVSTATUS pDevStatus = (LPLINEDEVSTATUS) pDataBuf,
                        pDevStatus2 = (LPLINEDEVSTATUS) NULL;


        //
        // Safely retrieve the API & SPI versions, also some other info
        //

        try
        {
            dwAPIVersion = ptLineClient->dwAPIVersion;

            ptLine = ptLineClient->ptLine;

            dwSPIVersion = ptLine->dwSPIVersion;

            dwNumOpens       = ptLine->dwNumOpens;
            dwOpenMediaModes = ptLine->dwOpenMediaModes;

            if (ptLineClient->dwKey != TLINECLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LGetLineDevStatus_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LGetLineDevStatus_epilog;
        }


        //
        // Determine the fixed size of the structure for the specified API
        // version, verify client's buffer is big enough
        //

        dwTotalSize = pParams->dwLineDevStatusTotalSize;

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeClient = 76;   // 19 * sizeof (DWORD)
            break;


        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeClient = sizeof (LINEDEVSTATUS);
            break;
        }

        if (dwTotalSize < dwFixedSizeClient)
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LGetLineDevStatus_epilog;
        }


        //
        // Determine the fixed size of the structure expected by the SP
        //

        switch (dwSPIVersion)
        {
        case TAPI_VERSION1_0:
        case TAPI_VERSION1_4:

            dwFixedSizeSP = 76;   // 19 * sizeof (DWORD)
            break;

        default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

            dwFixedSizeSP = sizeof (LINEDEVSTATUS);
            break;
        }


        //
        // If the client's buffer is < the fixed size of that expected by
        // the SP (client is lower version than SP) then allocate an
        // intermediate buffer
        //

        if (dwTotalSize < dwFixedSizeSP)
        {
            if (!(pDevStatus2 = ServerAlloc (dwFixedSizeSP)))
            {
                pParams->lResult = LINEERR_NOMEM;
                goto LGetLineDevStatus_epilog;
            }

            pDevStatus  = pDevStatus2;
            dwTotalSize = dwFixedSizeSP;
        }


        InitTapiStruct(
            pDevStatus,
            dwTotalSize,
            dwFixedSizeSP,
            (pDevStatus2 == NULL ? TRUE : FALSE)
            );

        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineGetLineDevStatus,
                "lineGetLineDevStatus",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) pDevStatus

                )) == 0)
        {
            //
            // Add the fields we're responsible for
            //

            pDevStatus->dwNumOpens       = dwNumOpens;
            pDevStatus->dwOpenMediaModes = dwOpenMediaModes;


            if (dwAPIVersion >= TAPI_VERSION2_0)
            {
                DWORD           dwAppInfoTotalSize, dwNumOpens, dwXxxOffset, i;
                TPOINTERLIST    clientList, *pClientList = &clientList;
                LPLINEAPPINFO   pAppInfo;


                //
                // Reset the num opens to 0 in case we return prior to
                // filling in the app info list (so tapi32.dll doesn't
                // blow up trying to do unicode->ascii conversion on
                // bad data)
                //

                pDevStatus->dwNumOpens = 0;


                //
                // Retrieve the list of line clients & determine how big
                // of a buffer we need to hold all the related app info
                // data.  Do it safely in case one of the widgets is
                // destroyed while we're reading it's data.
                //

                if (GetLineClientListFromLine (ptLine, &pClientList) != 0)
                {
                    goto LGetLineDevStatus_copyTmpBuffer;
                }

                dwAppInfoTotalSize = pClientList->dwNumUsedEntries *
                    sizeof (LINEAPPINFO);

                for (i = 0; i < pClientList->dwNumUsedEntries; i++)
                {
                    PTLINECLIENT    ptLineClient = (PTLINECLIENT)
                                        pClientList->aEntries[i];

                    try
                    {
                        DWORD   d;


                        d = ptLineClient->ptClient->dwComputerNameSize;

                        d += ptLineClient->ptClient->dwUserNameSize;

                        // don't include preceding '"'

                        d += ptLineClient->ptLineApp->dwModuleNameSize -
                                sizeof (WCHAR);

                        d += ptLineClient->ptLineApp->dwFriendlyNameSize;

                        if (ptLineClient->dwKey == TLINECLIENT_KEY)
                        {
                            dwAppInfoTotalSize += d;
                        }
                        else
                        {
                            pClientList->aEntries[i] = 0;
                        }
                    }
                    myexcept
                    {
                        pClientList->aEntries[i] = 0;
                    }
                }

                dwAppInfoTotalSize += 3; // add 3 to guarantee DWORD alignment

                pDevStatus->dwNeededSize += dwAppInfoTotalSize;


                //
                // Check to see if there's enough room in the app buffer
                // for all the app info data
                //

                if ((pDevStatus->dwTotalSize - pDevStatus->dwUsedSize) <
                        dwAppInfoTotalSize)
                {
                    goto LGetLineDevStatus_freeClientList;
                }

                //
                // Now figure out where the app info goes & safely fill
                // it in
                //

                pDevStatus->dwAppInfoSize = pClientList->dwNumUsedEntries *
                    sizeof (LINEAPPINFO);

                pDevStatus->dwAppInfoOffset = (pDevStatus->dwUsedSize + 3) &
                    0xfffffffc;

                pDevStatus->dwUsedSize += dwAppInfoTotalSize;

                pAppInfo = (LPLINEAPPINFO) (((LPBYTE) pDevStatus) +
                    pDevStatus->dwAppInfoOffset);

                dwXxxOffset = pDevStatus->dwAppInfoSize +
                    pDevStatus->dwAppInfoOffset;

                dwNumOpens = 0;

                for (i = 0; i < pClientList->dwNumUsedEntries; i++)
                {
                    PTLINECLIENT    ptLineClient = (PTLINECLIENT)
                                        pClientList->aEntries[i];


                    if (ptLineClient == NULL)
                    {
                        continue;
                    }

                    try
                    {
                        DWORD       d = dwXxxOffset;
                        PTCLIENT    ptClient  = ptLineClient->ptClient;
                        PTLINEAPP   ptLineApp = ptLineClient->ptLineApp;


                        pAppInfo->dwMachineNameSize =
                            ptClient->dwComputerNameSize;
                        pAppInfo->dwUserNameSize =
                            ptClient->dwUserNameSize;

                        if (ptClient->dwKey != TCLIENT_KEY)
                        {
                            continue;
                        }

                        pAppInfo->dwModuleFilenameSize =
                            ptLineApp->dwModuleNameSize - sizeof (WCHAR);
                        pAppInfo->dwFriendlyNameSize =
                            ptLineApp->dwFriendlyNameSize;

                        if (ptLineApp->dwKey != TLINEAPP_KEY)
                        {
                            continue;
                        }

                        pAppInfo->dwMachineNameOffset = d;

                        if (pAppInfo->dwMachineNameSize)
                        {
                            wcsncpy(
                                (LPWSTR) (((LPBYTE) pDevStatus) + d),
                                ptClient->pszComputerName,
                                pAppInfo->dwMachineNameSize / sizeof (WCHAR)
                                );

                            d += pAppInfo->dwMachineNameSize;
                        }

                        pAppInfo->dwUserNameOffset = d;

                        if (pAppInfo->dwUserNameSize)
                        {
                            wcsncpy(
                                (LPWSTR) (((LPBYTE) pDevStatus) + d),
                                ptClient->pszUserName,
                                pAppInfo->dwUserNameSize / sizeof (WCHAR)
                                );

                            d += pAppInfo->dwUserNameSize;
                        }

                        pAppInfo->dwModuleFilenameOffset = d;

                        if (pAppInfo->dwModuleFilenameSize)
                        {
                            // don't include preceding '"'

                            wcsncpy(
                                (LPWSTR) (((LPBYTE) pDevStatus) + d),
                                &ptLineApp->pszModuleName[1],
                                pAppInfo->dwModuleFilenameSize / sizeof (WCHAR)
                                );

                            d += pAppInfo->dwModuleFilenameSize;
                        }

                        pAppInfo->dwFriendlyNameOffset = d;

                        if (pAppInfo->dwFriendlyNameSize)
                        {
                            wcsncpy(
                                (LPWSTR) (((LPBYTE) pDevStatus) + d),
                                ptLineApp->pszFriendlyName,
                                pAppInfo->dwFriendlyNameSize / sizeof (WCHAR)
                                );

                            d += pAppInfo->dwFriendlyNameSize;
                        }

                        pAppInfo->dwMediaModes = ptLineClient->dwMediaModes;
                        pAppInfo->dwAddressID  = ptLineClient->dwAddressID;


                        //
                        // Finally, make sure the tLineClient is still good
                        // so we know all the info above  is kosher, &
                        // if so inc the appropriate vars
                        //

                        if (ptLineClient->dwKey == TLINECLIENT_KEY)
                        {
                            pAppInfo++;
                            dwNumOpens++;
                            dwXxxOffset = d;
                        }
                    }
                    myexcept
                    {
                        // do nothing, just continue to loop
                    }
                }

                pDevStatus->dwNumOpens    = dwNumOpens;
                pDevStatus->dwAppInfoSize = dwNumOpens * sizeof (LINEAPPINFO);

LGetLineDevStatus_freeClientList:

                if (pClientList !=  &clientList)
                {
                    ServerFree (pClientList);
                }
            }


            //
            // Munge fields where appropriate for old apps (don't want to
            // pass back flags that they won't understand)
            //


            //
            // If an intermediate buffer was used then copy the bits back
            // to the the original buffer, & free the intermediate buffer.
            // Also reset the dwUsedSize field to the fixed size of the
            // structure for the specifed version, since any data in the
            // variable portion is garbage as far as the client is concerned.
            //

LGetLineDevStatus_copyTmpBuffer:

            if (pDevStatus == pDevStatus2)
            {
                pDevStatus = (LPLINEDEVSTATUS) pDataBuf;

                CopyMemory (pDevStatus, pDevStatus2, dwFixedSizeClient);

                ServerFree (pDevStatus2);

                pDevStatus->dwTotalSize = pParams->dwLineDevStatusTotalSize;
                pDevStatus->dwUsedSize  = dwFixedSizeClient;
            }


            //
            // Indicate the API version of the hLine so tapi32.dll knows
            // which strings to munge from ascii to unicode
            //

            pParams->dwAPIVersion = dwAPIVersion;


            //
            // Indicate the offset & how many bytes of data we're passing back
            //

            pParams->dwLineDevStatusOffset = 0;

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                pDevStatus->dwUsedSize;
        }
    }

LGetLineDevStatus_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetLineDevStatus"
        );
}


void
WINAPI
LGetNewCalls(
    PTCLIENT                ptClient,
    PLINEGETNEWCALLS_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    LONG            lResult = 0;
    DWORD           dwTotalSize = pParams->dwCallListTotalSize, dwAddressID,
                    dwNumNewCalls, i, j, dwSelect = pParams->dwSelect;
    PTLINE          ptLine;
    HDRVLINE        hdLine;
    PTLINECLIENT    ptLineClient;
    TPOINTERLIST    callList, *pCallList = &callList;
    LPLINECALLLIST  pAppCallList = (LPLINECALLLIST) pDataBuf;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwCallListTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    //
    // Verify params
    //

    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto LGetNewCalls_return;
    }

    if (dwSelect == LINECALLSELECT_ADDRESS)
    {
        dwAddressID = pParams->dwAddressID;
    }
    else if (dwSelect != LINECALLSELECT_LINE)
    {
        pParams->lResult = LINEERR_INVALCALLSELECT;
        goto LGetNewCalls_return;
    }

    if (dwTotalSize < sizeof (LINECALLLIST))
    {
        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
        goto LGetNewCalls_return;
    }

    if (!(ptLineClient = ReferenceObject(
            ghHandleTable,
            pParams->hLine,
            TLINECLIENT_KEY
            )))
    {
        pParams->lResult = LINEERR_INVALLINEHANDLE;
        goto LGetNewCalls_return;
    }

    if (ptLineClient->ptClient != ptClient)
    {
        pParams->lResult = LINEERR_INVALLINEHANDLE;
        goto LGetNewCalls_dereference;
    }

    ptLine = ptLineClient->ptLine;


    //
    // HACK ALERT!
    //
    // GetNewCalls did not work on remote lines in tapi 2.1,
    // win98 gold, or nt4sp4.
    //
    // The way we get it to work here for remote lines is to do a
    // TSPI_lineGetID down to remotesp, specifying the device class
    // of "GetNewCalls", and passing it the pointer to our real
    // LineEventProc, not the LineEventProcSP which queues msgs,
    // so it can process LINE_CALLSTATE msgs inline (this allows
    // the initial/requisite monitor handles to be created, etc,
    // before we might do so below).
    //

    try
    {
        hdLine = (ptLine->ptProvider == pRemoteSP ? ptLine->hdLine : 0);

        if (ptLine->dwKey != TLINE_KEY)
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LGetNewCalls_dereference;
        }
    }
    myexcept
    {
        pParams->lResult = LINEERR_INVALLINEHANDLE;
        goto LGetNewCalls_dereference;
    }

    if (hdLine  &&  pRemoteSP->apfn[SP_LINEGETID])
    {
        CallSP7(
            pRemoteSP->apfn[SP_LINEGETID],
            "lineGetID(GetNewCalls)",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdLine,
            (DWORD) dwAddressID,
            (ULONG_PTR) 0,              // hdCall
            (DWORD) dwSelect,
            (ULONG_PTR) 0,              // lpDeviceID
            (ULONG_PTR) L"GetNewCalls", // lpszDeviceClass
            (ULONG_PTR) LineEventProc   // hTargetProcess
            );
    }


    //
    // Get list of tCalls on the tLine
    //

    if ((lResult = GetCallListFromLine (ptLine, &pCallList)) != 0)
    {
        pParams->lResult = lResult;
        goto LGetNewCalls_dereference;
    }


    //
    // Assume worst case scenario- that we have to create a new call
    // client for each tCall on the tLine- and make sure the app's call
    // list is large enough to hold them all
    //

    pAppCallList->dwTotalSize = dwTotalSize;

    if (dwTotalSize < (sizeof (LINECALLLIST) +
            pCallList->dwNumUsedEntries * sizeof(HCALL)))
    {
        pAppCallList->dwNeededSize = sizeof (LINECALLLIST) +
            pCallList->dwNumUsedEntries * sizeof(HCALL);

        pAppCallList->dwUsedSize = sizeof (LINECALLLIST);

        FillMemory (&pAppCallList->dwCallsNumEntries, 3 * sizeof (DWORD), 0);

        goto LGetNewCalls_cleanup;
    }


    //
    // Check to see if there's a call client for the specified
    // line client for each of the calls on the line/address,
    // create one with monitor privilege if not
    //

    dwNumNewCalls = 0;

    for (i = 0; i < pCallList->dwNumUsedEntries; i++)
    {
        BOOL            bContinue = FALSE;
        PTCALL          ptCall = (PTCALL) pCallList->aEntries[i];
        TPOINTERLIST    callClientList, *pCallClientList = &callClientList;


        //
        // Check to see if the post-processing routine (for outgoing calls)
        // or the CALLSTATE msg handler in the LineEventProc (for incoming
        // calls) has already created the list of monitors for this tCall.
        //

        try
        {
            if (ptCall->bCreatedInitialMonitors == FALSE)
            {
                bContinue = TRUE;
            }
        }
        myexcept
        {
            bContinue = TRUE;
        }

        if (dwSelect == LINECALLSELECT_ADDRESS)
        {
            try
            {
                if (dwAddressID != ptCall->dwAddressID)
                {
                    bContinue = TRUE;
                }
            }
            myexcept
            {
                bContinue = TRUE;
            }
        }

        if (bContinue)
        {
            continue;
        }

        if (GetCallClientListFromCall (ptCall, &pCallClientList) != 0)
        {
            continue;
        }

        for (j = 0; j < pCallClientList->dwNumUsedEntries; j++)
        {
            try
            {
                if (((PTCALLCLIENT)(pCallClientList->aEntries[j]))
                        ->ptLineClient == ptLineClient)
                {
                    break;
                }
            }
            myexcept
            {
                // just continue
            }
         }

         if (j == pCallClientList->dwNumUsedEntries)
         {
            PTCALLCLIENT    pNewCallClient;


            //
            // (Similar remotesp hack in CreateCallMonitors)
            //
            // NOTE: If client is remote(sp) then create the call client
            //       with OWNER privileges so client can still do everything.
            //       The remote tapisrv will deal with all the remote
            //       privilege issues.
            //
            //       This scheme might end up confusing other apps since
            //       a LINE_CALLINFO\NUMOWNERINCR (not NUMMONITORS) msgs
            //       get sent, but it certainly beats what we had in tapi 2.1 -
            //       that is, if a remote client did not initially have owner
            //       privilege then it could *never* get owner privilege.
            //

            if ((lResult = CreatetCallClient(
                    ptCall,
                    ptLineClient,
                    (IS_REMOTE_CLIENT (ptClient) ?
                        LINECALLPRIVILEGE_OWNER : LINECALLPRIVILEGE_MONITOR),
                    TRUE,
                    TRUE,
                    &pNewCallClient,
                    FALSE

                    )) == 0)
            {
                try
                {
                    *(((LPHCALL)(pAppCallList + 1)) + dwNumNewCalls) =
                        pNewCallClient->hCall;
                }
                myexcept
                {
                    //
                    // If here the call got torn down, meaning the line
                    // is going down too
                    //

                    pParams->lResult = LINEERR_INVALLINEHANDLE;
                    i = 0xfffffffe;
                    break;
                }

                dwNumNewCalls++;
            }
            else
            {
                // specfied tCall might have been closed, not a fatal error
            }
        }

        if (pCallClientList != &callClientList)
        {
            ServerFree (pCallClientList);
        }
    }

    {
        DWORD   dwCallsSize = dwNumNewCalls * sizeof (HCALL);


        pAppCallList->dwUsedSize        =
        pAppCallList->dwNeededSize      = sizeof (LINECALLLIST) + dwCallsSize;

        pAppCallList->dwCallsNumEntries = dwNumNewCalls;
        pAppCallList->dwCallsSize       = dwCallsSize;
        pAppCallList->dwCallsOffset     = sizeof (LINECALLLIST);
    }

LGetNewCalls_cleanup:

    if (pCallList != &callList)
    {
        ServerFree (pCallList);
    }

    pParams->dwCallListOffset = 0;

    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pAppCallList->dwUsedSize;

LGetNewCalls_dereference:

    DereferenceObject (ghHandleTable, pParams->hLine, 1);

LGetNewCalls_return:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetNewCalls: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetNewCalls: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LGetNumAddressIDs(
    PTCLIENT                        ptClient,
    PLINEGETNUMADDRESSIDS_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetNumAddressIDs"          // func name

            )) == 0)
    {
        try
        {
            pParams->dwNumAddresses = ptLineClient->ptLine->dwNumAddresses;

            *pdwNumBytesReturned = sizeof (LINEGETNUMADDRESSIDS_PARAMS);
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetNumAddressIDs"
        );
}


void
WINAPI
LGetNumRings(
    PTCLIENT                ptClient,
    PLINEGETNUMRINGS_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_NONE,                    // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetNumRings"               // func name

            )) == 0)
    {
        DWORD           i, dwNumRings = 0xffffffff,
                        dwAddressID = pParams->dwAddressID;
        PTLINE          ptLine;
        TPOINTERLIST    lineClientList, *pLineClientList = &lineClientList;


        try
        {
            ptLine = ptLineClient->ptLine;

            if (dwAddressID >= ptLine->dwNumAddresses)
            {
                pParams->lResult = LINEERR_INVALADDRESSID;
                goto LGetNumRings_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LGetNumRings_epilog;
        }

        {
            LONG    lResult;


            if ((lResult = GetLineClientListFromLine(
                    ptLine,
                    &pLineClientList

                    )) != 0)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LGetNumRings_epilog;
            }
        }

        for (i = 0; i < pLineClientList->dwNumUsedEntries; i++)
        {
            ptLineClient = (PTLINECLIENT) pLineClientList->aEntries[i];

            try
            {
               if (ptLineClient->aNumRings == NULL)
               {
                   continue;
               }
               else if (ptLineClient->aNumRings[dwAddressID] < dwNumRings)
               {
                   DWORD    dwNumRingsTmp =
                                ptLineClient->aNumRings[dwAddressID];


                   if (ptLineClient->dwKey == TLINECLIENT_KEY)
                   {
                       dwNumRings = dwNumRingsTmp;
                   }
               }
            }
            myexcept
            {
                // just continue
            }
        }

        if (pLineClientList != &lineClientList)
        {
            ServerFree (pLineClientList);
        }

        pParams->dwNumRings = dwNumRings;

        *pdwNumBytesReturned = sizeof (LINEGETNUMRINGS_PARAMS);
    }

LGetNumRings_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetNumRings"
        );
}


void
WINAPI
LGetProviderList(
    PTCLIENT                    ptClient,
    PLINEGETPROVIDERLIST_PARAMS pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    DWORD   iNumProviders = 0, i;
    WCHAR   *bufw;
    DWORD   dwFixedSizeClient, dwTotalSize, dwNeededSize;
    LPBYTE  pVarData;
    LPLINEPROVIDERLIST  pProviderList;
    LPLINEPROVIDERENTRY pProviderEntry;

    HKEY hKey;
    DWORD dwDataSize;
    DWORD dwDataType;

#ifdef MEMPHIS
    DWORD   iNum16BitProviders;
#endif


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwProviderListTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    switch (pParams->dwAPIVersion)
    {
    case TAPI_VERSION1_0:
    case TAPI_VERSION1_4:
    case TAPI_VERSION2_0:
    case TAPI_VERSION2_1:
    case TAPI_VERSION2_2:
    case TAPI_VERSION3_0:
    case TAPI_VERSION_CURRENT:

        dwFixedSizeClient = sizeof (LINEPROVIDERLIST);
        break;

    default:

        pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
        goto LGetProviderList_epilog;
    }

    if ((dwTotalSize = pParams->dwProviderListTotalSize) < dwFixedSizeClient)
    {
        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
        goto LGetProviderList_epilog;
    }

    if (ERROR_SUCCESS ==
        RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        gszRegKeyProviders,
        0,
        KEY_ALL_ACCESS,
        &hKey
        ))
    {
        dwDataSize = sizeof(iNumProviders);
        iNumProviders = 0;
        RegQueryValueEx(
            hKey,
            gszNumProviders,
            0,
            &dwDataType,
            (LPBYTE)&iNumProviders,
            &dwDataSize
            );
    }


#ifdef MEMPHIS

    // add in the 16 bit providers

    iNum16BitProviders = GetPrivateProfileInt(
        "PROVIDERS",    //szProviders,
        gszNumProviders,
        0,
        "TELEPHON.INI"//szTelephonIni
        );

    LOG((TL_INFO, "Number of 16 bit providers %d", iNum16BitProviders));

#endif

#ifdef MEMPHIS
    dwNeededSize = dwFixedSizeClient +
        ((iNumProviders + iNum16BitProviders) * sizeof (LINEPROVIDERENTRY));
#else
    dwNeededSize = dwFixedSizeClient +
        (iNumProviders * sizeof (LINEPROVIDERENTRY));
#endif

    pProviderList = (LPLINEPROVIDERLIST) pDataBuf;

    pProviderEntry = (LPLINEPROVIDERENTRY) (pDataBuf + dwFixedSizeClient);

    pVarData = pDataBuf + dwNeededSize;

    bufw = ServerAlloc (MAX_PATH*sizeof(WCHAR));  // enough for complete path

    if ( !bufw )
    {
        pParams->lResult = LINEERR_NOMEM;
        goto LGetProviderList_epilog;
    }

    for (i = 0; i < iNumProviders; i++)
    {
        TCHAR   szProviderXxxN[32];
        DWORD   dwNameLen;
        DWORD   dwNewSize;

        wsprintf (szProviderXxxN, TEXT("%s%d"), gszProviderFilename, i);

        dwNameLen = MAX_PATH*sizeof(WCHAR);

        if (TAPIRegQueryValueExW(
                hKey,
                szProviderXxxN,
                0,
                &dwDataType,
                (LPBYTE)bufw,
                &dwNameLen

                ) != ERROR_SUCCESS)
        {
            bufw[0] = 0;
        }

        dwNewSize = (lstrlenW(bufw)+1) * sizeof(WCHAR);

        dwNeededSize += dwNewSize;

        if (dwTotalSize >= dwNeededSize)
        {
            wsprintf(szProviderXxxN, TEXT("%s%d"), gszProviderID, i);

            dwDataSize = sizeof(pProviderEntry->dwPermanentProviderID);

            pProviderEntry->dwPermanentProviderID = 0;

            RegQueryValueEx(
                hKey,
                szProviderXxxN,
                0,
                &dwDataType,
                (LPBYTE)&(pProviderEntry->dwPermanentProviderID),
                &dwDataSize
                );

            pProviderEntry->dwProviderFilenameSize   = dwNewSize;
            pProviderEntry->dwProviderFilenameOffset =
                  (DWORD) (pVarData - ((LPBYTE) pProviderList));

            CopyMemory (pVarData, bufw, dwNewSize);

            pVarData += dwNewSize;

            pProviderEntry++;
        }
    }

#ifdef MEMPHIS

    for (i = 0; i < iNum16BitProviders; i++)
    {
        CHAR   szProviderXxxN[32];
        DWORD   dwNameLen;
        DWORD   dwNewSize;


        wsprintf(szProviderXxxN, "%s%d", gszProviderFilename, i);

        dwNameLen = MAX_PATH;

        dwNameLen = 1 + GetPrivateProfileString(
            "PROVIDERS",    //gszProviders,
            szProviderXxxN,
            "",
            buf,
            dwNameLen,
            "TELEPHON.INI"//szTelephonIni
            );

        dwNewSize = sizeof(WCHAR) * (MultiByteToWideChar(
            GetACP(),
            MB_PRECOMPOSED,
            (LPCSTR)buf,
            dwNameLen,
            bufw,
            MAX_PATH - 1
            ));

        dwNeededSize += dwNewSize;

        if (dwTotalSize >= dwNeededSize)
        {
            wsprintf(szProviderXxxN, "%s%d", gszProviderID, i);

            pProviderEntry->dwPermanentProviderID = GetPrivateProfileInt(
                "PROVIDERS",
                szProviderXxxN,
                0,
                "TELEPHON.INI"
                );

            pProviderEntry->dwProviderFilenameSize   = dwNewSize;
            pProviderEntry->dwProviderFilenameOffset =
                  pVarData - ((LPBYTE) pProviderList);

            CopyMemory (pVarData, bufw, dwNewSize);

            pVarData += dwNewSize;

            pProviderEntry++;
        }
    }

#endif

    ServerFree (bufw);

    pProviderList->dwTotalSize  = dwTotalSize;
    pProviderList->dwNeededSize = dwNeededSize;

    if (dwTotalSize >= dwNeededSize)
    {
        pProviderList->dwUsedSize           = dwNeededSize;
#ifdef MEMPHIS
        pProviderList->dwNumProviders       = (DWORD) iNumProviders +
            iNum16BitProviders;
        pProviderList->dwProviderListSize   = (DWORD)
            ((iNumProviders + iNum16BitProviders) * sizeof(LINEPROVIDERENTRY));
#else
        pProviderList->dwNumProviders       = (DWORD) iNumProviders;
        pProviderList->dwProviderListSize   =
            (DWORD) (iNumProviders * sizeof (LINEPROVIDERENTRY));
#endif
        pProviderList->dwProviderListOffset = dwFixedSizeClient;
    }
    else
    {
        pProviderList->dwUsedSize           = dwFixedSizeClient;
        pProviderList->dwNumProviders       =
        pProviderList->dwProviderListSize   =
        pProviderList->dwProviderListOffset = 0;
    }

    pParams->dwProviderListOffset = 0;

    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pProviderList->dwUsedSize;

    RegCloseKey (hKey);


LGetProviderList_epilog:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetProviderList: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetProviderList: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LGetProxyStatus(
    PTCLIENT                        ptClient,
    PLINEGETPROXYSTATUS_PARAMS      pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwProxyStatusTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    switch (pParams->dwAppAPIVersion)
    {
    case TAPI_VERSION1_0:
    case TAPI_VERSION1_4:
    case TAPI_VERSION2_0:
    case TAPI_VERSION2_1:
    case TAPI_VERSION2_2:
    case TAPI_VERSION3_0:
    case TAPI_VERSION_CURRENT:
        break;

    default:

        pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
        return;
    }

    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            (LPVOID) &dwDeviceID,       // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "GetProxyStatus"            // func name

            )) == 0)
    {
        DWORD     dwAPIVersion = 0;
        DWORD     dwTotalSize = 0;
        DWORD     dwFixedSizeClient = 0;


        //
        // Determine the fixed size of the structure for the
        // specified API version
        //

        dwAPIVersion = pParams->dwAppAPIVersion;

        dwFixedSizeClient = sizeof (LINEPROXYREQUESTLIST);


        //
        // Will it fit ?
        //

        dwTotalSize = pParams->dwProxyStatusTotalSize;

        if (dwTotalSize >= dwFixedSizeClient)
        {
            //
            // OK, buffer's large enough for fixed part.
            //
            // Is this line remote ?
            //

            if (pLookupEntry->bRemote)
            {
                LPLINEPROXYREQUESTLIST  pProxyReqList =
                                            (LPLINEPROXYREQUESTLIST) pDataBuf;

                InitTapiStruct(
                    pProxyReqList,
                    dwTotalSize,
                    dwFixedSizeClient,
                    TRUE
                    );

                pParams->lResult = CallSP3(
                    pRemoteSP->apfn[SP_LINEGETPROXYSTATUS],
                    "LineGetProxyStatus",
                    SP_FUNC_SYNC,
                    (DWORD) dwDeviceID,
                    (DWORD) dwAPIVersion,
                    (ULONG_PTR) pProxyReqList
                    );


                //
                // Set the return values
                //

                pParams->dwAPIVersion = dwAPIVersion;
                pParams->dwProxyStatusOffset = 0;
                *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                    pProxyReqList->dwUsedSize;
            }
            else  // it's a local line
            {
                DWORD                   i;
                DWORD                   dwNeededSize;
                LPDWORD                 pListEntry;
                PTLINE                  ptLine = pLookupEntry->ptLine;
                LPLINEPROXYREQUESTLIST  pProxyReqList =
                                            (LPLINEPROXYREQUESTLIST) pDataBuf;


                if (ptLine != NULL)
                {
                    //
                    // how much space is needed for the list ?
                    //

                    dwNeededSize = sizeof (LINEPROXYREQUESTLIST);
                    pProxyReqList->dwNumEntries = 0;

                    for(
                        i = LINEPROXYREQUEST_SETAGENTGROUP;
                        i <= LINEPROXYREQUEST_LASTVALUE;
                        i++
                        )
                    {
                        try  // Just in case the line gets closed
                        {
                            if (ptLine->apProxys[i] != NULL)
                            {
                                //
                                // So there's a proxy associated with
                                // this proxy request type, add on space
                                // requirement for it's list entry
                                //

                                dwNeededSize += sizeof(DWORD);
                            }
                        }
                        myexcept
                        {
                            pParams->lResult = LINEERR_OPERATIONUNAVAIL;
                            goto LGetProxyStatus_epilog;
                        }
                    }


                    //
                    // Will it fit ?
                    //

                    if (dwTotalSize >= dwNeededSize)
                    {
                        //
                        // enough room , so fill in list
                        //

                        pProxyReqList->dwListSize = 0;
                        pProxyReqList->dwNumEntries = 0;

                        pProxyReqList->dwListOffset =
                            sizeof(LINEPROXYREQUESTLIST);

                        pListEntry = (LPDWORD) ((BYTE *) pProxyReqList +
                            sizeof (LINEPROXYREQUESTLIST));

                        for(
                            i = LINEPROXYREQUEST_SETAGENTGROUP;
                            i <= LINEPROXYREQUEST_LASTVALUE;
                            i++
                            )
                        {
                            try  // Just in case the line gets closed
                            {
                                if (ptLine->apProxys[i] != NULL)
                                {
                                    //
                                    // So there's a proxy associated with
                                    // this proxy request type, add on space
                                    // requirement for entry to list size
                                    //

                                    pProxyReqList->dwListSize += sizeof(DWORD);


                                    //
                                    // Incr number of entries in the list
                                    //

                                    pProxyReqList->dwNumEntries++;


                                    //
                                    // Proxy reqest type is ..
                                    //

                                    *pListEntry++ = i;
                                }
                            }
                            myexcept
                            {
                                pParams->lResult = LINEERR_OPERATIONUNAVAIL;
                                goto LGetProxyStatus_epilog;
                            }
                        }


                        //
                        // set the total, used & need sizes
                        //

                        pProxyReqList->dwTotalSize  = dwTotalSize;
                        pProxyReqList->dwUsedSize   = dwNeededSize;
                        pProxyReqList->dwNeededSize = dwNeededSize;


                        //
                        // Set the return values
                        //

                        pParams->dwAPIVersion = dwAPIVersion;
                        pParams->dwProxyStatusOffset = 0;
                        *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                            pProxyReqList->dwUsedSize;
                    }
                    else // Buffer too small for the list, so return an error
                    {
                        pParams->lResult = LINEERR_STRUCTURETOOSMALL;
                    }
                }
                else // (ptLine == NULL) - No line open, so no proxies !
                {
                    pParams->lResult = LINEERR_OPERATIONFAILED;
                    LOG((TL_ERROR, "lineGetProxyStatus - no line open"));
                }
            } // endif bRemote
        }
        else // Buffer too small
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
        }

    } // endif LINEPROLOG

LGetProxyStatus_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "GetLineProxyStatus"
        );
}


void
WINAPI
LGetQueueInfo(
    PTCLIENT                    ptClient,
    PLINEGETQUEUEINFO_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    LGetAgentWithoutAddressIDXxx(
        ptClient,
        (PLINEGETAGENTINFO_PARAMS) pParams,
        dwParamsBufferSize,
        LINEPROXYREQUEST_GETQUEUEINFO,
        SP_LINEGETQUEUEINFO,
        sizeof (LINEQUEUEINFO)
#if DBG
        ,
        "GetQueueInfo"
#endif
        );
}

void
WINAPI
LGetQueueList(
    PTCLIENT                    ptClient,
    PLINEGETQUEUELIST_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwGroupIDSize,
            pParams->dwGroupIDOffset,
            sizeof(DWORD),
            "LGetQueueList",
            "pParams->GroupID"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "GetQueueList"          // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID,
                        dwTotalSize = pParams->dwQueueListTotalSize;
        PTLINECLIENT    pProxy;


        if (dwTotalSize < sizeof( LINEQUEUELIST ) )
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LGetQueueList_epilog;
        }

        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_GETQUEUELIST,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LGetQueueList_epilog;
        }


        //
        // Save the client's buf ptr & post processing proc ptr
        //

        pAsyncRequestInfo->dwParam1 = pParams->hpQueueList;
        pAsyncRequestInfo->dwParam2 = dwTotalSize;
        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_GETQUEUELIST,
                    sizeof(GUID) + sizeof(DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LGetQueueList_epilog;
            }

            CopyMemory(
                &(pProxyRequestWrapper->ProxyRequest.GetQueueList.GroupID),
                pDataBuf + pParams->dwGroupIDOffset,
                sizeof(GUID)
                );

            pProxyRequestWrapper->ProxyRequest.GetQueueList.
                QueueList.dwTotalSize = dwTotalSize;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LGetQueueList_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            LPBYTE                  pBuf;
            LPLINEAGENTINFO         pAgentInfo;


            //
            // Alloc a shadow buf that the SP can use until it completes this
            // request.  Make sure there's enough extra space in the buf for
            // an ASYNCEVENTMSG header so we don't have to alloc yet another
            // buf in the post processing proc when preparing the completion
            // msg to send to the client, and that the msg is 64-bit aligned.
            //

            if (!(pBuf = ServerAlloc(
                    sizeof (ASYNCEVENTMSG) + ((dwTotalSize + 7) & 0xfffffff8)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LGetQueueList_epilog;
            }

            pAsyncRequestInfo->pfnPostProcess =
                LGetAgentXxx_PostProcess;

            pAsyncRequestInfo->dwParam3 = (ULONG_PTR) pBuf;

            pAgentInfo = (LPLINEAGENTINFO)
                (pBuf + sizeof (ASYNCEVENTMSG));

            pAgentInfo->dwTotalSize = dwTotalSize;

            pParams->lResult = CallSP4(
                pRemoteSP->apfn[SP_LINEGETQUEUELIST],
                "LineGetQueueList",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) (pDataBuf + pParams->dwGroupIDOffset),
                (ULONG_PTR) pAgentInfo
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LGetQueueList_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "GetQueueList"
        );
}


void
WINAPI
LGetRequest(
    PTCLIENT                ptClient,
    PLINEGETREQUEST_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    PTLINEAPP           ptLineApp;
    PTREQUESTMAKECALL   pRequestMakeCall;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (dwParamsBufferSize < sizeof (LINEREQMAKECALLW))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((ptLineApp = WaitForExclusiveLineAppAccess(
            pParams->hLineApp,
            ptClient
            )))
    {
        if (pParams->dwRequestMode == LINEREQUESTMODE_MAKECALL)
        {
            if (!ptLineApp->pRequestRecipient)
            {
                pParams->lResult = LINEERR_NOTREGISTERED;
                goto LGetRequest_releaseMutex;
            }

            EnterCriticalSection (&gPriorityListCritSec);

            // note: if here guaranteed to be >=1 reqRecip obj in global list

            if (_wcsicmp(
                    ptLineApp->pszModuleName,
                    TapiGlobals.pHighestPriorityRequestRecipient->
                        ptLineApp->pszModuleName

                    ) == 0)
            {
                if ((pRequestMakeCall = TapiGlobals.pRequestMakeCallList))
                {
                    CopyMemory(
                        pDataBuf,
                        &pRequestMakeCall->LineReqMakeCall,
                        sizeof (LINEREQMAKECALLW)
                        );

                    LOG((TL_INFO, "Getting request:  0x%p", pRequestMakeCall));

                    LOG((TL_INFO, "   DestAddress: [%ls]",
                          pRequestMakeCall->LineReqMakeCall.szDestAddress));

                    LOG((TL_INFO, "   AppName: [%ls]",
                          pRequestMakeCall->LineReqMakeCall.szAppName));

                    LOG((TL_INFO, "   CalledParty: [%ls]",
                          pRequestMakeCall->LineReqMakeCall.szCalledParty));

                    LOG((TL_INFO, "   Comment: [%ls]",
                          pRequestMakeCall->LineReqMakeCall.szComment));

                    pParams->dwRequestBufferOffset = 0;
                    pParams->dwSize = sizeof (LINEREQMAKECALLW);

                    *pdwNumBytesReturned = sizeof (TAPI32_MSG) +
                        sizeof (LINEREQMAKECALLW);

                    if (!(TapiGlobals.pRequestMakeCallList =
                            pRequestMakeCall->pNext))
                    {
                        TapiGlobals.pRequestMakeCallListEnd = NULL;
                    }

                    ServerFree (pRequestMakeCall);
                }
                else
                {
                    pParams->lResult = LINEERR_NOREQUEST;
                }
            }
            else
            {
                pParams->lResult = LINEERR_NOREQUEST;
            }

            LeaveCriticalSection (&gPriorityListCritSec);
        }
        else if (pParams->dwRequestMode == LINEREQUESTMODE_MEDIACALL)
        {
            pParams->lResult = (ptLineApp->bReqMediaCallRecipient ?
                LINEERR_NOREQUEST : LINEERR_NOTREGISTERED);
        }
        else
        {
            pParams->lResult = LINEERR_INVALREQUESTMODE;
        }

LGetRequest_releaseMutex:

        UNLOCKTLINEAPP(ptLineApp);
    }
    else
    {
        pParams->lResult = (TapiGlobals.dwNumLineInits == 0 ?
            LINEERR_UNINITIALIZED : LINEERR_INVALAPPHANDLE);
    }

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetRequest: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetRequest: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LGetStatusMessages(
    PTCLIENT                        ptClient,
    PLINEGETSTATUSMESSAGES_PARAMS   pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    PTLINECLIENT    ptLineClient;


    if ((ptLineClient = ReferenceObject(
            ghHandleTable,
            pParams->hLine,
            TLINECLIENT_KEY
            )))
    {
        if (ptLineClient->ptClient == ptClient)
        {
            pParams->dwLineStates    = ptLineClient->dwLineStates;
            pParams->dwAddressStates = ptLineClient->dwAddressStates;

            *pdwNumBytesReturned = sizeof (LINEGETSTATUSMESSAGES_PARAMS);
        }
        else
        {
            pParams->lResult = (TapiGlobals.dwNumLineInits ?
                LINEERR_INVALLINEHANDLE : LINEERR_UNINITIALIZED);
        }

        DereferenceObject (ghHandleTable, pParams->hLine, 1);
    }
    else
    {
        pParams->lResult = (TapiGlobals.dwNumLineInits ?
            LINEERR_INVALLINEHANDLE : LINEERR_UNINITIALIZED);
    }

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineGetStatusMessages: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineGetStatusMessages: exit, result=x%x",
            pParams->lResult
            ));
#endif
}


void
WINAPI
LHandoff(
    PTCLIENT            ptClient,
    PLINEHANDOFF_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    DWORD           objectToDereference;
    PTCALLCLIENT    ptCallClientApp;
    TPOINTERLIST    xxxClientList, *pXxxClientList = &xxxClientList;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwFileNameOffset != TAPI_NO_DATA)  &&

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwFileNameOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClientApp,           // context
            "Handoff"                   // func name

            )) == 0)
    {
        LONG            lResult;
        DWORD           dwAPIVersion, dwValidMediaModes, i,
                        dwMediaMode = pParams->dwMediaMode;
        WCHAR          *pszFileName = (pParams->dwFileNameOffset==TAPI_NO_DATA
                            ? NULL :
                            (PWSTR)(pDataBuf + pParams->dwFileNameOffset));
        PTLINE          ptLine;
        PTCALL          ptCall;
        PTLINECLIENT    ptLineClientApp, ptLineClientTarget, ptLineClientTmp;


        //
        // Safely retrieve all the object pointers needed below, then get
        // a list of line clients
        //

        try
        {
            ptCall          = ptCallClientApp->ptCall;
            ptLineClientApp = ptCallClientApp->ptLineClient;
            ptLine          = ptLineClientApp->ptLine;
            dwAPIVersion    = ptLineClientApp->dwAPIVersion;
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LHandoff_epilog;
        }

        if ((lResult = GetLineClientListFromLine (ptLine, &pXxxClientList)))
        {
            pParams->lResult = lResult;
            goto LHandoff_epilog;
        }

        if (pszFileName)
        {
            //
            // "Directed" handoff
            //
            // Walk thru the list of clients on this line & find the oldest
            // one (that's an owner) with an app name that matches the
            // specified app name
            //
            // Note: It's possible that a target app who opened the line
            // with OWNER privilege for only DATAMODEM calls will be a
            // target of a directed handoff for calls of a different media
            // mode, i.e. G3FAX.  TNixon decided that it was desirable
            // to maintain this behavior for existing apps which may rely
            // on it.  (10/24/95)
            //

            _wcsupr(pszFileName);

            ptLineClientTarget = NULL;

            for (i = 0; i < pXxxClientList->dwNumUsedEntries; i++)
            {
                ptLineClientTmp = (PTLINECLIENT) pXxxClientList->aEntries[i];

                try
                {
                    //
                    // Recall that all app names start with '"'
                    //

                    LOG((TL_INFO,
                        "LHandoff: Looking for [%ls] list entry [%ls]",
                        pszFileName,
                        ptLineClientTmp->ptLineApp->pszModuleName
                        ));

                    if ((_wcsicmp(
                            pszFileName,
                            ptLineClientTmp->ptLineApp->pszModuleName + 1
                            ) == 0) &&

                        (ptLineClientTmp->dwPrivileges &
                            LINECALLPRIVILEGE_OWNER))
                    {
                        ptLineClientTarget = ptLineClientTmp;
                    }
                }
                myexcept
                {
                    // just continue
                }
            }

            if (ptLineClientTarget == NULL)
            {
                pParams->lResult = LINEERR_TARGETNOTFOUND;
                goto LHandoff_freeXxxClientList;
            }
            else if (ptLineClientTarget == ptLineClientApp)
            {
                pParams->lResult = LINEERR_TARGETSELF;
                goto LHandoff_freeXxxClientList;
            }
        }
        else
        {
            //
            // "Non-directed" handoff
            //
            // Validate the media mode, then walk thru the list of line
            // clients and find the highest pri one with owner privileges
            // that wants calls of the specified media mode
            //

            switch (dwAPIVersion)
            {
            case TAPI_VERSION1_0:

                dwValidMediaModes = AllMediaModes1_0;
                break;

            case TAPI_VERSION1_4:
            case TAPI_VERSION2_0:

                dwValidMediaModes = AllMediaModes1_4;
                break;

            //case TAPI_VERSION2_1:
            //case TAPI_VERSION2_2:
            default: //case TAPI_VERSION_CURRENT:

                dwValidMediaModes = AllMediaModes2_1;
                break;
            }

            if ((dwMediaMode == 0) ||
                (dwAPIVersion <= TAPI_VERSION2_1 ) &&
                    !IsOnlyOneBitSetInDWORD(dwMediaMode) ||
                (dwMediaMode & (dwValidMediaModes ^ 0x00ffffff)))
            {
                pParams->lResult = LINEERR_INVALMEDIAMODE;
                goto LHandoff_freeXxxClientList;
            }

            if ((ptLineClientTarget = GetHighestPriorityLineClient(
                    ptLine,
                    dwMediaMode,
                    0xffffffff

                    )) == NULL)
            {
                pParams->lResult = LINEERR_TARGETNOTFOUND;
                goto LHandoff_freeXxxClientList;
            }
            else if (ptLineClientTarget == ptLineClientApp)
            {
                pParams->lResult = LINEERR_TARGETSELF;
                goto LHandoff_freeXxxClientList;
            }

        }


        //
        // We've found a target tLineClient. See if it already has a
        // tCallClient for this call, and if not create one.  Then set
        // the privilege on the target's tCallClient to OWNER & send
        // the appropriate msgs.
        //

        if (pXxxClientList != &xxxClientList)
        {
            ServerFree (pXxxClientList);
        }

        if ((lResult = GetCallClientListFromCall(
                ptCall,
                &pXxxClientList
                )))
        {
            pParams->lResult = lResult;
            goto LHandoff_epilog;
        }

        {
            BOOL            bCreatedtCallClient,
                            bTargetAlreadyOwner = FALSE;
            HANDLE          hMutex;
            PTCALLCLIENT    ptCallClientTarget = NULL, ptCallClientTmp;


            for (i = 0; i < pXxxClientList->dwNumUsedEntries; i++)
            {
                ptCallClientTmp = (PTCALLCLIENT) pXxxClientList->aEntries[i];

                try
                {
                    if (ptCallClientTmp->ptLineClient == ptLineClientTarget)
                    {
                        ptCallClientTarget = ptCallClientTmp;
                        break;
                    }
                }
                myexcept
                {
                    // just continue
                }
            }

            if (!ptCallClientTarget)
            {
                if ((lResult = CreatetCallClient(
                        ptCall,
                        ptLineClientTarget,
                        LINECALLPRIVILEGE_OWNER,
                        TRUE,
                        TRUE,
                        &ptCallClientTarget,
                        FALSE

                        )) != 0)
                {
                    pParams->lResult = lResult;
                    goto LHandoff_freeXxxClientList;
                }

                bCreatedtCallClient = TRUE;
            }
            else
            {
                bCreatedtCallClient = FALSE;
            }

            if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
            {
                DWORD dwCallInfoState, dwCallState, dwCallStateMode;


                if (bCreatedtCallClient)
                {
                    //
                    // CreatetCallClient will have already sent out the
                    // appropriate CALLINFO msgs & updated NumOwners field
                    //

                    dwCallInfoState = 0;
                }
                else if (ptCallClientTarget->dwPrivilege ==
                            LINECALLPRIVILEGE_MONITOR)
                {
                    ptCallClientTarget->dwPrivilege = LINECALLPRIVILEGE_OWNER;

                    ptCall->dwNumOwners++;
                    ptCall->dwNumMonitors--;

                    dwCallInfoState = LINECALLINFOSTATE_NUMOWNERINCR |
                        LINECALLINFOSTATE_NUMMONITORS;
                }
                else
                {
                    //
                    // Ideally we should just be able to exit at this point.
                    // However, TAPI 1.x will send a CALLSTATE msg with
                    // dwParam3 == OWNER to the target app even though it
                    // already is an OWNER.  Some Intel app relies on this
                    // behavior, so we'll preserve it for now...
                    //

                    bTargetAlreadyOwner = TRUE;
                    dwCallInfoState = 0;
                }

                dwCallState     = ptCall->dwCallState;
                dwCallStateMode = ptCall->dwCallStateMode;

                UNLOCKTCALL(ptCall);

                if (dwCallInfoState ||
                    bCreatedtCallClient ||
                    bTargetAlreadyOwner)
                {
                    BOOL            bIndicatePrivilege = TRUE;
                    PTCLIENT        ptClientTarget;
                    ASYNCEVENTMSG   msg[2];


                    if (bCreatedtCallClient)
                    {
                        try
                        {
                            if (ptLineClientTarget->dwAPIVersion >=
                                    TAPI_VERSION2_0 &&
                                !FMsgDisabled(
                                    ptLineClientTarget->ptLineApp->dwAPIVersion,
                                    ptLineClientTarget->adwEventSubMasks,
                                    LINE_APPNEWCALL,
                                    0
                                    ))
                            {
                                ASYNCEVENTMSG   newCallMsg[2],
                                                *pNewCallMsg = newCallMsg;
                                PTCONFERENCELIST    pConfList;
                                BOOL                bConfParent = FALSE;


                                pNewCallMsg->TotalSize  =
                                    sizeof (ASYNCEVENTMSG) + 3*sizeof (DWORD);
                                pNewCallMsg->fnPostProcessProcHandle = 0;
                                pNewCallMsg->hDevice      =
                                    ptLineClientTarget->hRemoteLine;

                                pNewCallMsg->Msg        = LINE_APPNEWCALL;
                                pNewCallMsg->Param1     =
                                    ptCall->dwAddressID;
                                pNewCallMsg->Param2     =
                                    ptCallClientTarget->hCall;
                                pNewCallMsg->Param3     =
                                    LINECALLPRIVILEGE_OWNER;

                                *(&pNewCallMsg->Param4 + 1) =
                                    ptCall->dwCallID;
                                *(&pNewCallMsg->Param4 + 2) =
                                    ptCall->dwRelatedCallID;
                                if ((pConfList = ptCall->pConfList) &&
                                    (pConfList != (PTCONFERENCELIST) LongToPtr(0xffffffff)) &&
                                    (pConfList->aptCalls[0] == ptCall))
                                {
                                    bConfParent = TRUE;
                                }
                                *(&pNewCallMsg->Param4 + 3) = (DWORD) bConfParent;

                                pNewCallMsg->InitContext =
                                    ptLineClientTarget->ptLineApp->
                                        InitContext;

                                pNewCallMsg->OpenContext =
                                    ptLineClientTarget->OpenContext;

                                ptClientTarget = ptCallClientTarget->ptClient;

                                if (ptCallClientTarget->dwKey ==
                                        TCALLCLIENT_KEY)
                                {
                                    bIndicatePrivilege = FALSE;
                                    WriteEventBuffer(
                                        ptClientTarget,
                                        pNewCallMsg
                                        );
                                }
                            }
                        }
                        myexcept
                        {
                        }

                    }

                    msg->TotalSize = sizeof (ASYNCEVENTMSG) +
                        sizeof (HCALLHUB);

                    msg->fnPostProcessProcHandle = 0;

                    msg->Msg     = LINE_CALLSTATE;
                    msg->Param1  = dwCallState;
                    msg->Param2  = dwCallStateMode;
                    msg->Param3  = (bIndicatePrivilege ?
                        LINECALLPRIVILEGE_OWNER : 0);

                    try
                    {
                        msg->hDevice = ptCallClientTarget->hCall;

                        msg->InitContext =
                            ptLineClientTarget->ptLineApp->InitContext;

                        msg->OpenContext =
                            ptLineClientTarget->OpenContext;

                        *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                            (ptCallClientTarget->ptCallHubClient)?
                                ptCallClientTarget->ptCallHubClient->hCallHub : 
                                (HCALLHUB)(ULONG_PTR)NULL;

                        ptClientTarget = ptCallClientTarget->ptClient;

                        if (ptCallClientTarget->dwKey == TCALLCLIENT_KEY &&
                            !FMsgDisabled (
                                ptCallClientTarget->ptLineClient->ptLineApp->dwAPIVersion,
                                ptCallClientTarget->adwEventSubMasks,
                                LINE_CALLSTATE,
                                dwCallState
                                ))
                        {
                            WriteEventBuffer (ptClientTarget, msg);
                        }
                    }
                    myexcept
                    {
                    }

                    if (dwCallInfoState != 0)
                    {
                        LineEventProc(
                            (HTAPILINE)ptLine,
                            (HTAPICALL)ptCall,
                            LINE_CALLINFO,
                            dwCallInfoState,
                            0,
                            0
                            );
                    }

                    if (bCreatedtCallClient)
                    {
                        UpdateCallHubHashing(ptCall);
                    }
                }
            }
        }

LHandoff_freeXxxClientList:

        if (pXxxClientList != &xxxClientList)
        {
            ServerFree (pXxxClientList);
        }
    }

LHandoff_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "Handoff"
        );
}


void
WINAPI
LHold(
    PTCLIENT            ptClient,
    PLINEHOLD_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineHold;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEHOLD,                // provider func index
            &pfnTSPI_lineHold,          // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Hold"                      // func name

            )) > 0)
    {
        pParams->lResult = CallSP2(
            pfnTSPI_lineHold,
            "lineHold",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Hold"
        );
}


void
WINAPI
LInitialize(
    PTCLIENT                ptClient,
    PLINEINITIALIZE_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL        bInitClient = FALSE;
    DWORD       dwFriendlyNameSize, dwModuleNameSize;
    PTLINEAPP   ptLineApp;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwFriendlyNameOffset
            ) ||

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwModuleNameOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    //
    // Alloc & init a new tLineApp
    //

    dwFriendlyNameSize = sizeof(WCHAR) * (1 + lstrlenW(
        (PWSTR)(pDataBuf + pParams->dwFriendlyNameOffset))
        );

    dwModuleNameSize = sizeof(WCHAR) * (2 + lstrlenW(
        (PWSTR)(pDataBuf + pParams->dwModuleNameOffset))
        );

    if (!(ptLineApp = ServerAlloc(
            sizeof(TLINEAPP) +
            dwFriendlyNameSize +
            dwModuleNameSize
            )))
    {
        pParams->lResult = LINEERR_NOMEM;
        goto LInitialize_return;
    }

    LOG((TL_INFO, "LInitialize: calling NewObject ptLineApp %p, pParams->InitContext %lx", ptLineApp, pParams->InitContext));

    if (!(ptLineApp->hLineApp = (HLINEAPP) NewObject(
            ghHandleTable,
            ptLineApp,
            NULL
            )))
    {
        pParams->lResult = LINEERR_NOMEM;
        ServerFree (ptLineApp);
        goto LInitialize_return;
    }

    LOG((TL_INFO, "LInitialize: NewObject returned hLineApp %lx", ptLineApp->hLineApp));

    ptLineApp->dwKey        = TLINEAPP_KEY;
    ptLineApp->ptClient     = ptClient;

    LOG((TL_INFO, "LInitialize: initializing ptLineApp->InitContext with %lx", pParams->InitContext));

    ptLineApp->InitContext  = pParams->InitContext;
    ptLineApp->dwAPIVersion = pParams->dwAPIVersion;
    
    LOG((TL_INFO, "LInitialize: initialized ptLineApp->dwAPIVersion with %lx", pParams->dwAPIVersion));

    ptLineApp->dwFriendlyNameSize = dwFriendlyNameSize;
    ptLineApp->pszFriendlyName    = (WCHAR *) (ptLineApp + 1);

    wcscpy(
        ptLineApp->pszFriendlyName,
        (PWSTR)(pDataBuf + pParams->dwFriendlyNameOffset)
        );

    //
    // Note: we prepend the '"' char to the saved module name to aid in
    //       priority determination for incoming calls
    //

    ptLineApp->dwModuleNameSize = dwModuleNameSize;
    ptLineApp->pszModuleName = (WCHAR *)((LPBYTE)(ptLineApp + 1) +
                                         dwFriendlyNameSize);

    ptLineApp->pszModuleName[0] = '"';

    wcscpy(
        &ptLineApp->pszModuleName[1],
        (WCHAR *)(pDataBuf + pParams->dwModuleNameOffset)
        );

    _wcsupr (&ptLineApp->pszModuleName[1]);


    //
    // Safely insert new tLineApp at front of tClient's tLineApp list
    //

    if (ptClient->ptLineApps == NULL)
    {
        bInitClient = TRUE;
    }

    if (WaitForExclusiveClientAccess (ptClient))
    {
        if (ptLineApp->dwAPIVersion <= TAPI_VERSION3_0)
        {
            FillMemory (
                ptLineApp->adwEventSubMasks, 
                sizeof(DWORD) * EM_NUM_MASKS,
                (BYTE) 0xff
                );
        }
        else
        {
            CopyMemory (
                ptLineApp->adwEventSubMasks, 
                ptClient->adwEventSubMasks,
                sizeof(DWORD) * EM_NUM_MASKS
                );
        }
        
        if ((ptLineApp->pNext = ptClient->ptLineApps))
        {
            ptLineApp->pNext->pPrev = ptLineApp;
        }

        ptClient->ptLineApps = ptLineApp;

        UNLOCKTCLIENT (ptClient);
    }
    else
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        goto LInitialize_error1;
    }


    //
    // Check if global reinit flag set
    //

    if (TapiGlobals.dwFlags & TAPIGLOBALS_REINIT)
    {
        pParams->lResult = LINEERR_REINIT;
        goto LInitialize_error2;
    }


    //
    // See if we need to go thru init
    //

    TapiEnterCriticalSection (&TapiGlobals.CritSec);

    if ((TapiGlobals.dwNumLineInits == 0) &&
        (TapiGlobals.dwNumPhoneInits == 0) &&
        !gbServerInited)
    {

        if ((pParams->lResult = ServerInit(FALSE)) != 0)
        {
            TapiLeaveCriticalSection (&TapiGlobals.CritSec);
            goto LInitialize_error2;
        }
        gbServerInited = TRUE;
    }

#if TELE_SERVER
    if (bInitClient)
    {
        if (pParams->lResult = InitializeClient(ptClient))
        {
            TapiLeaveCriticalSection (&TapiGlobals.CritSec);
            goto LInitialize_error2;
        }
    }
#else
    pParams->lResult = 0; // That's what happens if it's not a tele_server...
#endif


    //
    // Fill in the return values
    //


    pParams->hLineApp  = ptLineApp->hLineApp;

    LOG((TL_INFO, "LInitialize: returning pParams->hLineApp %p", pParams->hLineApp));

    pParams->dwNumDevs = TapiGlobals.dwNumLines;

    LOG((TL_INFO, "LInitialize: returning pParams->dwNumDevs %p", pParams->dwNumDevs));

#if TELE_SERVER
    if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
        !IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
    {
        pParams->dwNumDevs = ptClient->dwLineDevices;
        LOG((TL_TRACE,  "LInitialize: returning pParams->dwNumDevs %p (again)", pParams->dwNumDevs));
    }
#endif


    //
    // Increment total num line inits
    //

    TapiGlobals.dwNumLineInits++;

    *pdwNumBytesReturned = sizeof (LINEINITIALIZE_PARAMS);

    TapiLeaveCriticalSection (&TapiGlobals.CritSec);

    goto LInitialize_return;


LInitialize_error2:

    if (WaitForExclusiveClientAccess (ptClient))
    {
        if (ptLineApp->pNext)
        {
            ptLineApp->pNext->pPrev = ptLineApp->pPrev;
        }

        if (ptLineApp->pPrev)
        {
            ptLineApp->pPrev->pNext = ptLineApp->pNext;
        }
        else
        {
            ptClient->ptLineApps = ptLineApp->pNext;
        }

        UNLOCKTCLIENT (ptClient);

    }

LInitialize_error1:

    DereferenceObject (ghHandleTable, ptLineApp->hLineApp, 1);

LInitialize_return:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineInitialize: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE,
            "lineInitialize: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
LMakeCall_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PTCALL          ptCall = (PTCALL) pAsyncRequestInfo->dwParam1;
    DWORD           hpCallHandle = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__);

    PTCALLCLIENT    ptCallClient;


    if (WaitForExclusivetCallAccess (ptCall, TINCOMPLETECALL_KEY))
    {
        PTCALL      ptCallThen = (PTCALL) pAsyncRequestInfo->dwParam5;


        //
        // Check to make sure this is the call we think it is (that the
        // pointer wasn't freed by a previous call to lineClose/Shutdown
        // and realloc'd for use as a ptCall again)
        //

        if (ptCall != ptCallThen)
        {
            UNLOCKTCALL(ptCall);
            goto LMakeCall_PostProcess_bad_ptCall;
        }

        ptCallClient = ptCall->ptCallClients;

        if (pAsyncEventMsg->Param2 == 0)  // success
        {
            //
            // In general it's ok with us if service providers want to
            // specify NULL as their hdCall (could be an index in an
            // array).  But in the TSPI_lineForward case, the spec says
            // that a NULL hdCall value following successful completion
            // indicates that no call was created, so in that case we
            // want to nuke the tCall & tCallClient we created, and
            // indicate a NULL call handle to the client. A non-zero
            // pAsyncRequestInfo->dwParam3 tells us that we are
            // post-processing a lineForward request, otherwise it's a
            // make call or similar (non-Forward) request.
            //

            if (pAsyncRequestInfo->dwParam3 && !ptCall->hdCall)
            {
                goto LMakeCall_PostProcess_cleanupCalls;
            }


            //
            // Check to see if the app closed the line & left us with
            // 0 call clients (in which case it'll also be taking care of
            // cleaning up this tCall too)
            //

            if (ptCall->ptCallClients == NULL)
            {
                UNLOCKTCALL(ptCall);

                ptCallClient = (PTCALLCLIENT) NULL;

                if (pAsyncEventMsg->Param2 == 0)
                {
                    pAsyncEventMsg->Param2 = LINEERR_INVALLINEHANDLE;
                }

                goto LMakeCall_PostProcess_initMsgParams;
            }


            //
            // Retrieve the various call IDs, then check if call
            // client was destroyed by another thread (due to
            // lineClose/Shutdown) while we were getting the call ID.
            // If so, we'll need to clean up the tCall, since we know
            // the other thread didn't do it because GetCallIDs marks
            // the call as a zombie.
            //

            GetCallIDs (ptCall);

            if (ptCall->ptCallClients == NULL)
            {
                goto LMakeCall_PostProcess_cleanupCalls;
            }


            //
            // Stuff the various call IDs in the var data section
            // of the ASYNCEVENTMSG.
            //
            // Make sure to increment the dwTotalSize of the ASYNCEVENTMSG
            // as appropriate.  We rely on the fact that CompletionProc()
            // calls us with a AsyncEventMsg buffer which is big enough to
            // handle a few extra DWORDs.
            //

            pAsyncEventMsg->Param3 = ptCallClient->hCall;

            pAsyncEventMsg->TotalSize +=
                3 * sizeof (pAsyncEventMsg->Param1);

            *(&pAsyncEventMsg->Param4 + 1) = ptCall->dwAddressID;
            *(&pAsyncEventMsg->Param4 + 2) = ptCall->dwCallID;
            *(&pAsyncEventMsg->Param4 + 3) = ptCall->dwRelatedCallID;


            //
            // Mark the calls as valid, the release the mutex.
            //

            ptCall->dwKey       = TCALL_KEY;
            ptCallClient->dwKey = TCALLCLIENT_KEY;

            UNLOCKTCALL(ptCall);


            //
            // Create monitor tCallClients
            //

            if (ptCallThen == ptCall)
            {
                CreateCallMonitors (ptCall, FALSE);
            }
        }
        else    // error
        {

LMakeCall_PostProcess_cleanupCalls:

            //
            // Invalidate the tCall, & if there's still a tCallClient
            // (might have already been destroyed by a lineClose/Shutdown
            // in another thread) invalidate it too. Then unlock the
            // tCall & remove the object(s) from the list(s).
            //

            ptCall->dwKey = INVAL_KEY;

            if (ptCall->ptCallClients)
            {
                ptCallClient->dwKey = INVAL_KEY;
                ptCall->lActiveFastCallClients--;
            }
            else
            {
                ptCallClient = NULL;
            }

            UNLOCKTCALL(ptCall);

            RemoveCallFromLineList (ptCall);

            if (ptCallClient)
            {
                DereferenceObject (ghHandleTable, ptCallClient->hCall, 1);
                RemoveCallClientFromLineClientList (ptCallClient);
            }


            //
            // Make sure all fast call clients cleaned up before free tCall
            //

            while (ptCall->lActiveFastCallClients != 0)
            {
                Sleep (5);
            }

            FreetCall  (ptCall);

            ptCallClient = NULL;
        }
    }
    else
    {
        //
        // If here we can assume that the call was already destroyed
        // and just fail the request
        //

LMakeCall_PostProcess_bad_ptCall:

        ptCallClient = (PTCALLCLIENT) NULL;

        if (pAsyncEventMsg->Param2 == 0)
        {
            pAsyncEventMsg->Param2 = LINEERR_OPERATIONFAILED;
        }
    }


LMakeCall_PostProcess_initMsgParams:

    //
    // Fill in the params to pass to client (important to remotesp in both
    // the success & fail cases so it can either init or clean up drvCall)
    //

    pAsyncEventMsg->Param4 = hpCallHandle;
}


void
WINAPI
LMakeCall(
    PTCLIENT                ptClient,
    PLINEMAKECALL_PARAMS    pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineMakeCall;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEMAKECALL,            // provider func index
            &pfnTSPI_lineMakeCall,      // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "MakeCall"                  // func name

            )) > 0)
    {
        LONG                lResult;
        PTCALL              ptCall;
        LPWSTR              lpszDestAddress;
        //HTAPICALL           htCall;
        PTCALLCLIENT        ptCallClient;
        LPLINECALLPARAMS    pCallParamsApp, pCallParamsSP;

        LOG((TL_INFO, "LMakeCall: LINEPROLOG succeeded ."));

        //
        // Verify size/offset/string params given our input buffer/size
        //

        if (((pParams->dwDestAddressOffset != TAPI_NO_DATA)  &&

                IsBadStringParam(
                    dwParamsBufferSize,
                    pDataBuf,
                    pParams->dwDestAddressOffset
                    ))  ||

            ((pParams->dwCallParamsOffset != TAPI_NO_DATA)  &&

                IsBadStructParam(
                    dwParamsBufferSize,
                    pDataBuf,
                    pParams->dwCallParamsOffset
                    )))

        {
            LOG((TL_INFO, "LMakeCall: LINEERR_STRUCTURETOOSMALL."));

            //
            // Note: Passing back ERR_STRUCTURETOOSMALL handles the case
            //       where app has passed in a bad size in the callparams,
            //       and does so in a spec-friendly manner.  The only
            //       other reason we'd end up here would be an rpc attack,
            //       and in that case it's just important that we fail.
            //

            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LMakeCall_return;
        }


        pCallParamsApp = (LPLINECALLPARAMS)
            (pParams->dwCallParamsOffset == TAPI_NO_DATA ?
                0 : (pDataBuf + pParams->dwCallParamsOffset));

        LOG((TL_INFO, "LMakeCall: pCallParamsApp %p.", pCallParamsApp));

        if (pCallParamsApp)
        {
            DWORD           dwAPIVersion, dwSPIVersion;


            if (GetLineVersions(
                    ptLineClient,
                    &dwAPIVersion,
                    &dwSPIVersion

                    ) != 0)
            {
                LOG((TL_ERROR, "LMakeCall: GetLineVersions failed. LINEERR_INVALLINEHANDLE"));

                lRequestID = LINEERR_INVALLINEHANDLE;
                goto LMakeCall_return;
            }

            if ((lResult = ValidateCallParams(
                    pCallParamsApp,
                    &pCallParamsSP,
                    dwAPIVersion,
                    dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage

                    )) != 0)
            {
                LOG((TL_ERROR, "LMakeCall: ValidateCallParams failed. %lx", lResult));
                lRequestID = lResult;
                goto LMakeCall_return;
            }
        }
        else
        {
            pCallParamsSP = (LPLINECALLPARAMS) NULL;
        }

        if (CreatetCallAndClient(
                ptLineClient,
                &ptCall,
                &ptCallClient,
                pCallParamsSP,
                NULL

                ) != 0)
        {
            LOG((TL_ERROR, "LMakeCall: CreatetCallAndClient failed. LINEERR_NOMEM"));

            lRequestID = LINEERR_NOMEM;
            goto LMakeCall_freeCallParams;
        }

        //htCall = ptCall->hCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpCall;
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        if (pParams->dwDestAddressOffset == TAPI_NO_DATA)
        {
            LOG((TL_ERROR, "LMakeCall: pParams->dwDestAddressOffset == TAPI_NO_DATA"));

            lpszDestAddress = NULL;
        }
        else
        {
            lpszDestAddress = (LPWSTR)(pDataBuf +pParams->dwDestAddressOffset);
        }

        LOG((TL_TRACE,  "LMakeCall: calling CallSP7"));

        pParams->lResult = CallSP7(
                pfnTSPI_lineMakeCall,
                "lineMakeCall",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) ptCall,
                (ULONG_PTR) &ptCall->hdCall,
                (ULONG_PTR) lpszDestAddress,
                (DWORD) pParams->dwCountryCode,
                (ULONG_PTR) pCallParamsSP
                );

        LOG((TL_ERROR, "LMakeCall: CallSP7 returnded %lx", pParams->lResult));


        SetDrvCallFlags(
            ptCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

LMakeCall_freeCallParams:

        if (pCallParamsSP != pCallParamsApp)
        {
            ServerFree (pCallParamsSP);
        }
    }

LMakeCall_return:

    LOG((TL_TRACE,  "LMakeCall: calling LINEEPILOGASYNC"));

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "MakeCall"
        );

    LOG((TL_TRACE,  "LMakeCall: LINEEPILOGASYNC returned"));

}

void
WINAPI
LMonitorDigits(
    PTCLIENT                    ptClient,
    PLINEMONITORDIGITS_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineMonitorDigits;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient, ptCallClient2;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEMONITORDIGITS,       // provider func index
            &pfnTSPI_lineMonitorDigits, // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "MonitorDigits"             // func name

            )) == 0)
    {
        DWORD           dwUnionDigitModes;
        PTCALL          ptCall;


        if ((pParams->dwDigitModes & (~AllDigitModes)))
        {
            pParams->lResult = LINEERR_INVALDIGITMODE;
            goto LMonitorDigits_epilog;
        }


        //
        // Determine the new union of modes
        //

        dwUnionDigitModes = pParams->dwDigitModes;

        try
        {
            ptCall = ptCallClient->ptCall;
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LMonitorDigits_epilog;
        }

        if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
        {
            ptCallClient2 = ptCall->ptCallClients;

            while (ptCallClient2)
            {
                if (ptCallClient2 != ptCallClient)
                {
                    dwUnionDigitModes |=
                        ptCallClient2->dwMonitorDigitModes;
                }

                ptCallClient2 = ptCallClient2->pNextSametCall;
            }

            UNLOCKTCALL (ptCall);
        }
        else
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LMonitorDigits_epilog;
        }

        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineMonitorDigits,
                "lineMonitorDigits",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdCall,
                (DWORD) dwUnionDigitModes

                )) == 0)
        {
            ptCallClient->dwMonitorDigitModes = pParams->dwDigitModes;
        }
    }

LMonitorDigits_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "MonitorDigits"
        );
}


void
WINAPI
LMonitorMedia(
    PTCLIENT                    ptClient,
    PLINEMONITORMEDIA_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineMonitorMedia;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient, ptCallClient2;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEMONITORMEDIA,        // provider func index
            &pfnTSPI_lineMonitorMedia,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "MonitorMedia"              // func name

            )) == 0)
    {
        DWORD           dwAPIVersion, dwValidMediaModes, dwUnionMediaModes;
        PTCALL          ptCall;


        //
        // Validate the specified modes
        //

        try
        {
            ptCall = ptCallClient->ptCall;
            dwAPIVersion = ptCallClient->ptLineClient->dwAPIVersion;

            if (ptCallClient->dwKey != TCALLCLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LMonitorMedia_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LMonitorMedia_epilog;
        }

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:

            dwValidMediaModes = AllMediaModes1_0;
            break;

        case TAPI_VERSION1_4:
        case TAPI_VERSION2_0:

            dwValidMediaModes = AllMediaModes1_4;
            break;

        //case TAPI_VERSION2_1:
        //case TAPI_VERSION2_2:
        //case TAPI_VERSION_CURRENT:
        default:

            dwValidMediaModes = AllMediaModes2_1;
            break;
        }

        if (pParams->dwMediaModes & (~dwValidMediaModes & 0x00FFFFFF))
        {
            pParams->lResult = LINEERR_INVALMEDIAMODE;
            goto LMonitorMedia_epilog;
        }


        //
        // Determine the new union of modes
        //

        dwUnionMediaModes = pParams->dwMediaModes;

        if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
        {
            ptCallClient2 = ptCall->ptCallClients;

            while (ptCallClient2)
            {
                if (ptCallClient2 != ptCallClient)
                {
                    dwUnionMediaModes |=
                        ptCallClient2->dwMonitorMediaModes;
                }

                ptCallClient2 = ptCallClient2->pNextSametCall;
            }

            UNLOCKTCALL (ptCall);
        }
        else
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LMonitorMedia_epilog;
        }

        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineMonitorMedia,
                "lineMonitorMedia",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdCall,
                (DWORD) dwUnionMediaModes

                )) == 0)
        {
            ptCallClient->dwMonitorMediaModes = pParams->dwMediaModes;
        }
    }

LMonitorMedia_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "MonitorMedia"
        );
}


void
WINAPI
LMonitorTones(
    PTCLIENT                    ptClient,
    PLINEMONITORTONES_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVCALL        hdCall;
    TSPIPROC        pfnTSPI_lineMonitorTones;
    DWORD               objectToDereference;
    PTCALLCLIENT    ptCallClient, ptCallClient2;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwTonesOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwNumEntries,      // really dwNumEntries *
                                        //     sizeof(LINEMONITORTONE)
            pParams->dwTonesOffset,
            sizeof(DWORD),
            "LMonitorTones",
            "pParams->MSPBuffer"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_MONITOR,  // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEMONITORTONES,        // provider func index
            &pfnTSPI_lineMonitorTones,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "MonitorTones"              // func name

            )) == 0)
    {
        //
        // If this is a remotesp call then we want to mark the
        // tCallClient as being interested in LINE_MONITORTONES
        // messages (due to our kludgy handling of this msg, see
        // comments in the msg handler in LineEventProc()).
        //

        PTCALL          ptCall;


        try
        {
            ptCall = ptCallClient->ptCall;

            if (ptCall->ptProvider != pRemoteSP)
            {
                ptCall = NULL;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LMonitorTones_epilog;
        }

        if (ptCall)
        {
            if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
            {
                for(
                    ptCallClient2 = ptCall->ptCallClients;
                    (ptCallClient2  &&  (ptCallClient2 != ptCallClient));
                    ptCallClient2 = ptCallClient2->pNextSametCall
                    );

                if (ptCallClient2 == ptCallClient)
                {
                    ptCallClient->bMonitoringTones =
                        (pParams->dwTonesOffset == TAPI_NO_DATA ? 0 : 1);
                }

                UNLOCKTCALL (ptCall);

                if (!ptCallClient2)
                {
                    pParams->lResult = LINEERR_INVALCALLHANDLE;
                    goto LMonitorTones_epilog;
                }
            }
            else
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LMonitorTones_epilog;
            }
        }

        pParams->lResult = CallSP4(
            pfnTSPI_lineMonitorTones,
            "lineMonitorTones",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->hCall,
            (ULONG_PTR) (pParams->dwTonesOffset == TAPI_NO_DATA ? 0 :
                pDataBuf + pParams->dwTonesOffset),
            (DWORD) pParams->dwNumEntries / sizeof (LINEMONITORTONE)
            );
    }

LMonitorTones_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "MonitorTones"
        );
}


void
WINAPI
LNegotiateAPIVersion(
    PTCLIENT                        ptClient,
    PLINENEGOTIATEAPIVERSION_PARAMS pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{

    //
    // Note: TAPI_VERSION1_0 <= dwNegotiatedAPIVersion <= dwSPIVersion
    //

    DWORD   dwDeviceID = pParams->dwDeviceID;

    LOG((TL_TRACE,  "LNegotiateAPIVersion: started. dwDeviceID %lx, dwAPILowVersion %lx dwAPIHighVersion %lx", 
        pParams->dwDeviceID, pParams->dwAPILowVersion, pParams->dwAPIHighVersion));

    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (dwParamsBufferSize < sizeof (LINEEXTENSIONID))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto LNegotiateAPIVersion_exit;
    }


#if TELE_SERVER
    if ((TapiGlobals.dwFlags & TAPIGLOBALS_SERVER) &&
        !IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
    {
        try
        {
            if (dwDeviceID >= ptClient->dwLineDevices)
            {
                pParams->lResult = LINEERR_BADDEVICEID;
                goto LNegotiateAPIVersion_exit;
            }
            dwDeviceID = ptClient->pLineDevices[dwDeviceID];
        }
        myexcept
        {
            LOG((TL_ERROR, "ptClient excepted in LineNegotiateAPIVersion"));
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LNegotiateAPIVersion_exit;
        }
    }
#endif


    if (dwDeviceID < TapiGlobals.dwNumLines)
    {
        DWORD       dwAPIHighVersion = pParams->dwAPIHighVersion,
                    dwAPILowVersion  = pParams->dwAPILowVersion,
                    dwHighestValidAPIVersion;
        PTLINEAPP   ptLineApp;


        //
        // Do a minimax test on the specified lo/hi values
        //

        if ((dwAPILowVersion > dwAPIHighVersion) ||
            (dwAPILowVersion > TAPI_VERSION_CURRENT) ||
            (dwAPIHighVersion < TAPI_VERSION1_0))
        {
            LOG((TL_ERROR, "LNegotiateAPIVersion: LINEERR_INCOMPATIBLEAPIVERSION1"));

            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto LNegotiateAPIVersion_exit;
        }


        //
        // HACKALERT! Some dumb apps like SmarTerm negotiate specifying
        // a dwHighVersion of 0x7fffffff or higher, which can really
        // cause them problems (like when they try to pass down structures
        // of a size that was fine in the TAPI version under which the app
        // was built, but which were enlarged in subsequent versions of
        // TAPI, and the result is lots of LINEERR_STRUCTURETOOSMALL
        // errors).
        //
        // Since we're nice, accomodating people we'll try to munge the
        // dwHighVersion in these cases to be a value that makes sense, so
        // we don't end up negotiating a version that the app can't handle.
        //

        if (dwAPIHighVersion & 0xc0000000)
        {
            dwAPIHighVersion = (dwAPILowVersion > TAPI_VERSION1_0 ?
                dwAPILowVersion : TAPI_VERSION1_0);
        }


        //
        // Find the highest valid API version given the lo/hi values.
        // Since valid vers aren't consecutive we need to check for
        // errors that our minimax test missed.
        //

        if (dwAPIHighVersion < TAPI_VERSION_CURRENT)
        {
            if ((dwAPIHighVersion >= TAPI_VERSION3_0) &&
                (dwAPILowVersion <= TAPI_VERSION3_0))
            {
                dwHighestValidAPIVersion = TAPI_VERSION3_0;
            }
            else if ((dwAPIHighVersion >= TAPI_VERSION2_2) &&
                (dwAPILowVersion <= TAPI_VERSION2_2))
            {
                dwHighestValidAPIVersion = TAPI_VERSION2_2;
            }
            else if ((dwAPIHighVersion >= TAPI_VERSION2_1) &&
                (dwAPILowVersion <= TAPI_VERSION2_1))
            {
                dwHighestValidAPIVersion = TAPI_VERSION2_1;
            }
            else if ((dwAPIHighVersion >= TAPI_VERSION2_0) &&
                (dwAPILowVersion <= TAPI_VERSION2_0))
            {
                dwHighestValidAPIVersion = TAPI_VERSION2_0;
            }
            else if ((dwAPIHighVersion >= TAPI_VERSION1_4) &&
                (dwAPILowVersion <= TAPI_VERSION1_4))
            {
                dwHighestValidAPIVersion = TAPI_VERSION1_4;
            }
            else if ((dwAPIHighVersion >= TAPI_VERSION1_0) &&
                (dwAPILowVersion <= TAPI_VERSION1_0))
            {
                dwHighestValidAPIVersion = TAPI_VERSION1_0;
            }
            else
            {
                LOG((TL_ERROR, "LNegotiateAPIVersion: LINEERR_INCOMPATIBLEAPIVERSION2"));

                pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
                goto LNegotiateAPIVersion_exit;
            }
        }
        else
        {
            dwHighestValidAPIVersion = TAPI_VERSION_CURRENT;
        }


        //
        // WARNING!!! WARNING!!! WARNING!!! WARNING!!!
        // This code overwrites ptLineApp and later invalidates it.
        // Do NOT use ptLineApp after the MyReleaseMutex call.
        //
        if ((ptLineApp = WaitForExclusiveLineAppAccess(
                pParams->hLineApp,
                ptClient
                )))
        {

            LOG((TL_INFO, 
                "LNegotiateAPIVersion WaitForExclusiveLineAppAccess succeeded returned ptLineApp %p for hLineApp %p",
                ptLineApp, pParams->hLineApp));


            //
            // Is this app trying to negotiate something valid?
            //
            // If an app has called lineInitalize (as opposed to
            // lineInitializeEx), we'll clamp the max APIVersion they can
            // negotiate to 1.4.
            //

            if ( ptLineApp->dwAPIVersion < TAPI_VERSION2_0 )
            {
                dwHighestValidAPIVersion =
                    (dwHighestValidAPIVersion >= TAPI_VERSION1_4) ?
                    TAPI_VERSION1_4 : TAPI_VERSION1_0;
            }


            //
            // Save the highest valid API version the client says it supports
            // (we need this for determining which msgs to send to it)
            //

            if (dwHighestValidAPIVersion > ptLineApp->dwAPIVersion)
            {
                ptLineApp->dwAPIVersion = dwHighestValidAPIVersion;
            }

            UNLOCKTLINEAPP(ptLineApp);
        }
        else
        {
            LOG((TL_ERROR, "LNegotiateAPIVersion WaitForExclusiveLineAppAccess returned NULL"));

            pParams->lResult = LINEERR_INVALAPPHANDLE;
            goto LNegotiateAPIVersion_exit;
        }


        //
        // See if there's a valid match with the SPI ver
        //

        {
            DWORD               dwSPIVersion;
            PTLINELOOKUPENTRY   pLookupEntry;


            pLookupEntry = GetLineLookupEntry (dwDeviceID);
            if (NULL == pLookupEntry)
            {
                pParams->lResult = LINEERR_INVALAPPHANDLE;
                goto LNegotiateAPIVersion_exit;
            }

            dwSPIVersion = pLookupEntry->dwSPIVersion;

            if (pLookupEntry->bRemoved)
            {
                pParams->lResult = LINEERR_NODEVICE;
                goto LNegotiateAPIVersion_exit;
            }

            if (pLookupEntry->ptProvider == NULL)
            {
                pParams->lResult = LINEERR_NODRIVER;
                goto LNegotiateAPIVersion_exit;
            }

            if (dwAPILowVersion <= dwSPIVersion)
            {
                pParams->dwAPIVersion =
                    (dwHighestValidAPIVersion > dwSPIVersion ?
                    dwSPIVersion : dwHighestValidAPIVersion);


                //
                // Retrieve ext id (indicate no exts if GetExtID not exported)
                //

                if ((pLookupEntry->ptProvider->apfn[SP_LINEGETEXTENSIONID]))
                {
                    pParams->lResult = CallSP3(
                        pLookupEntry->ptProvider->
                        apfn[SP_LINEGETEXTENSIONID],
                        "lineGetExtensionID",
                        SP_FUNC_SYNC,
                        (DWORD) dwDeviceID,
                        (DWORD) dwSPIVersion,
                        (ULONG_PTR) pDataBuf
                        );

                    // the sp returned operationunavail.  we don't want
                    // to return that, so we just indicate that
                    // there are no extensions

                    if (pParams->lResult == LINEERR_OPERATIONUNAVAIL)
                    {
                        pParams->lResult = 0;
                        FillMemory (pDataBuf, sizeof (LINEEXTENSIONID), 0);
                    }
                }
                else
                {
                    // doesn't export it, so fill in with zeros
                    pParams->lResult = 0;
                    FillMemory (pDataBuf, sizeof (LINEEXTENSIONID), 0);
                }
            }
            else
            {

                LOG((TL_ERROR, "LNegotiateAPIVersion: LINEERR_INCOMPATIBLEAPIVERSION3"));
                pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
                goto LNegotiateAPIVersion_exit;
            }
        }

        pParams->dwExtensionIDOffset = 0;
        

        pParams->dwSize              = sizeof (LINEEXTENSIONID);

        
        *pdwNumBytesReturned = sizeof (LINEEXTENSIONID) + sizeof (TAPI32_MSG);
    }
    else
    {
        pParams->lResult = LINEERR_BADDEVICEID;
    }

LNegotiateAPIVersion_exit:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineNegotiateAPIVersion: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineNegotiateAPIVersion: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
NegotiateAPIVersionForAllDevices(
    PTCLIENT                                    ptClient,
    PNEGOTIATEAPIVERSIONFORALLDEVICES_PARAMS    pParams,
    DWORD                                       dwParamsBufferSize,
    LPBYTE                                      pDataBuf,
    LPDWORD                                     pdwNumBytesReturned
    )
{
#if TELE_SERVER
    //
    // Note: TAPI_VERSION1_0 <= dwNegotiatedAPIVersion <= dwSPIVersion
    //

    DWORD               i,
                        dwNumLineDevices = pParams->dwNumLineDevices,
                        dwNumPhoneDevices = pParams->dwNumPhoneDevices,
                        dwAPIHighVersion = pParams->dwAPIHighVersion,
                        dwAPILowVersion  = TAPI_VERSION1_0,
                        dwHighestValidAPIVersion, *pdwAPIVersion;
    LPLINEEXTENSIONID   pExtID;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    i = pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize +
        pParams->dwPhoneAPIVersionListSize +
        pParams->dwPhoneExtensionIDListSize;

    if ((pParams->dwLineAPIVersionListSize & 0xc0000000) ||
        (pParams->dwLineExtensionIDListSize & 0xc0000000) ||
        (pParams->dwPhoneAPIVersionListSize & 0xc0000000) ||
        (pParams->dwPhoneExtensionIDListSize & 0xc0000000) ||
        (i > dwParamsBufferSize))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    //
    // Verify the specified dwNumLine/PhonesDevices
    //

    if (IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
    {
        if ((pParams->dwNumLineDevices > TapiGlobals.dwNumLines)  ||
            (pParams->dwNumPhoneDevices > TapiGlobals.dwNumPhones))
        {
            pParams->lResult = LINEERR_OPERATIONFAILED;
            return;
        }
    }
    else
    {
        if ((pParams->dwNumLineDevices > ptClient->dwLineDevices)  ||
            (pParams->dwNumPhoneDevices > ptClient->dwPhoneDevices))
        {
            pParams->lResult = LINEERR_OPERATIONFAILED;
            return;
        }
    }


    //
    //
    //

    if (TapiGlobals.dwNumLineInits == 0)
    {
        pParams->lResult = LINEERR_UNINITIALIZED;
        goto NegotiateAPIVersionForAllDevices_exit;
    }


    //
    // Do a minimax test on the specified lo/hi values
    //

    if ((dwAPILowVersion > dwAPIHighVersion) ||
        (dwAPILowVersion > TAPI_VERSION_CURRENT) ||
        (dwAPIHighVersion < TAPI_VERSION1_0))
    {
        pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
        goto NegotiateAPIVersionForAllDevices_exit;
    }


    //
    // Find the highest valid API version given the lo/hi values.
    // Since valid vers aren't consecutive we need to check for
    // errors that our minimax test missed.
    //

    if (dwAPIHighVersion < TAPI_VERSION_CURRENT)
    {
        if ((dwAPIHighVersion >= TAPI_VERSION3_0) &&
            (dwAPILowVersion <= TAPI_VERSION3_0))
        {
            dwHighestValidAPIVersion = TAPI_VERSION3_0;
        }
        else if ((dwAPIHighVersion >= TAPI_VERSION2_2) &&
            (dwAPILowVersion <= TAPI_VERSION2_2))
        {
            dwHighestValidAPIVersion = TAPI_VERSION2_2;
        }
        else if ((dwAPIHighVersion >= TAPI_VERSION2_1) &&
            (dwAPILowVersion <= TAPI_VERSION2_1))
        {
            dwHighestValidAPIVersion = TAPI_VERSION2_1;
        }
        else if ((dwAPIHighVersion >= TAPI_VERSION2_0) &&
            (dwAPILowVersion <= TAPI_VERSION2_0))
        {
            dwHighestValidAPIVersion = TAPI_VERSION2_0;
        }
        else if ((dwAPIHighVersion >= TAPI_VERSION1_4) &&
            (dwAPILowVersion <= TAPI_VERSION1_4))
        {
            dwHighestValidAPIVersion = TAPI_VERSION1_4;
        }
        else if ((dwAPIHighVersion >= TAPI_VERSION1_0) &&
            (dwAPILowVersion <= TAPI_VERSION1_0))
        {
            dwHighestValidAPIVersion = TAPI_VERSION1_0;
        }
        else
        {
            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto NegotiateAPIVersionForAllDevices_exit;
        }
    }
    else
    {
        dwHighestValidAPIVersion = TAPI_VERSION_CURRENT;
    }


    //
    // Now for each line device do negotiation
    //

    pdwAPIVersion = (LPDWORD) pDataBuf;

    pExtID = (LPLINEEXTENSIONID)
        (pDataBuf + pParams->dwLineAPIVersionListSize);

    for (i = 0; i < dwNumLineDevices; i++, pdwAPIVersion++, pExtID++)
    {
        DWORD               dwDeviceID, dwSPIVersion;
        PTLINELOOKUPENTRY   pLookupEntry;


        if (!IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
        {
            dwDeviceID = ptClient->pLineDevices[i];
        }
        else
        {
            dwDeviceID = i;
        }

        pLookupEntry = GetLineLookupEntry (dwDeviceID);
        if (NULL == pLookupEntry)
        {
            // Something is wrong with this device ID;
            // skip it.
            continue;
        }

        dwSPIVersion = pLookupEntry->dwSPIVersion;

        if ((dwAPILowVersion <= dwSPIVersion)  &&
            !pLookupEntry->bRemoved  &&
            (pLookupEntry->ptProvider != NULL))
        {
            *pdwAPIVersion =
                (dwHighestValidAPIVersion > dwSPIVersion ?
                dwSPIVersion : dwHighestValidAPIVersion);


            //
            // Retrieve ext id (indicate no exts if GetExtID not exported)
            //

            if (!(pLookupEntry->ptProvider->apfn[SP_LINEGETEXTENSIONID]) ||

                CallSP3(
                    pLookupEntry->ptProvider->apfn[SP_LINEGETEXTENSIONID],
                    "lineGetExtensionID",
                    SP_FUNC_SYNC,
                    (DWORD) dwDeviceID,
                    (DWORD) dwSPIVersion,
                    (ULONG_PTR) pExtID

                    ) != 0)
            {
                ZeroMemory (pExtID, sizeof (LINEEXTENSIONID));
            }
        }
        else
        {
            *pdwAPIVersion = 0;
        }
    }


    //
    // Now for each phone device do negotiation
    //

    pdwAPIVersion = (LPDWORD) (pDataBuf +
        pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize);

    pExtID = (LPLINEEXTENSIONID) (pDataBuf +
        pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize +
        pParams->dwPhoneAPIVersionListSize);

    for (i = 0; i < dwNumPhoneDevices; i++, pdwAPIVersion++, pExtID++)
    {
        DWORD               dwDeviceID, dwSPIVersion;
        PTPHONELOOKUPENTRY  pLookupEntry;


        if (!IS_FLAG_SET(ptClient->dwFlags, PTCLIENT_FLAG_ADMINISTRATOR))
        {
            dwDeviceID = ptClient->pPhoneDevices[i];
        }
        else
        {
            dwDeviceID = i;
        }

        pLookupEntry = GetPhoneLookupEntry (dwDeviceID);
        if (NULL == pLookupEntry)
        {
            // Something wrong with this device;
            // skip it.
            continue;
        }

        dwSPIVersion = pLookupEntry->dwSPIVersion;

        if ((dwAPILowVersion <= dwSPIVersion)  &&
            !pLookupEntry->bRemoved  &&
            (pLookupEntry->ptProvider != NULL))
        {
            *pdwAPIVersion =
                (dwHighestValidAPIVersion > dwSPIVersion ?
                dwSPIVersion : dwHighestValidAPIVersion);


            //
            // Retrieve ext id (indicate no exts if GetExtID not exported)
            //

            if (!(pLookupEntry->ptProvider->apfn[SP_PHONEGETEXTENSIONID]) ||

                CallSP3(
                    pLookupEntry->ptProvider->apfn[SP_PHONEGETEXTENSIONID],
                    "phoneGetExtensionID",
                    SP_FUNC_SYNC,
                    (DWORD) dwDeviceID,
                    (DWORD) dwSPIVersion,
                    (ULONG_PTR) pExtID

                    ) != 0)
            {
                ZeroMemory (pExtID, sizeof (LINEEXTENSIONID));
            }
        }
        else
        {
            *pdwAPIVersion = 0;
        }
    }

    pParams->dwLineAPIVersionListOffset = 0;

    pParams->dwLineExtensionIDListOffset =
        pParams->dwLineAPIVersionListSize;

    pParams->dwPhoneAPIVersionListOffset =
        pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize;

    pParams->dwPhoneExtensionIDListOffset =
        pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize +
        pParams->dwPhoneAPIVersionListSize;

    *pdwNumBytesReturned =
        sizeof (TAPI32_MSG) +
        pParams->dwLineAPIVersionListSize +
        pParams->dwLineExtensionIDListSize +
        pParams->dwPhoneAPIVersionListSize +
        pParams->dwPhoneExtensionIDListSize;


NegotiateAPIVersionForAllDevices_exit:

#endif

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "NegotiateAPIVersionForAllDevices: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "NegotiateAPIVersionForAllDevices: exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LNegotiateExtVersion(
    PTCLIENT                        ptClient,
    PLINENEGOTIATEEXTVERSION_PARAMS pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineNegotiateExtVersion;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINENEGOTIATEEXTVERSION, // provider func index
            &pfnTSPI_lineNegotiateExtVersion,   // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "NegotiateExtVersion"       // func name

            )) == 0)
    {
        DWORD   dwSPIVersion = pLookupEntry->dwSPIVersion;


        if (!IsAPIVersionInRange(
                pParams->dwAPIVersion,
                dwSPIVersion
                ))
        {
            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto LNegotiateExtVersion_epilog;
        }

        if ((pParams->lResult = CallSP5(
                pfnTSPI_lineNegotiateExtVersion,
                "lineNegotiateExtVersion",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (DWORD) dwSPIVersion,
                (DWORD) pParams->dwExtLowVersion,
                (DWORD) pParams->dwExtHighVersion,
                (ULONG_PTR) &pParams->dwExtVersion

                )) == 0)
        {
            if (pParams->dwExtVersion == 0)
            {
                pParams->lResult = LINEERR_INCOMPATIBLEEXTVERSION;
            }
            else
            {
                *pdwNumBytesReturned = sizeof (LINENEGOTIATEEXTVERSION_PARAMS);
            }
        }
    }

LNegotiateExtVersion_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "NegotiateExtVersion"
        );
}



VOID
PASCAL
xxxLOpen(
    PTCLIENT            ptClient,
    PLINEOPEN_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned,
    BOOL                bLineMapper
    )
{
    BOOL                bCloseMutex,
                        bOpenedtLine = FALSE,
                        bDecrExtVerCountOnError = FALSE,
                        bReleasetLineMutex = FALSE,
                        bFreeCallParams = FALSE;
    LONG                lResult;
    HLINE               hLine;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    PTLINE              ptLine = NULL;
    DWORD               objectToDereference;
    PTPROVIDER          ptProvider = NULL;
    PTLINECLIENT        ptLineClient = NULL;
    PTLINELOOKUPENTRY   pLookupEntry;
    LPLINECALLPARAMS    pCallParams = NULL;
    TCHAR               szClsid[40];
    HANDLE              hLookupEntryMutex = NULL;

    szClsid[0] = 0;
    if ((lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            (DWORD) pParams->hLineApp,  // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            (bLineMapper ? "Open(LINEMAPPER)" : "Open")
                                        // func name

            )) == 0)
    {
        DWORD               dwPrivileges = pParams->dwPrivileges,
                            dwAPIVersion = pParams->dwAPIVersion,
                            dwExtVersion = pParams->dwExtVersion,
                            dwMediaModes, dwNumProxyRequestTypes,
                            dwRegisteredProxys,
                           *pdwProxyRequestTypes,
                            i;
        PTLINEAPP           ptLineApp;
        BOOL                bDuplicateOK = FALSE;


        //
        // Check if the global reinit flag is set
        //

        if (TapiGlobals.dwFlags & TAPIGLOBALS_REINIT)
        {
            lResult = LINEERR_REINIT;
            goto xxxLOpen_cleanup;
        }


        //
        // Validate params
        //
        if (!IsAPIVersionInRange(
                dwAPIVersion,
                pLookupEntry->dwSPIVersion
                ))
        {
            lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto xxxLOpen_cleanup;
        }

        ptProvider = pLookupEntry->ptProvider;


        #define VALID_LOPEN_BITS (LINECALLPRIVILEGE_NONE       | \
                                  LINECALLPRIVILEGE_MONITOR    | \
                                  LINECALLPRIVILEGE_OWNER      | \
                                  LINEOPENOPTION_SINGLEADDRESS | \
                                  LINEOPENOPTION_PROXY)

        #define VALID_PRIV_BITS  (LINECALLPRIVILEGE_NONE       | \
                                  LINECALLPRIVILEGE_MONITOR    | \
                                  LINECALLPRIVILEGE_OWNER)

        if (!(dwPrivileges & VALID_PRIV_BITS) ||

            (dwPrivileges & ~VALID_LOPEN_BITS) ||

            ((dwPrivileges & LINECALLPRIVILEGE_NONE) &&
                (dwPrivileges & (LINECALLPRIVILEGE_MONITOR |
                    LINECALLPRIVILEGE_OWNER))))
        {
            lResult = LINEERR_INVALPRIVSELECT;
            goto xxxLOpen_cleanup;
        }

        if (dwPrivileges & (LINEOPENOPTION_SINGLEADDRESS |
                LINEOPENOPTION_PROXY)  ||
            bLineMapper)
        {
            pCallParams = (LPLINECALLPARAMS)
                (pParams->dwCallParamsOffset == TAPI_NO_DATA ?
                    NULL : pDataBuf + pParams->dwCallParamsOffset);

            if (!pCallParams)
            {
                lResult = LINEERR_INVALPOINTER;
                goto xxxLOpen_cleanup;
            }


            //
            // Verify size/offset/string params given our input buffer/size
            //

            if (IsBadStructParam(
                    dwParamsBufferSize,
                    pDataBuf,
                    pParams->dwCallParamsOffset
                    ))
            {
                pParams->lResult = LINEERR_OPERATIONFAILED;
                goto xxxLOpen_cleanup;
            }

            if ((lResult = ValidateCallParams(
                    pCallParams,
                    &pCallParams,
                    dwAPIVersion,
                    pLookupEntry->dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage
                    )))
            {
                lResult = LINEERR_INVALPOINTER;
                goto xxxLOpen_cleanup;
            }

            if (pCallParams != (LPLINECALLPARAMS)
                    (pDataBuf + pParams->dwCallParamsOffset))
            {
                bFreeCallParams = TRUE;
            }

            if ((dwPrivileges & LINEOPENOPTION_SINGLEADDRESS) &&

                (pCallParams->dwAddressMode != LINEADDRESSMODE_ADDRESSID))
            {
                LOG((TL_ERROR,
                    "lineOpen(SINGLEADDRESS): callParams.dwAddressMode" \
                        "!= ADDRESSID"
                    ));

                lResult = LINEERR_INVALCALLPARAMS;
                goto xxxLOpen_cleanup;
            }

            if (dwPrivileges & LINEOPENOPTION_PROXY)
            {
                //
                // Verify the array of DWORDs (request types) in the
                // DevSpecific var field
                //

                dwNumProxyRequestTypes =
                    (pCallParams->dwDevSpecificSize & 0xfffc) /
                        sizeof (DWORD);

                if (dwNumProxyRequestTypes == 0 ||
                    dwNumProxyRequestTypes > LINEPROXYREQUEST_LASTVALUE)
                {
                    LOG((TL_ERROR,
                        "lineOpen(PROXY): inval proxy request type array "\
                            "size (callParams.dwDevSpecificSize=x%x)",
                        pCallParams->dwDevSpecificSize
                        ));

                    lResult = LINEERR_INVALCALLPARAMS;
                    goto xxxLOpen_cleanup;
                }

                //
                //  Per the TAPI (Proxy) server publishing:
                //
                //  If the HIWORD of dwDevSpecificSize is non-zero
                //  immediately after the dwDevSpecificSize/Offset
                //  field, there is a zero terminated CLSID field
                //  of the proxy server
                //
                if (HIWORD(pCallParams->dwDevSpecificSize))
                {
                    LPTSTR      lpsz;

                    lpsz = (LPTSTR) (((LPBYTE) pCallParams) +
                        pCallParams->dwDevSpecificOffset +
                        LOWORD(pCallParams->dwDevSpecificSize));
                    pCallParams->dwDevSpecificSize = 
                        LOWORD(pCallParams->dwDevSpecificSize);
                    if (_tcslen (lpsz) > sizeof(szClsid) / sizeof(TCHAR))
                    {
                        lResult = LINEERR_INVALCALLPARAMS;
                        goto xxxLOpen_cleanup;
                    }
                    _tcscpy (szClsid, lpsz);
                }

                pdwProxyRequestTypes = (LPDWORD) (((LPBYTE) pCallParams) +
                    pCallParams->dwDevSpecificOffset);

                for (i = 0; i < dwNumProxyRequestTypes; i++)
                {
                    if (*(pdwProxyRequestTypes + i) == 0 ||
                        *(pdwProxyRequestTypes + i) >
                            LINEPROXYREQUEST_LASTVALUE)
                    {
                        LOG((TL_ERROR,
                            "lineOpen(PROXY): inval proxy request type "\
                                "(x%x)",
                            *(pdwProxyRequestTypes + i)
                            ));

                        lResult = LINEERR_INVALCALLPARAMS;
                        goto xxxLOpen_cleanup;
                    }
                }
            }
        }

        if ((dwPrivileges & LINECALLPRIVILEGE_OWNER))
        {
            DWORD dwAllMediaModes;


            dwMediaModes = pParams->dwMediaModes;

            switch (dwAPIVersion)
            {
            case TAPI_VERSION1_0:

                dwAllMediaModes = AllMediaModes1_0;
                break;

            case TAPI_VERSION1_4:
            case TAPI_VERSION2_0:

                dwAllMediaModes = AllMediaModes1_4;
                break;

            //case TAPI_VERSION2_1:
            //case TAPI_VERSION2_2:
            default: //case TAPI_VERSION_CURRENT:

                dwAllMediaModes = AllMediaModes2_1;
                break;
            }

            if ((dwMediaModes == 0) ||
                (dwMediaModes & (0x00ffffff & ~dwAllMediaModes)))
            {
                lResult = LINEERR_INVALMEDIAMODE;
                goto xxxLOpen_cleanup;
            }
        }
        else
        {
            dwMediaModes = 0;
        }


        //
        // Create & init a tLineClient & associated resources
        //

        if (!(ptLineClient = ServerAlloc (sizeof(TLINECLIENT))))
        {
            lResult = LINEERR_NOMEM;
            goto xxxLOpen_cleanup;
        }

        LOG((TL_TRACE,  "lineOpen: calling NewObject. ptLineClient %p", ptLineClient));

        if (!(ptLineClient->hLine = (HLINE) NewObject(
                ghHandleTable,
                ptLineClient,
                0
                )))
        {
            ptLineClient = NULL;
            ServerFree (ptLineClient);
            lResult = LINEERR_NOMEM;
            goto xxxLOpen_cleanup;
        }

        LOG((TL_TRACE,  "lineOpen: NewObject returned %p", ptLineClient->hLine));

        ptLineClient->ptClient     = ptClient;
        ptLineClient->hRemoteLine  = (pParams->hRemoteLine ?
            (DWORD) pParams->hRemoteLine : ptLineClient->hLine);
        ptLineClient->dwAPIVersion = dwAPIVersion;
        ptLineClient->dwPrivileges = dwPrivileges;
        ptLineClient->dwMediaModes = dwMediaModes;
        ptLineClient->OpenContext  = pParams->OpenContext;
        ptLineClient->dwAddressID  =
            (dwPrivileges & LINEOPENOPTION_SINGLEADDRESS ?
                pCallParams->dwAddressID : 0xffffffff);

        LOG((TL_INFO, "lineOpen: OpenContext %p", pParams->OpenContext));

        //
        // Duplicate the handle to tLine's mutex
        // Grab the mutex using the duplicate handle then start doing the open
        //

        TapiEnterCriticalSection (&TapiGlobals.CritSec);

        if ( pLookupEntry->hMutex )
        {
            bDuplicateOK = DuplicateHandle(
                TapiGlobals.hProcess,
                pLookupEntry->hMutex,
                TapiGlobals.hProcess,
                &hLookupEntryMutex,
                0,
                FALSE,
                DUPLICATE_SAME_ACCESS
                );
        }

        TapiLeaveCriticalSection(&TapiGlobals.CritSec);

        if ( !bDuplicateOK )
        {
            bReleasetLineMutex = FALSE;
            lResult = LINEERR_OPERATIONFAILED;
            goto xxxLOpen_cleanup;
        }

xxxLOpen_waitForMutex:

        if (WaitForSingleObject (hLookupEntryMutex, INFINITE)
                != WAIT_OBJECT_0)
        {
            bReleasetLineMutex = FALSE;
            lResult = LINEERR_OPERATIONFAILED;
            goto xxxLOpen_cleanup;
        }

        bReleasetLineMutex = TRUE;


        //
        // If the tLine is in the process of being destroyed then spin
        // until it's been completely destroyed (DestroytLine() will
        // NULLify pLookupEntry->ptLine when it's finished). Make sure
        // to release the mutex while sleeping so we don't block
        // DestroytLine.
        //

        try
        {
            while (pLookupEntry->ptLine &&
                   pLookupEntry->ptLine->dwKey != TLINE_KEY)
            {
                ReleaseMutex (hLookupEntryMutex);
                Sleep (0);
                goto xxxLOpen_waitForMutex;
            }
        }
        myexcept
        {
            // If here pLookupEntry->ptLine was NULLified, safe to continue
        }

        //
        //  Check if the line has been removed
        //
        if (pLookupEntry->bRemoved)
        {
            lResult = LINEERR_BADDEVICEID;
            goto xxxLOpen_cleanup;
        }

        //
        // Validate ext ver as appropriate
        //

        if (dwExtVersion != 0 &&
            (!IsValidLineExtVersion (dwDeviceID, dwExtVersion) ||
            ptProvider->apfn[SP_LINESELECTEXTVERSION] == NULL))
        {

            if ( ptProvider->apfn[SP_LINESELECTEXTVERSION] == NULL)
            {
                LOG((TL_ERROR,
                    "The provider does not support TSPI_lineSelectExtVersion" \
                        " - that's a problem"
                    ));
            }
            else
            {
                LOG((TL_ERROR,
                    "Bad ExtVersion was passed in - that's a problem"
                    ));
            }

            lResult = LINEERR_INCOMPATIBLEEXTVERSION;
            goto xxxLOpen_cleanup;
        }


        //
        // If line isn't open already then try to open it
        //

        if (!(ptLine = pLookupEntry->ptLine))
        {
            if (!(ptLine = ServerAlloc (sizeof(TLINE))))
            {
                lResult = LINEERR_NOMEM;
                goto xxxLOpen_cleanup;
            }

            LOG((TL_TRACE,  "xxxLOpen: calling NewObject ptLine %p", ptLine));

            if (!(ptLine->hLine = (HLINE) NewObject(
                    ghHandleTable,
                    (LPVOID) ptLine,
                    NULL
                    )))
            {
                ServerFree (ptLine);
                lResult = LINEERR_NOMEM;
                goto xxxLOpen_cleanup;
            }

            ptLine->dwKey        = TINCOMPLETELINE_KEY;
            ptLine->hMutex       = pLookupEntry->hMutex;
            ptLine->ptProvider   = ptProvider;
            ptLine->dwDeviceID   = dwDeviceID;
            ptLine->dwSPIVersion = pLookupEntry->dwSPIVersion;

            {
                //
                // Hack Alert!
                //
                // We need to pass the privileges,etc through to
                // remote sp, so we make this a special case
                //

                ULONG_PTR   aParams[5];
                ULONG_PTR   param;


                if (ptProvider == pRemoteSP)
                {
                    aParams[0] = (ULONG_PTR) ptLine;
                    aParams[1] = pParams->dwPrivileges;
                    aParams[2] = pParams->dwMediaModes;
                    aParams[3] = (ULONG_PTR) pCallParams;
                    aParams[4] = dwExtVersion;

                    param = (ULONG_PTR) aParams;
                }
                else
                {
                    param = (ULONG_PTR) ptLine;
                }

                if (ptProvider->apfn[SP_LINEOPEN] == NULL)
                {
                    lResult = LINEERR_OPERATIONUNAVAIL;
                }
                if (lResult != S_OK ||
                    (lResult = CallSP5(
                        ptProvider->apfn[SP_LINEOPEN],
                        "lineOpen",
                        SP_FUNC_SYNC,
                        (DWORD) dwDeviceID,
                        (ULONG_PTR) param,
                        (ULONG_PTR) &ptLine->hdLine,
                        (DWORD) pLookupEntry->dwSPIVersion,
                        (ULONG_PTR) LineEventProcSP

                        )) != 0)
                {
                    DereferenceObject(
                        ghHandleTable,
                        ptLine->hLine,
                        1
                        );

                    lResult = (bLineMapper ? LINEERR_OPERATIONFAILED :
                        lResult);
                    goto xxxLOpen_cleanup;
                }

                bOpenedtLine = TRUE;
            }

            if (ptProvider->apfn[SP_LINEGETNUMADDRESSIDS] == NULL)
            {
                lResult = LINEERR_OPERATIONUNAVAIL;
                goto xxxLOpen_cleanup;
            }
            CallSP2(
                ptProvider->apfn[SP_LINEGETNUMADDRESSIDS],
                "lineGetNumAddressIDs",
                SP_FUNC_SYNC,
                (ULONG_PTR) ptLine->hdLine,
                (ULONG_PTR) &ptLine->dwNumAddresses
                );

            // PERF
            PerfBlock.dwLinesInUse++;
        }


        //
        // If line is already opened & client is trying to register
        // as a proxy then see if there's any conflicts with existing
        // proxys
        //

        else if (dwPrivileges & LINEOPENOPTION_PROXY)
        {
            for (i = 0; i < dwNumProxyRequestTypes; i++)
            {
                DWORD dwProxyRequestType = *(pdwProxyRequestTypes + i);


                if (ptLine->apProxys[dwProxyRequestType] != NULL)
                {
                    lResult = LINEERR_NOTREGISTERED;
                    goto xxxLOpen_cleanup;
                }
            }
        }

        ptLineClient->ptLine = ptLine;


        //
        // Verify the specified addr if appropriate
        //

        if ((dwPrivileges & LINEOPENOPTION_SINGLEADDRESS) &&

            (ptLineClient->dwAddressID >= ptLine->dwNumAddresses))
        {
            lResult = LINEERR_INVALADDRESSID;
            goto xxxLOpen_cleanup;
        }


        //
        // If the client has specified a non-zero ext version then
        // ask the driver to enable it and/or increment the ext
        // version count. If this fails, and we're processing a
        // LINEMAPPER request, then return a generic error so the
        // caller will try the next device.
        //

        if (dwExtVersion)
        {
            if (ptLine->dwExtVersionCount == 0)
            {
                if (ptProvider != pRemoteSP &&
                    ptProvider->apfn[SP_LINESELECTEXTVERSION] == NULL)
                {
                    lResult = LINEERR_OPERATIONUNAVAIL;
                    goto xxxLOpen_cleanup;
                }
                if (ptProvider != pRemoteSP  &&

                    (lResult = CallSP2(
                        ptProvider->apfn[SP_LINESELECTEXTVERSION],
                        "lineSelectExtVersion",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptLine->hdLine,
                        (DWORD) dwExtVersion

                        )) != 0)
                {
                    lResult = (bLineMapper ? LINEERR_OPERATIONFAILED :
                        lResult);
                    goto xxxLOpen_cleanup;
                }

                ptLine->dwExtVersion = dwExtVersion;
            }

            ptLineClient->dwExtVersion = dwExtVersion;
            ptLine->dwExtVersionCount++;
            bDecrExtVerCountOnError = TRUE;
        }


        //
        // If we're processing a LINEMAPPER request, check to see if the
        // device supports capabilities requested by client.  If not,
        // return a generic error so the caller will try the next device.
        //

        if (bLineMapper)
        {
            if (ptProvider->apfn[SP_LINECONDITIONALMEDIADETECTION] == NULL ||
                CallSP3(
                    ptProvider->apfn[SP_LINECONDITIONALMEDIADETECTION],
                    "lineConditionalMediaDetection",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) ptLine->hdLine,
                    (DWORD) dwMediaModes | ptLine->dwOpenMediaModes,
                    (ULONG_PTR) pCallParams

                    ) != 0)
            {
                PerfBlock.dwLinesInUse--;
                lResult = LINEERR_OPERATIONFAILED;
                goto xxxLOpen_cleanup;
            }
        }


        //
        // If the client is requesting OWNER privileges (it's interested
        // in incoming calls of the specified media mode(s)), then check
        // to see if it wants incoming calls of a media mode(s) other
        // than that the device has already agreed to indicate, and ask
        // the driver if it can support looking for all of them at the
        // same time. If this fails, and we're processing a LINEMAPPER
        // request, then return a generic error so the caller will try
        // the next device.
        //

        if (pParams->dwPrivileges & LINECALLPRIVILEGE_OWNER)
        {
            if ((dwMediaModes & ptLine->dwOpenMediaModes) != dwMediaModes)
            {
                DWORD   dwUnionMediaModes = dwMediaModes |
                                            ptLine->dwOpenMediaModes;


                if (ptProvider->apfn[SP_LINESETDEFAULTMEDIADETECTION])
                {
                    if ((lResult  = CallSP2(
                            ptProvider->apfn[SP_LINESETDEFAULTMEDIADETECTION],
                            "lineSetDefaultMediaDetection",
                            SP_FUNC_SYNC,
                            (ULONG_PTR) ptLine->hdLine,
                            (DWORD) dwUnionMediaModes

                            )) != 0)
                    {
                        lResult = (bLineMapper ? LINEERR_OPERATIONFAILED :
                                   lResult);
                        PerfBlock.dwLinesInUse--;
                        goto xxxLOpen_cleanup;
                    }
                }
                else
                {
                    lResult = LINEERR_OPERATIONUNAVAIL;
                    PerfBlock.dwLinesInUse--;
                    goto xxxLOpen_cleanup;
                }

                ptLine->dwOpenMediaModes = dwUnionMediaModes;
            }
        }


        //
        // Set the proxy ptrs if appropriate
        //

        if (dwPrivileges & LINEOPENOPTION_PROXY)
        {
            for (i = 0; i < dwNumProxyRequestTypes; i++)
            {
                ptLine->apProxys[*(pdwProxyRequestTypes + i)] =
                    ptLineClient;
            }
        }


        //
        // What proxys have opened now ?
        //

        for(
            i = LINEPROXYREQUEST_SETAGENTGROUP, dwRegisteredProxys = 0;
            i <= LINEPROXYREQUEST_LASTVALUE;
            i++
            )
        {
            if (ptLine->apProxys[i] != NULL)
            {
                //
                // Munge them all into a DWORD (if we ever
                // get more that 32 we'll have to do an
                // additional test)
                //

                dwRegisteredProxys |=  ( 1<<i );
            }
        }


        //
        // Add the tLineClient to the tLine's list & increment the
        // number of opens
        //

        if ((ptLineClient->pNextSametLine = ptLine->ptLineClients))
        {
            ptLineClient->pNextSametLine->pPrevSametLine = ptLineClient;
        }

        ptLine->ptLineClients = ptLineClient;
        ptLine->dwNumOpens++;

        if (bOpenedtLine)
        {
            pLookupEntry->ptLine = ptLine;
            ptLine->dwKey = TLINE_KEY;
        }

        ReleaseMutex (hLookupEntryMutex);

        bReleasetLineMutex = FALSE;


        //
        // Safely add the new tLineClient to the tLineApp's list.
        //

        {
            LOG((TL_TRACE,  "xxxLOpen: adding ptLineClient [%p] to tLineApp's [%p] list", ptLineClient, pParams->hLineApp));

            if ((ptLineApp = WaitForExclusiveLineAppAccess(
                    pParams->hLineApp,
                    ptClient
                    )))
            {
                if (ptLineApp->dwAPIVersion <= TAPI_VERSION3_0)
                {
                    FillMemory (
                        ptLineClient->adwEventSubMasks, 
                        sizeof(DWORD) * EM_NUM_MASKS,
                        (BYTE) 0xff
                        );
                }
                else
                {
                    CopyMemory (
                        ptLineClient->adwEventSubMasks, 
                        ptLineApp->adwEventSubMasks,
                        sizeof(DWORD) * EM_NUM_MASKS
                        );
                }
                if ((ptLineClient->pNextSametLineApp =
                        ptLineApp->ptLineClients))
                {
                    ptLineClient->pNextSametLineApp->pPrevSametLineApp =
                        ptLineClient;
                }

                ptLineApp->ptLineClients = ptLineClient;


                //
                // Note: it's important to mark the newtLineClient as
                // valid way down here because another thread could be
                // simultaneously trying to do an unconditional
                // DestroytLine (due to receiving a LINE_CLOSE, etc.)
                // and we want to make sure the tLineClient is in both
                // tLine's & tLineApp's lists before DestroytLine calls
                // DestroytLineClient which'll try to yank the tLineClient
                // out of these lists.
                //

                hLine = ptLineClient->hLine;

                ptLineClient->ptLineApp = ptLineApp;
                ptLineClient->dwKey     = TLINECLIENT_KEY;

                UNLOCKTLINEAPP(ptLineApp);


                //
                // Alert other clients that another open has occured
                //

                SendMsgToLineClients(
                    ptLine,
                    ptLineClient,
                    LINE_LINEDEVSTATE,
                    LINEDEVSTATE_OPEN,
                    0,
                    0
                    );


                //
                // Alert other clients that a proxy open has occured
                //

                if (dwPrivileges & LINEOPENOPTION_PROXY)
                {
                    // One message per LINEPROXYREQUEST_ type

                    for (i = 0; i < dwNumProxyRequestTypes; i++)
                    {
                        LOG((TL_INFO,
                            "tell clients proxy %02X opened",
                            *(pdwProxyRequestTypes + i)
                            ));

                        SendMsgToLineClients(
                            ptLine,
                            ptLineClient,
                            LINE_PROXYSTATUS,
                            LINEPROXYSTATUS_OPEN,
                            *(pdwProxyRequestTypes + i),// LINEPROXYREQUEST_xx
                            0
                            );
                    }


                    //
                    // Now see if we have all the ones required by
                    // TAPI3.0 for an ACD proxy
                    //

                    if ((dwRegisteredProxys & AllRequiredACDProxyRequests3_0)
                            == AllRequiredACDProxyRequests3_0)
                    {
                        LOG((TL_INFO,
                            "tell clients that all proxys needed " \
                                "for TAPI3.0 ACD are open"
                            ));

                        SendMsgToLineClients(
                            ptLine,
                            ptLineClient,
                            LINE_PROXYSTATUS,
                            LINEPROXYSTATUS_ALLOPENFORACD,
                            0,
                            0
                            );
                    }

                    //
                    //  If we are given a proxy server CLSID, register
                    //  the proxy server in DS, error not considered
                    //  critical here
                    //
                    if (*szClsid != 0)
                    {
                        ptLineClient->szProxyClsid = ServerAlloc (
                            (_tcslen (szClsid) + 1) * sizeof(TCHAR)
                            );
                        if (ptLineClient->szProxyClsid)
                        {
                            _tcscpy (ptLineClient->szProxyClsid, szClsid);
                            OnProxyLineOpen (szClsid);
                        }
                    }
                }


                //
                // Fill in the return values
                //

                LOG((TL_TRACE,  "xxxLOpen returning hLine of %p", hLine));

                pParams->hLine = hLine;
                *pdwNumBytesReturned = sizeof (LINEOPEN_PARAMS);


                //
                // (Now we need to return the call params if this
                // is a remote client)
                //
                // This was for some 2.1 kludge which didn't make
                // any sense, so i've chg'd to indicate NO_DATA
                // so nothing gets copied back on the client side.
                //
                // DanKn, Aug 6 '98
                //

                if (IS_REMOTE_CLIENT (ptClient))
                {
                    pParams->dwCallParamsReturnOffset = TAPI_NO_DATA;
                }
            }
            else
            {
                //
                // If here the app handle is bad, & we've some special
                // case cleanup to do.  Since the tLineClient is not
                // in the tLineApp's list, we can't simply call
                // DestroytLine(Client) to clean things up, since the
                // pointer-resetting code will blow up.  So we'll
                // grab the tLine's mutex and explicitly remove the
                // new tLineClient from it's list, then do a conditional
                // shutdown on the tLine (in case any other clients
                // have come along & opened it). Also deselect the
                // ext version and/or decrement the ext version count
                // as appropriate.
                //
                // Note: keep in mind that a LINE_CLOSE might be being
                //       processed by another thread (if so, it will be
                //       spinning on trying to destroy the tLineClient
                //       which isn't valid at this point)
                //

                lResult = LINEERR_INVALAPPHANDLE;

                WaitForSingleObject (hLookupEntryMutex, INFINITE);

                if (ptLineClient->pNextSametLine)
                {
                    ptLineClient->pNextSametLine->pPrevSametLine =
                        ptLineClient->pPrevSametLine;
                }

                if (ptLineClient->pPrevSametLine)
                {
                    ptLineClient->pPrevSametLine->pNextSametLine =
                        ptLineClient->pNextSametLine;
                }
                else
                {
                    ptLine->ptLineClients = ptLineClient->pNextSametLine;
                }

                ptLine->dwNumOpens--;

                if (bDecrExtVerCountOnError == TRUE)
                {
                    ptLine->dwExtVersionCount--;

                    if (ptLine->dwExtVersionCount == 0)
                    {
                        ptLine->dwExtVersion = 0;

                        if (ptProvider->apfn[SP_LINESELECTEXTVERSION])
                        {
                            CallSP2(
                                ptProvider->apfn[SP_LINESELECTEXTVERSION],
                                "lineSelectExtVersion",
                                SP_FUNC_SYNC,
                                (ULONG_PTR) ptLine->hdLine,
                                (DWORD) 0
                                );
                        }
                    }
                }

                ReleaseMutex (hLookupEntryMutex);

                DestroytLine (ptLine, FALSE); // conditional destroy

                bOpenedtLine = FALSE; // so we don't do err handling below
            }
        }

        CloseHandle  (hLookupEntryMutex);

    }

xxxLOpen_cleanup:

    if (bReleasetLineMutex)
    {
        if (lResult != 0)
        {
            if (bDecrExtVerCountOnError == TRUE)
            {
                ptLine->dwExtVersionCount--;

                if (ptLine->dwExtVersionCount == 0)
                {
                    ptLine->dwExtVersion = 0;

                    if (ptProvider->apfn[SP_LINESELECTEXTVERSION])
                    {
                        CallSP2(
                            ptProvider->apfn[SP_LINESELECTEXTVERSION],
                            "lineSelectExtVersion",
                            SP_FUNC_SYNC,
                            (ULONG_PTR) ptLine->hdLine,
                            (DWORD) 0
                            );
                    }
                }
            }

            if (bOpenedtLine == TRUE && ptProvider->apfn[SP_LINECLOSE])
            {
                CallSP1(
                    ptProvider->apfn[SP_LINECLOSE],
                    "lineClose",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) ptLine->hdLine
                    );
            }
        }

        ReleaseMutex (hLookupEntryMutex);
        CloseHandle  (hLookupEntryMutex);
    }

    if ((pParams->lResult = lResult) != 0)
    {
        if (ptLineClient)
        {
            DereferenceObject (ghHandleTable, ptLineClient->hLine, 1);
        }

        if (bOpenedtLine)
        {
            DereferenceObject (ghHandleTable, ptLine->hLine, 1);
        }
    }

    if (bFreeCallParams)
    {
        ServerFree (pCallParams);
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        (bLineMapper ? "Open(LINEMAPPER)" : "Open")
        );
}



void
WINAPI
LOpen(
    PTCLIENT            ptClient,
    PLINEOPEN_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    if (pParams->dwDeviceID != LINEMAPPER)
    {
        xxxLOpen(
            ptClient,
            pParams,
            dwParamsBufferSize,
            pDataBuf,
            pdwNumBytesReturned,
            FALSE
            );
    }
    else
    {
        //
        // Try to open each line device, starting with device 0, until
        // either we find a device that'll handle the capabilities
        // requested by the client or we run out of devices. If we
        // encounter a certain subset of parameter errors the first time
        // we call xxxLOpen we want to return these back to the app
        // immediately to aid debugging (rather than always returning
        // LINEMAPPERFAILED).
        //

        for(
            pParams->dwDeviceID = 0;
            pParams->dwDeviceID < TapiGlobals.dwNumLines;
            pParams->dwDeviceID++
            )
        {
            xxxLOpen(
                ptClient,
                pParams,
                dwParamsBufferSize,
                pDataBuf,
                pdwNumBytesReturned,
                TRUE
                );

            if (pParams->dwDeviceID == 0)
            {
                switch (pParams->lResult)
                {
                case LINEERR_BADDEVICEID:       // 0 line devices
                case LINEERR_INVALAPPHANDLE:
                case LINEERR_INVALCALLPARAMS:
                case LINEERR_INVALMEDIAMODE:
                case LINEERR_INVALPOINTER:      // no call params, etc
                case LINEERR_INVALPRIVSELECT:
                case LINEERR_REINIT:
                case LINEERR_UNINITIALIZED:

                    return;

                default:

                    break;
                }
            }

            if (pParams->lResult == 0)
            {
                break;
            }
        }

        if (pParams->dwDeviceID >= TapiGlobals.dwNumLines)
        {
            pParams->lResult = LINEERR_LINEMAPPERFAILED;
        }
    }

}


void
LPark_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    //
    // Note: pAsyncEventMsg->dwParam1 & dwParam2 are reserved for
    //       the request ID and result, respectively
    //

    PASYNCEVENTMSG      pNewAsyncEventMsg = (PASYNCEVENTMSG)
                            pAsyncRequestInfo->dwParam1;
    LPVARSTRING         pNonDirAddress = (LPVARSTRING) (pNewAsyncEventMsg + 1);


    CopyMemory (pNewAsyncEventMsg, pAsyncEventMsg, sizeof (ASYNCEVENTMSG));

    *ppBuf = (LPVOID) pNewAsyncEventMsg;

    if (pAsyncEventMsg->Param2 == 0)  // success
    {
        //
        // Add the used size of the non-dir addr, & keep the total
        // length of the msg DWORD-aligned
        //

        pNewAsyncEventMsg->TotalSize +=
            ((pNonDirAddress->dwUsedSize + TALIGN_COUNT) & TALIGN_MASK);

        pNewAsyncEventMsg->Param3 = 
            DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__); // hpNonDirAddr

        pNewAsyncEventMsg->Param4 = pNonDirAddress->dwUsedSize;

    }
}


void
WINAPI
LPark(
    PTCLIENT            ptClient,
    PLINEPARK_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_linePark;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwParkMode == LINEPARKMODE_DIRECTED)  &&

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDirAddressOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEPARK,                // provider func index
            &pfnTSPI_linePark,          // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Park"                      // func name

            )) > 0)
    {
        LPBYTE      pBuf;
        LPVARSTRING pNonDirAddress;


        if (pParams->dwParkMode == LINEPARKMODE_NONDIRECTED)
        {
            if (pParams->dwNonDirAddressTotalSize < sizeof (VARSTRING))
            {
                lRequestID = LINEERR_STRUCTURETOOSMALL;
                goto LPark_return;
            }

            if (!(pBuf = ServerAlloc(
                    (pParams->dwNonDirAddressTotalSize +
                        sizeof (ASYNCEVENTMSG) + TALIGN_COUNT) & TALIGN_MASK
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LPark_return;
            }

            pNonDirAddress = (LPVARSTRING) (pBuf + sizeof (ASYNCEVENTMSG));

            pNonDirAddress->dwTotalSize  = pParams->dwNonDirAddressTotalSize;
            pNonDirAddress->dwNeededSize =
            pNonDirAddress->dwUsedSize   = sizeof (VARSTRING);

            pAsyncRequestInfo->pfnPostProcess = LPark_PostProcess;
            pAsyncRequestInfo->dwParam1 = (ULONG_PTR) pBuf;
            pAsyncRequestInfo->dwParam2 = pParams->hpNonDirAddress;

            pAsyncRequestInfo->hfnClientPostProcessProc =
                pParams->hfnPostProcessProc;
        }
        else if (pParams->dwParkMode == LINEPARKMODE_DIRECTED)
        {
            pNonDirAddress = (LPVARSTRING) NULL;
        }
        else
        {
            lRequestID = LINEERR_INVALPARKMODE;
            goto LPark_return;
        }

        pParams->lResult = CallSP5(
            pfnTSPI_linePark,
            "linePark",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->dwParkMode,
            (ULONG_PTR) (pParams->dwParkMode == LINEPARKMODE_NONDIRECTED ?
                NULL : pDataBuf + pParams->dwDirAddressOffset),
            (ULONG_PTR) pNonDirAddress
            );
    }

LPark_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Park"
        );
}


void
WINAPI
LPickup(
    PTCLIENT            ptClient,
    PLINEPICKUP_PARAMS  pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_linePickup;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (((pParams->dwDestAddressOffset != TAPI_NO_DATA)  &&

            IsBadStringParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwDestAddressOffset
                ))  ||

        ((pParams->dwGroupIDOffset != TAPI_NO_DATA)  &&

            IsBadStringParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwGroupIDOffset
                )))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEPICKUP,              // provider func index
            &pfnTSPI_linePickup,        // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "Pickup"                    // func name

            )) > 0)
    {
        PTCALL          ptCall;
        //HTAPICALL       htCall;
        PTCALLCLIENT    ptCallClient;


        if (CreatetCallAndClient(
                ptLineClient,
                &ptCall,
                &ptCallClient,
                NULL,
                NULL

                ) != 0)
        {
            lRequestID = LINEERR_NOMEM;
            goto LPickup_return;
        }

        //htCall = ptCall->hCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpCall;
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP7(
            pfnTSPI_linePickup,
            "linePickup",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) ptCall,
            (ULONG_PTR) &ptCall->hdCall,
            (ULONG_PTR) (pParams->dwDestAddressOffset == TAPI_NO_DATA ? 0 :
                pDataBuf + pParams->dwDestAddressOffset),
            (ULONG_PTR) (pParams->dwGroupIDOffset == TAPI_NO_DATA ? 0 :
                pDataBuf + pParams->dwGroupIDOffset)
            );

        SetDrvCallFlags(
            ptCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );
    }

LPickup_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Pickup"
        );
}


void
WINAPI
LPrepareAddToConference(
    PTCLIENT                            ptClient,
    PLINEPREPAREADDTOCONFERENCE_PARAMS  pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdConfCall;
    TSPIPROC            pfnTSPI_linePrepareAddToConference;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptConfCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hConfCall, // client widget handle
            (LPVOID) &hdConfCall,       // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEPREPAREADDTOCONFERENCE,  // provider func index
            &pfnTSPI_linePrepareAddToConference,// provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptConfCallClient,          // context
            "PrepareAddToConference"    // func name

            )) > 0)
    {
        LONG                lResult;
        PTCALL              ptConsultCall;
        //HTAPICALL           htConsultCall;
        PTCALLCLIENT        ptConsultCallClient;
        PTLINECLIENT        ptLineClient;
        LPLINECALLPARAMS    pCallParamsApp, pCallParamsSP;


        //
        // Verify size/offset/string params given our input buffer/size
        //

        if ((pParams->dwCallParamsOffset != TAPI_NO_DATA)  &&

            IsBadStructParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwCallParamsOffset
                ))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LPrepareAddToConference_return;
        }


        pCallParamsApp = (LPLINECALLPARAMS)
            (pParams->dwCallParamsOffset == TAPI_NO_DATA ?
                0 : pDataBuf + pParams->dwCallParamsOffset);

        try
        {
            //
            // Safely get the ptLineClient
            //

            ptLineClient = ptConfCallClient->ptLineClient;


            //
            // Make sure the hConfCall is really a conf parent
            //

            {
                PTCALL  ptCall;


                ptCall = (PTCALL) ptConfCallClient->ptCall;

                if ((ptCall->pConfList)->aptCalls[0] != ptCall)
                {
                    lRequestID = LINEERR_INVALCONFCALLHANDLE;
                    goto LPrepareAddToConference_return;
                }
            }
        }
        myexcept
        {
            //
            // If here the conf call was destroyed
            //

            lRequestID = LINEERR_INVALCONFCALLHANDLE;
            goto LPrepareAddToConference_return;
        }

        if (pCallParamsApp)
        {
            DWORD   dwAPIVersion, dwSPIVersion;


            if (GetLineVersions(
                    ptLineClient,
                    &dwAPIVersion,
                    &dwSPIVersion

                    ) != 0)
            {
                lRequestID = LINEERR_INVALCONFCALLHANDLE;
                goto LPrepareAddToConference_return;
            }

            if ((lResult = ValidateCallParams(
                    pCallParamsApp,
                    &pCallParamsSP,
                    dwAPIVersion,
                    dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage

                    )) != 0)
            {
                lRequestID = lResult;
                goto LPrepareAddToConference_return;
            }
        }
        else
        {
            pCallParamsSP = (LPLINECALLPARAMS) NULL;
        }

        if (CreatetCallAndClient(
                ptLineClient,
                &ptConsultCall,
                &ptConsultCallClient,
                pCallParamsSP,
                NULL

                ) != 0)
        {
            lRequestID = LINEERR_NOMEM;
            goto LPrepareAddToConference_freeCallParams;
        }

        //htConsultCall = ptConsultCall->htCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptConsultCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptConsultCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpConsultCall;
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptConsultCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP5(
            pfnTSPI_linePrepareAddToConference,
            "linePrepareAddToConference",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdConfCall,
            (ULONG_PTR) ptConsultCall,
            (ULONG_PTR) &ptConsultCall->hdCall,
            (ULONG_PTR) pCallParamsSP
            );

        SetDrvCallFlags(
            ptConsultCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

LPrepareAddToConference_freeCallParams:

        if (pCallParamsSP != pCallParamsApp)
        {
            ServerFree (pCallParamsSP);
        }
    }

LPrepareAddToConference_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "PrepareAddToConference"
        );
}


void
WINAPI
LProxyMessage(
    PTCLIENT                    ptClient,
    PLINEPROXYMESSAGE_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_NONE,                    // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "ProxyMessage"              // func name

            )) == 0)
    {
        DWORD           dwMsg = pParams->dwMsg, i;
        PTCALL          ptCall;
        PTLINE          ptLine;
        TPOINTERLIST    clientList, *pClientList = &clientList;
        ASYNCEVENTMSG   msg[2];


        //
        // Verify params
        //

        try
        {
            ptLine = ptLineClient->ptLine;

            if (!(ptLineClient->dwPrivileges & LINEOPENOPTION_PROXY))
            {
                pParams->lResult = LINEERR_NOTREGISTERED;
                goto LProxyMessage_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LProxyMessage_epilog;
        }

        switch (dwMsg)
        {
        case LINE_AGENTSTATUS:

            // ignore the hCall param

            if (pParams->dwParam1 >= ptLine->dwNumAddresses)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (AGENTSTATUS): dwParam1 " \
                        "bad addr ID (=x%x, num addrs=x%x)",
                    pParams->dwParam1,
                    ptLine->dwNumAddresses
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam2 == 0 ||
                    pParams->dwParam2 & ~AllAgentStatus)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (AGENTSTATUS): dwParam2 " \
                        "(=x%x) bad LINEAGENTSTATUS_ flags",
                    pParams->dwParam2
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam2 & LINEAGENTSTATUS_STATE)
            {
                if (!IsOnlyOneBitSetInDWORD (pParams->dwParam3) ||
                    pParams->dwParam3 & ~AllAgentStates)
                {
                    LOG((TL_ERROR,
                        "ERROR: lineProxyMessage (AGENTSTATUS): " \
                            "dwParam3 (=x%x) bad LINEAGENTSTATE_ flags",
                        pParams->dwParam3
                        ));

                    pParams->lResult = LINEERR_INVALPARAM;
                    goto LProxyMessage_epilog;
                }
            }
            else if (pParams->dwParam3 != 0)
            {
                // don't bother complaining about a non-zero dwParam3

                pParams->dwParam3 = 0;
            }

            break;

        case LINE_AGENTSPECIFIC:

            // ignore dwParam1, dwParam2, & dwParam3 (app-specific)

            if (pParams->hCall)
            {
                PTCALLCLIENT    ptCallClient;


                if (!(ptCallClient = ReferenceCall(
                        pParams->hCall,
                        ptClient
                        )))
                {
                    pParams->lResult = LINEERR_INVALCALLHANDLE;
                    goto LProxyMessage_epilog;
                }

                ptCall = ptCallClient->ptCall;

                DereferenceObject (ghHandleTable, pParams->hCall, 1);

                goto LProxyMessage_fwdMsgToCallClients;
            }

            break;

        case LINE_AGENTSESSIONSTATUS:
            // ignore the hCall param

            // ignore the dwParam1 , it's the agent handle

            if (pParams->dwParam2 == 0 ||
                    pParams->dwParam2 & ~AllAgentSessionStatus)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (AGENTSESSIONSTATUS): dwParam2 " \
                        "(=x%x) bad LINEAGENTSESSIONSTATUS_ flags",
                    pParams->dwParam2
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam2 & LINEAGENTSESSIONSTATUS_STATE)
            {
                if (!IsOnlyOneBitSetInDWORD (pParams->dwParam3) ||
                    pParams->dwParam3 & ~AllAgentSessionStates)
                {
                    LOG((TL_ERROR,
                        "ERROR: lineProxyMessage (AGENTSESSIONSTATUS): " \
                            "dwParam3 (=x%x) bad LINEAGENTSESSIONSTATE_ flags",
                        pParams->dwParam3
                        ));

                    pParams->lResult = LINEERR_INVALPARAM;
                    goto LProxyMessage_epilog;
                }
            }
            else if (pParams->dwParam3 != 0)
            {
                // don't bother complaining about a non-zero dwParam3

                pParams->dwParam3 = 0;
            }

            break;

        case LINE_AGENTSTATUSEX:
            // ignore the hCall param

            // ignore the dwParam1 , it's the agent handle

            if (pParams->dwParam2 == 0 ||
                    pParams->dwParam2 & ~AllAgentStatusEx)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (AGENTSTATUSEX): dwParam2 " \
                        "(=x%x) bad LINEAGENTSTATUSEX_ flags",
                    pParams->dwParam2
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam2 & LINEAGENTSESSIONSTATUS_STATE)
            {
                if (!IsOnlyOneBitSetInDWORD (pParams->dwParam3) ||
                    pParams->dwParam3 & ~AllAgentStatesEx)
                {
                    LOG((TL_ERROR,
                        "ERROR: lineProxyMessage (AGENTSTATUSEX): " \
                            "dwParam3 (=x%x) bad LINEAGENTSTATEEX_ flags",
                        pParams->dwParam3
                        ));

                    pParams->lResult = LINEERR_INVALPARAM;
                    goto LProxyMessage_epilog;
                }
            }
            else if (pParams->dwParam3 != 0)
            {
                // don't bother complaining about a non-zero dwParam3

                pParams->dwParam3 = 0;
            }

            break;

        case LINE_QUEUESTATUS:
            // ignore the hCall param

            // ignore the dwParam1 , it's the queue handle

            if (pParams->dwParam2 == 0 ||
                    pParams->dwParam2 & ~AllQueueStatus)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (QUEUESTATUS): dwParam2 " \
                        "(=x%x) bad LINEQUEUESTATUS_ flags",
                    pParams->dwParam2
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam3 != 0)
            {
                // don't bother complaining about a non-zero dwParam3

                pParams->dwParam3 = 0;
            }

            break;

        case LINE_GROUPSTATUS:
            // ignore the hCall param

            if (pParams->dwParam1 == 0 ||
                    pParams->dwParam1 & ~AllGroupStatus)
            {
                LOG((TL_ERROR,
                    "ERROR: lineProxyMessage (GROUPSTATUS): dwParam1 " \
                        "(=x%x) bad LINEQUEUESTATUS_ flags",
                    pParams->dwParam1
                    ));

                pParams->lResult = LINEERR_INVALPARAM;
                goto LProxyMessage_epilog;
            }
            else if (pParams->dwParam2 != 0)
            {
                // don't bother complaining about a non-zero dwParam2

                pParams->dwParam2 = 0;
            }
            else if (pParams->dwParam3 != 0)
            {
                // don't bother complaining about a non-zero dwParam3

                pParams->dwParam3 = 0;
            }

            break;

        default:

            LOG((TL_ERROR,
                "ERROR : lineProxyMessage: inval dwMsg (=x%x)",
                pParams->dwMsg
                ));

            pParams->lResult = LINEERR_INVALPARAM;
            goto LProxyMessage_epilog;

        } // switch (dwMsg)


        //
        // Fwd this msg on to all line's clients who say they support
        // >= TAPI_VERSION2_0 (not including the proxy's line client)
        //

        if ((pParams->lResult = GetLineClientListFromLine(
                ptLine,
                &pClientList

                )) != 0)
        {
            goto LProxyMessage_epilog;
        }

        msg->TotalSize          = sizeof (ASYNCEVENTMSG);
        msg->fnPostProcessProcHandle = 0;
        msg->Msg                = dwMsg;
        msg->Param1             = pParams->dwParam1;
        msg->Param2             = pParams->dwParam2;
        msg->Param3             = pParams->dwParam3;
        msg->Param4             = 0;

        for (i = 0; i < pClientList->dwNumUsedEntries; i++)
        {
            PTLINECLIENT    ptLineClient2 = (PTLINECLIENT)
                                pClientList->aEntries[i];


            if (ptLineClient2 != ptLineClient)
            {
                try
                {
                    if (ptLineClient2->ptLineApp->dwAPIVersion >=
                            TAPI_VERSION2_0)
                    {
                        if (FMsgDisabled (
                            ptLineClient2->ptLineApp->dwAPIVersion,
                            ptLineClient2->adwEventSubMasks,
                            dwMsg,
                            pParams->dwParam1
                            ))
                        {
                            continue;
                        }
                    
                        msg->InitContext =
                            ptLineClient2->ptLineApp->InitContext;
                        msg->hDevice     = ptLineClient2->hRemoteLine;
                        msg->OpenContext = ptLineClient2->OpenContext;


                        //
                        // Now a final check to make sure all the
                        // params are valid before sending the msg
                        //

                        {
                            PTCLIENT ptClient = ptLineClient2->ptClient;


                            if (ptLineClient2->dwKey == TLINECLIENT_KEY)
                            {
                                WriteEventBuffer (ptClient, msg);
                            }
                        }
                    }
                }
                myexcept
                {
                    // just continue
                }
            }
        }

        goto LProxyMessage_freeClientList;


        //
        // Fwd this msg on to all call's clients who say they support
        // >= TAPI_VERSION2_0 (not including the proxy's line client)
        //

LProxyMessage_fwdMsgToCallClients:

        if ((pParams->lResult = GetCallClientListFromCall(
                ptCall,
                &pClientList

                )) != 0)
        {
            goto LProxyMessage_epilog;
        }

        msg->TotalSize          = sizeof (ASYNCEVENTMSG) + sizeof (HCALLHUB);
        msg->fnPostProcessProcHandle = 0;
        msg->Msg                = dwMsg;
        msg->Param1             = pParams->dwParam1;
        msg->Param2             = pParams->dwParam2;
        msg->Param3             = pParams->dwParam3;

        for (i = 0; i < pClientList->dwNumUsedEntries; i++)
        {
            PTCALLCLIENT    ptCallClient = (PTCALLCLIENT)
                                pClientList->aEntries[i];


            try
            {
                if (ptCallClient->hCall != pParams->hCall)
                {
                        PTLINEAPP   ptLineApp;


                        ptLineApp = ptCallClient->ptLineClient->ptLineApp;

                        if (ptLineApp->dwAPIVersion >= TAPI_VERSION2_0)
                        {
                            if (FMsgDisabled (
                                ptCallClient->ptLineClient->ptLineApp->dwAPIVersion,
                                ptCallClient->adwEventSubMasks,
                                dwMsg,
                                pParams->dwParam1
                                ))
                            {
                                continue;
                            }
                            
                            msg->InitContext = ptLineApp->InitContext;
                            msg->hDevice     = ptCallClient->hCall;
                            msg->OpenContext =
                                ptCallClient->ptLineClient->OpenContext;
                            msg->Param4       =
                                ptCallClient->ptLineClient->hRemoteLine;


                            *((LPHCALLHUB)(&msg->Param4 + 1)) = 
                                    (ptCallClient->ptCallHubClient)?
                                        ptCallClient->ptCallHubClient->hCallHub : 
                                        (HCALLHUB)(ULONG_PTR)NULL;

                            //
                            // Now a final check to make sure all the
                            // params are valid before sending the msg
                            //

                            {
                                PTCLIENT ptClient = ptCallClient->ptClient;


                                if (ptCallClient->dwKey == TCALLCLIENT_KEY)
                                {
                                    WriteEventBuffer (ptClient, msg);
                                }
                            }
                        }
                }
            }
            myexcept
            {
                // just continue
            }
        }

LProxyMessage_freeClientList:

        if (pClientList != &clientList)
        {
            ServerFree (pClientList);
        }


    }

LProxyMessage_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "ProxyMessage"
        );
}


void
WINAPI
LProxyResponse(
    PTCLIENT                    ptClient,
    PLINEPROXYRESPONSE_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT    pProxy;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_NONE,                    // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pProxy,                    // context
            "ProxyResponse"             // func name

            )) == 0)
    {
        PASYNCREQUESTINFO   pAsyncRequestInfo;


        //
        // The dwInstance param is the dwLocalRequestID for the
        // AsyncRequestInfo struct.  Note that the dwKey value of
        // this struct was reset to == pProxy->hLine in
        // CreateProxyRequest().
        //

        if (!(pAsyncRequestInfo = ReferenceObject(
                ghHandleTable,
                pParams->dwInstance,
                pParams->hLine
                )))
        {
                pParams->dwResult = LINEERR_OPERATIONFAILED;
                goto LProxyResponse_Epilog;
        }


        //
        // Safely remove the proxy request from the list of pending requests
        //

        if (WaitForExclusiveLineClientAccess (pProxy))
        {
            if (pAsyncRequestInfo->dwKey == pParams->hLine)
            {
                pAsyncRequestInfo->dwKey = TASYNC_KEY;

                if (pAsyncRequestInfo->dwParam5)
                {
                    ((PASYNCREQUESTINFO) pAsyncRequestInfo->dwParam5)->dwParam4 =
                        pAsyncRequestInfo->dwParam4;
                }

                if (pAsyncRequestInfo->dwParam4)
                {
                    ((PASYNCREQUESTINFO) pAsyncRequestInfo->dwParam4)->dwParam5 =
                        pAsyncRequestInfo->dwParam5;
                }
                else
                {
                    pProxy->pPendingProxyRequests = (PASYNCREQUESTINFO)
                        pAsyncRequestInfo->dwParam5;
                }

                UNLOCKTLINECLIENT(pProxy);
            }
            else
            {
                //
                // Another thread must have simultaneously been
                // completing this request, so fail gracefully
                //

                UNLOCKTLINECLIENT(pProxy);
                DereferenceObject (ghHandleTable, pParams->dwInstance, 1);
                pParams->dwResult = LINEERR_OPERATIONFAILED;
                goto LProxyResponse_Epilog;
            }
        }
        else
        {
            DereferenceObject (ghHandleTable, pParams->dwInstance, 1);
            pParams->dwResult = LINEERR_INVALLINEHANDLE;
            goto LProxyResponse_Epilog;
        }


        //
        // If this is a proxy request where there's data to be returned
        // to the client (aside from the result) then we want to alloc
        // a buffer & fill it with the data.  We'll make it look like a
        // DevSpecific request that just completed, and have the DevSpecfic
        // post process routine deal with it.
        //
        // Make sure buffers are 64-bit aligned
        //

        if (pParams->dwProxyResponseOffset != TAPI_NO_DATA &&
            pParams->dwResult == 0)
        {
            DWORD               dwSize;
            LPBYTE              pBuf;
            LPLINEPROXYREQUEST  pProxyRequest = (LPLINEPROXYREQUEST)
                                    ( pDataBuf + pParams->dwProxyResponseOffset );

            switch (pProxyRequest->dwRequestType)
            {
            case LINEPROXYREQUEST_AGENTSPECIFIC:
            {
                dwSize = pProxyRequest->AgentSpecific.dwSize;

                if (!(pBuf = ServerAlloc(
                        sizeof (ASYNCEVENTMSG) + ((dwSize + 7) & 0xfffffff8)
                        )))
                {
                    pParams->dwResult = LINEERR_NOMEM;
                    goto LProxyResponse_completeRequest;
                }

                CopyMemory(
                    pBuf + sizeof (ASYNCEVENTMSG),
                    pProxyRequest->AgentSpecific.Params,
                    dwSize
                    );

                break;
            }
            case LINEPROXYREQUEST_CREATEAGENT:
            case LINEPROXYREQUEST_CREATEAGENTSESSION:
            {
                //
                // Proces both of these together.  the handle is
                // the first member of both structures
                //

                dwSize = sizeof (HAGENT);

                pBuf = ServerAlloc(
                    sizeof(ASYNCEVENTMSG) + ((dwSize + 7) & 0xfffffff8)
                    );

                if ( NULL == pBuf )
                {
                    pParams->dwResult = LINEERR_NOMEM;
                    goto LProxyResponse_completeRequest;
                }

                CopyMemory(
                    pBuf + sizeof(ASYNCEVENTMSG),
                    &(pProxyRequest->CreateAgent.hAgent),
                    dwSize
                    );

                break;
            }
            case LINEPROXYREQUEST_GETQUEUELIST:
            {
                dwSize = pProxyRequest->GetQueueList.QueueList.dwUsedSize;

                pBuf = ServerAlloc(
                    sizeof(ASYNCEVENTMSG) + ((dwSize + 7) & 0xfffffff8)
                    );

                if ( NULL == pBuf )
                {
                    pParams->dwResult = LINEERR_NOMEM;
                    goto LProxyResponse_completeRequest;
                }

                CopyMemory(
                    pBuf + sizeof(ASYNCEVENTMSG),
                    &(pProxyRequest->GetQueueList.QueueList),
                    dwSize
                    );

                break;
            }
            case LINEPROXYREQUEST_GETGROUPLIST:
            {
                dwSize = pProxyRequest->GetGroupList.GroupList.dwUsedSize;

                pBuf = ServerAlloc(
                    sizeof(ASYNCEVENTMSG) + ((dwSize + 7) & 0xfffffff8)
                    );

                if ( NULL == pBuf )
                {
                    pParams->dwResult = LINEERR_NOMEM;
                    goto LProxyResponse_completeRequest;
                }

                CopyMemory(
                    pBuf + sizeof(ASYNCEVENTMSG),
                    &(pProxyRequest->GetGroupList.GroupList),
                    dwSize
                    );

                break;
            }
            case LINEPROXYREQUEST_GETAGENTCAPS:
            case LINEPROXYREQUEST_GETAGENTSTATUS:
            case LINEPROXYREQUEST_GETAGENTACTIVITYLIST:
            case LINEPROXYREQUEST_GETAGENTGROUPLIST:
            case LINEPROXYREQUEST_GETAGENTINFO:
            case LINEPROXYREQUEST_GETAGENTSESSIONLIST:
            case LINEPROXYREQUEST_SETAGENTSESSIONSTATE:
            case LINEPROXYREQUEST_GETAGENTSESSIONINFO:
            case LINEPROXYREQUEST_GETQUEUEINFO:
            {
                dwSize = pProxyRequest->GetAgentCaps.AgentCaps.dwUsedSize;

                if (!(pBuf = ServerAlloc(
                        sizeof (ASYNCEVENTMSG) + ((dwSize + 7) & 0xfffffff8)
                        )))
                {
                    pParams->dwResult = LINEERR_NOMEM;
                    goto LProxyResponse_completeRequest;
                }

                CopyMemory(
                    pBuf + sizeof (ASYNCEVENTMSG),
                    &pProxyRequest->GetAgentCaps.AgentCaps,
                    dwSize
                    );

                break;
            }
            default:

                LOG((TL_ERROR,
                    "LProxyResponse: unrecognized proxy request type (x%x)",
                    pProxyRequest->dwRequestType
                    ));

                pParams->dwResult = LINEERR_OPERATIONFAILED;
                goto LProxyResponse_completeRequest;
            }

            //
            // Check to see if the size of the data we're copying back is
            // larger than the size of client's buffer
            //

            if (dwSize > pAsyncRequestInfo->dwParam2)
            {
                LOG((TL_ERROR,
                    "LProxyResponse: data size too large (exp<=x%x,act=x%x)",
                    pAsyncRequestInfo->dwParam2,
                    dwSize
                    ));

                ServerFree (pBuf);
                pParams->dwResult = LINEERR_OPERATIONFAILED;
                goto LProxyResponse_completeRequest;
            }


            pAsyncRequestInfo->pfnPostProcess = LDevSpecific_PostProcess;
            pAsyncRequestInfo->dwParam2       = dwSize;
            pAsyncRequestInfo->dwParam3       = (ULONG_PTR) pBuf;
        }


        //
        // Now call the deferred completion proc with the "request id"
        // & result, just like a provider would
        //

LProxyResponse_completeRequest:

        CompletionProcSP(
            pAsyncRequestInfo->dwLocalRequestID,
            pParams->dwResult
            );

        DereferenceObject (ghHandleTable, pParams->dwInstance, 1);
    }

LProxyResponse_Epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "ProxyResponse"
        );

}


void
WINAPI
LRedirect(
    PTCLIENT                ptClient,
    PLINEREDIRECT_PARAMS    pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineRedirect;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDestAddressOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEREDIRECT,            // provider func index
            &pfnTSPI_lineRedirect,      // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Redirect"                  // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineRedirect,
            "lineRedirect",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pDataBuf + pParams->dwDestAddressOffset),
            (DWORD) pParams->dwCountryCode
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Redirect"
        );
}


void
WINAPI
LRegisterRequestRecipient(
    PTCLIENT                                ptClient,
    PLINEREGISTERREQUESTRECIPIENT_PARAMS    pParams,
    DWORD                                   dwParamsBufferSize,
    LPBYTE                                  pDataBuf,
    LPDWORD                                 pdwNumBytesReturned
    )
{
    PTLINEAPP   ptLineApp;


    if ((ptLineApp = WaitForExclusiveLineAppAccess(
            pParams->hLineApp,
            ptClient
            )))
    {
        DWORD   dwRequestMode = pParams->dwRequestMode;


        if (!(dwRequestMode &
                (LINEREQUESTMODE_MAKECALL | LINEREQUESTMODE_MEDIACALL)) ||
            (dwRequestMode &
                (~(LINEREQUESTMODE_MAKECALL | LINEREQUESTMODE_MEDIACALL))))
        {
            pParams->lResult = LINEERR_INVALREQUESTMODE;
            goto LRegisterRequestRecipient_myReleaseMutex;
        }

        if (pParams->bEnable)
        {
            //
            // If app wants MEDIACALL requests see if already registered
            //

            if ((dwRequestMode & LINEREQUESTMODE_MEDIACALL) &&
                ptLineApp->bReqMediaCallRecipient)
            {
                LOG((TL_ERROR, "App is already registered for mediacall"));
                pParams->lResult = LINEERR_OPERATIONFAILED;
                goto LRegisterRequestRecipient_myReleaseMutex;
            }


            //
            // If app wants MAKECALL requests see if already registered,
            // then prepare a request recipient object & add it to the
            // global list
            //

            if (dwRequestMode & LINEREQUESTMODE_MAKECALL)
            {
                if (!ptLineApp->pRequestRecipient)
                {
                    //
                    // Add to request recipient list
                    //

                    PTREQUESTRECIPIENT  pRequestRecipient;


                    if (!(pRequestRecipient= (PTREQUESTRECIPIENT) ServerAlloc(
                            sizeof (TREQUESTRECIPIENT)
                            )))
                    {
                        LOG((TL_ERROR, "Failed alloc for requestrecip struct"));
                        pParams->lResult = LINEERR_NOMEM;
                        goto LRegisterRequestRecipient_myReleaseMutex;
                    }

                    pRequestRecipient->ptLineApp = ptLineApp;
                    pRequestRecipient->dwRegistrationInstance =
                        pParams->dwRegistrationInstance;

                    EnterCriticalSection (&gPriorityListCritSec);

                    if ((pRequestRecipient->pNext =
                            TapiGlobals.pRequestRecipients))
                    {
                        pRequestRecipient->pNext->pPrev = pRequestRecipient;
                    }

                    TapiGlobals.pRequestRecipients = pRequestRecipient;

                    LeaveCriticalSection (&gPriorityListCritSec);

                    ptLineApp->pRequestRecipient = pRequestRecipient;

                    TapiGlobals.pHighestPriorityRequestRecipient =
                         GetHighestPriorityRequestRecipient();

                    if (TapiGlobals.pRequestMakeCallList)
                    {
                        NotifyHighestPriorityRequestRecipient();
                    }
                }
                else // already registered
                {
                    LOG((TL_ERROR, "App is already registered for makecall"));
                    pParams->lResult = LINEERR_OPERATIONFAILED;
                    goto LRegisterRequestRecipient_myReleaseMutex;
                }
            }


            //
            // Now register app for MEDIACALL reqs as appropriate
            //

            ptLineApp->bReqMediaCallRecipient =
                (dwRequestMode & LINEREQUESTMODE_MEDIACALL ?
                1 : ptLineApp->bReqMediaCallRecipient);
        }
        else
        {
            //
            // If apps doesn't want MEDIACALL requests see if not registered
            //

            if ((dwRequestMode & LINEREQUESTMODE_MEDIACALL) &&
                !ptLineApp->bReqMediaCallRecipient)
            {
                LOG((TL_ERROR, "App is not registered for mediacall"));
                pParams->lResult = LINEERR_OPERATIONFAILED;
                goto LRegisterRequestRecipient_myReleaseMutex;
            }


            //
            // If app doesn't want MAKECALL requests see if already
            // registered, then remove it's request recipient object
            // from the global list
            //

            if (dwRequestMode & LINEREQUESTMODE_MAKECALL)
            {
                if (ptLineApp->pRequestRecipient)
                {
                    //
                    // Remove from request recipient list
                    //

                    PTREQUESTRECIPIENT  pRequestRecipient =
                                            ptLineApp->pRequestRecipient;


                    EnterCriticalSection (&gPriorityListCritSec);

                    if (pRequestRecipient->pNext)
                    {
                        pRequestRecipient->pNext->pPrev =
                            pRequestRecipient->pPrev;
                    }

                    if (pRequestRecipient->pPrev)
                    {
                        pRequestRecipient->pPrev->pNext =
                            pRequestRecipient->pNext;
                    }
                    else
                    {
                        TapiGlobals.pRequestRecipients =
                            pRequestRecipient->pNext;
                    }

                    LeaveCriticalSection (&gPriorityListCritSec);

                    ServerFree (pRequestRecipient);

                    ptLineApp->pRequestRecipient = NULL;


                    //
                    // Reset the highest priority request recipient, then check
                    // to see if there's any pending request make calls
                    //

                    TapiGlobals.pHighestPriorityRequestRecipient =
                        GetHighestPriorityRequestRecipient();

                    if (TapiGlobals.pRequestMakeCallList)
                    {
                        if (TapiGlobals.pHighestPriorityRequestRecipient)
                        {
                            NotifyHighestPriorityRequestRecipient();
                        }

                        else
                        {
                            //
                            // We couldn't start a request recipient so
                            // nuke all pending request make calls
                            //

                            PTREQUESTMAKECALL   pRequestMakeCall,
                                                pNextRequestMakeCall;


                            pRequestMakeCall =
                                TapiGlobals.pRequestMakeCallList;

                            TapiGlobals.pRequestMakeCallList    =
                            TapiGlobals.pRequestMakeCallListEnd = NULL;

                            while (pRequestMakeCall)
                            {
                                pNextRequestMakeCall = pRequestMakeCall->pNext;
                                ServerFree (pRequestMakeCall);
                                pRequestMakeCall =  pNextRequestMakeCall;
                            }

                            LOG((TL_INFO,
                                "LRegisterRequestRecipient: deleting " \
                                    "pending MakeCall requests"
                                ));
                        }
                    }
                }
                else // not registered
                {
                    LOG((TL_ERROR, "App is not registered for makecall"));
                    pParams->lResult = LINEERR_OPERATIONFAILED;
                }
            }


            //
            // Now deregister app for MEDIACALL reqs as appropriate
            //

            ptLineApp->bReqMediaCallRecipient =
                (dwRequestMode & LINEREQUESTMODE_MEDIACALL ?
                0 : ptLineApp->bReqMediaCallRecipient);
        }

LRegisterRequestRecipient_myReleaseMutex:

        UNLOCKTLINEAPP(ptLineApp);
    }
    else
    {
        pParams->lResult = (TapiGlobals.dwNumLineInits == 0 ?
            LINEERR_UNINITIALIZED : LINEERR_INVALAPPHANDLE);
    }

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
			"lineRegisterRequestRecipient: exit, returning %s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
			"lineRegisterRequestRecipient: exit, returning x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LReleaseUserUserInfo(
    PTCLIENT            ptClient,
    PLINEDIAL_PARAMS    pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineReleaseUserUserInfo;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINERELEASEUSERUSERINFO, // provider func index
            &pfnTSPI_lineReleaseUserUserInfo,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "ReleaseUserUserInfo"       // func name

            )) > 0)
    {
        pParams->lResult = CallSP2(
            pfnTSPI_lineReleaseUserUserInfo,
            "lineReleaseUserUserInfo",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "ReleaseUserUserInfo"
        );
}


void
LRemoveFromConference_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    if (pAsyncEventMsg->Param2 == 0)
    {
        PTCALL ptCall = (PTCALL) pAsyncRequestInfo->dwParam1;


        SetCallConfList (ptCall, (PTCONFERENCELIST) NULL, FALSE);
    }
}


void
WINAPI
LRemoveFromConference(
    PTCLIENT                            ptClient,
    PLINEREMOVEFROMCONFERENCE_PARAMS    pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineRemoveFromConference;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEREMOVEFROMCONFERENCE,// provider func index
            &pfnTSPI_lineRemoveFromConference,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "RemoveFromConference"      // func name

            )) > 0)
    {
        PTCALL ptCall;


        //
        // Safely make sure the call is currently conferenced &
        // that it's not a conf parent
        //

        try
        {
            PTCONFERENCELIST pConfList;


            ptCall = ptCallClient->ptCall;

            pConfList = ptCall->pConfList;

            if (!pConfList ||
                (pConfList == (LPVOID) LongToPtr(0xffffffff)) ||
                (pConfList->aptCalls[0] == ptCall))
            {
                lRequestID = LINEERR_INVALCALLSTATE;
                goto LRemoveFromConference_return;
            }
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LRemoveFromConference_return;
        }

        //
        // Set up the async request struct & call the SP
        //

        pAsyncRequestInfo->pfnPostProcess = LRemoveFromConference_PostProcess;
        pAsyncRequestInfo->dwParam1       = (ULONG_PTR) ptCall;

        pParams->lResult = CallSP2(
            pfnTSPI_lineRemoveFromConference,
            "lineRemoveFromConference",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall
            );
    }

LRemoveFromConference_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "RemoveFromConference"
        );
}


void
WINAPI
LSecureCall(
    PTCLIENT                ptClient,
    PLINESECURECALL_PARAMS  pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    DWORD               objectToDereference;
    TSPIPROC            pfnTSPI_lineSecureCall;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESECURECALL,          // provider func index
            &pfnTSPI_lineSecureCall,    // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SecureCall"                // func name

            )) > 0)
    {
        pParams->lResult = CallSP2(
            pfnTSPI_lineSecureCall,
            "lineSecureCall",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SecureCall"
        );
}


void
WINAPI
LSelectExtVersion(
    PTCLIENT                        ptClient,
    PLINESELECTEXTVERSION_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex, bCloseMutex2;
    HANDLE              hMutex, hMutex2;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineSelectExtVersion;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESELECTEXTVERSION,    // provider func index
            &pfnTSPI_lineSelectExtVersion,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SelectExtVersion"          // func name

            )) == 0)
    {
        if (WaitForExclusivetLineAccess(
                ptLineClient->ptLine,
                &hMutex2,
                &bCloseMutex2,
                INFINITE
                ))
        {
            if (IsValidLineExtVersion(
                    ptLineClient->ptLine->dwDeviceID,
                    pParams->dwExtVersion
                    ))
            {
                if (pParams->dwExtVersion)
                {
                    if (ptLineClient->ptLine->dwExtVersionCount  ||

                        (pParams->lResult = CallSP2(
                            pfnTSPI_lineSelectExtVersion,
                            "lineSelectExtVersion",
                            SP_FUNC_SYNC,
                            (ULONG_PTR) hdLine,
                            (DWORD) pParams->dwExtVersion

                            )) == 0)
                    {
                        ptLineClient->dwExtVersion =
                        ptLineClient->ptLine->dwExtVersion =
                            pParams->dwExtVersion;
                        ptLineClient->ptLine->dwExtVersionCount++;

                    }
                }
                else if (ptLineClient->ptLine->dwExtVersionCount)
                {
                    if (--ptLineClient->ptLine->dwExtVersionCount == 0)
                    {
                        CallSP2(
                            pfnTSPI_lineSelectExtVersion,
                            "lineSelectExtVersion",
                            SP_FUNC_SYNC,
                            (ULONG_PTR) hdLine,
                            (DWORD) 0
                            );

                        ptLineClient->ptLine->dwExtVersion = 0;
                    }

                    ptLineClient->dwExtVersion = 0;
                }
            }
            else
            {
                pParams->lResult = LINEERR_INCOMPATIBLEEXTVERSION;
            }

            MyReleaseMutex (hMutex2, bCloseMutex2);
        }
        else
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetDefaultMediaDetection"
        );

}


void
WINAPI
LSendUserUserInfo(
    PTCLIENT                        ptClient,
    PLINESENDUSERUSERINFO_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSendUserUserInfo;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwUserUserInfoOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSize,
            pParams->dwUserUserInfoOffset,
            sizeof(DWORD),
            "LSendUserUserInfo",
            "pParams->UserUserInfo"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESENDUSERUSERINFO,    // provider func index
            &pfnTSPI_lineSendUserUserInfo,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SendUserUserInfo"          // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineSendUserUserInfo,
            "lineSendUserUserInfo",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwUserUserInfoOffset),
            (DWORD) (pParams->dwUserUserInfoOffset == TAPI_NO_DATA ? 0 :
                pParams->dwSize)
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SendUserUserInfo"
        );
}


void
WINAPI
LSetAppPriority(
    PTCLIENT                    ptClient,
    PLINESETAPPPRIORITY_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    DWORD   dwMediaMode   = pParams->dwMediaMode,
            dwRequestMode = pParams->dwRequestMode,
            dwPriority    = pParams->dwPriority;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwAppNameOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if (dwMediaMode == 0)
    {
        if ((dwRequestMode != LINEREQUESTMODE_MAKECALL) &&
            (dwRequestMode != LINEREQUESTMODE_MEDIACALL))
        {
            pParams->lResult = LINEERR_INVALREQUESTMODE;
            goto LSetAppPriority_return;
        }
    }
    else if ( dwMediaMode & ~AllMediaModes2_1 )
    {
        pParams->lResult = LINEERR_INVALMEDIAMODE;
        goto LSetAppPriority_return;
    }



    if ((dwPriority & 0xfffffffe))
    {
        pParams->lResult = LINEERR_INVALPARAM;
        goto LSetAppPriority_return;
    }


    if ((dwMediaMode & 0x00ffffff) || (dwMediaMode == 0))
    {
        WCHAR   szModuleName[MAX_PATH];
        WCHAR  *pszCurrentPriorityList, **ppszCurrentPriorityList;
        WCHAR  *pszLocationInPriorityList;
        DWORD   dwAppNameLength;
        DWORD   dwCount;


        szModuleName[0] = '"';
        wcscpy(szModuleName + 1, (PWSTR)(pDataBuf + pParams->dwAppNameOffset));
        _wcsupr(szModuleName + 1);
        dwAppNameLength = (DWORD) lstrlenW(szModuleName);


        //
        // Enter the pri list critical section before we start munging
        //

        EnterCriticalSection (&gPriorityListCritSec);


        //
        // Determine which of the priority lists we want to look at
        //

        if  (dwMediaMode & 0x00ffffff)
        {
            pszCurrentPriorityList = NULL;
            ppszCurrentPriorityList = NULL;
            for(
                dwCount = 0;
                dwCount < TapiGlobals.dwUsedPriorityLists;
                dwCount++
                )
            {
                // did we find it?
                if (dwMediaMode == TapiGlobals.pPriLists[dwCount].dwMediaModes)
                {
                    ppszCurrentPriorityList =
                        &(TapiGlobals.pPriLists[dwCount].pszPriList);
                    pszCurrentPriorityList = *ppszCurrentPriorityList;
                    break;
                }
            }

            // did we find it?
            if (NULL == ppszCurrentPriorityList)
            {
                // are we setting
                if (pParams->dwPriority != 0)
                {
                    // do we need to alloc more space?
                    if (TapiGlobals.dwUsedPriorityLists ==
                            TapiGlobals.dwTotalPriorityLists)
                    {
                        PRILISTSTRUCT * pNewList;

                        pNewList = (PRILISTSTRUCT *)ServerAlloc(
                            sizeof (PRILISTSTRUCT) *
                                TapiGlobals.dwTotalPriorityLists * 2
                            );

                        if (NULL == pNewList)
                        {
                            LOG((TL_ERROR, "Alloc failed in LineSetAppPriority"));
                            pParams->lResult = LINEERR_NOMEM;

                            goto LSetAppPriority_return;
                        }

                        CopyMemory(
                            pNewList,
                            TapiGlobals.pPriLists,
                            sizeof( PRILISTSTRUCT ) *
                                TapiGlobals.dwUsedPriorityLists
                            );

                        ServerFree(TapiGlobals.pPriLists);

                        TapiGlobals.pPriLists = pNewList;
                        TapiGlobals.dwTotalPriorityLists *= 2;
                    }

                    TapiGlobals.pPriLists[TapiGlobals.dwUsedPriorityLists].
                        dwMediaModes = dwMediaMode;
                    ppszCurrentPriorityList = &(TapiGlobals.pPriLists
                        [TapiGlobals.dwUsedPriorityLists].pszPriList);
                    pszCurrentPriorityList = *ppszCurrentPriorityList;
                    TapiGlobals.dwUsedPriorityLists++;
                }

            }
        }
        else
        {
            ppszCurrentPriorityList = (dwRequestMode==LINEREQUESTMODE_MAKECALL
                ? &TapiGlobals.pszReqMakeCallPriList :
                &TapiGlobals.pszReqMediaCallPriList);

            pszCurrentPriorityList = *ppszCurrentPriorityList;
        }


        LOG((TL_INFO,
            "LSetAppPri: priList=%ls",
            (pszCurrentPriorityList ? pszCurrentPriorityList : L"<empty>")
            ));


        //
        // Add app to priority list
        //

        if (pParams->dwPriority)
        {
            if (pszCurrentPriorityList &&

                (pszLocationInPriorityList = wcsstr(
                    pszCurrentPriorityList,
                    szModuleName
                    )))
            {
                //
                // App already in list. If app not currently at front of
                // list then move it to front.
                //

                if (pszLocationInPriorityList != pszCurrentPriorityList)
                {
                    MoveMemory(
                        pszCurrentPriorityList + dwAppNameLength,
                        pszCurrentPriorityList,
                        (pszLocationInPriorityList - pszCurrentPriorityList) *
                            sizeof(WCHAR)
                        );

                    wcscpy(pszCurrentPriorityList, szModuleName);

                    pszCurrentPriorityList[dwAppNameLength] = '"';
                }
            }
            else
            {
                //
                // App not in list, so create a new list
                //

                WCHAR *pszNewPriorityList;


                if (!(pszNewPriorityList = ServerAlloc(
                      sizeof(WCHAR) *
                         (dwAppNameLength + (pszCurrentPriorityList ?
                            lstrlenW(pszCurrentPriorityList) : 0) +
                            1)   // for terminating NULL
                        )))
                {
                    pParams->lResult = LINEERR_NOMEM;
                }
                else
                {
                    wcscpy(pszNewPriorityList, szModuleName);

                    if (pszCurrentPriorityList)
                    {
                        wcscat(pszNewPriorityList, pszCurrentPriorityList);
                        ServerFree (pszCurrentPriorityList);
                    }

                    *ppszCurrentPriorityList = pszNewPriorityList;
                }
            }
        }


        //
        // Remove app from priority list for specified media mode
        //
        // Note: We currently do not alloc a smaller buffer to store
        //       the new list in, we just use the existing one.
        //

        else
        {
            if (pszCurrentPriorityList &&

                (pszLocationInPriorityList = wcsstr(
                    pszCurrentPriorityList,
                    szModuleName
                    )))
            {
                if (*(pszLocationInPriorityList + dwAppNameLength) != 0)
                {
                    //
                    // This is not the last app in the list, so move
                    // following apps up one notch in the list
                    //

                    wcscpy(
                        pszLocationInPriorityList,
                        pszLocationInPriorityList + dwAppNameLength
                        );
                }
                else if (pszLocationInPriorityList == pszCurrentPriorityList)
                {
                    //
                    // This is the only app in the list, so free the buffer
                    // & set the global pointer to NULL
                    //

                    ServerFree (pszCurrentPriorityList);
                    *ppszCurrentPriorityList = NULL;
                }
                else
                {
                    //
                    // This is the last app in the list, so just mark this as
                    // the end of the list
                    //

                    *pszLocationInPriorityList = 0;
                }
            }
        }


        //
        // We're done munging, so leave the pri list crit sec
        //

        LeaveCriticalSection (&gPriorityListCritSec);
    }

LSetAppPriority_return:


    LOG((TL_TRACE, 
		"LineEpilogSync (lineSetAppPriority) exit, returning x%x",
        pParams->lResult
        ));
}


void
WINAPI
LSetAgentActivity(
    PTCLIENT                        ptClient,
    PLINESETAGENTACTIVITY_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentActivity"          // func name

            )) > 0)
    {
       LONG            lResult;
       DWORD           dwDeviceID;
       PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                pParams->dwAddressID,
                LINEPROXYREQUEST_SETAGENTACTIVITY,
                &pProxy,
                &dwDeviceID,
                0               // API ver wasn't checked in 2.0
                )))
        {
            lRequestID = lResult;
            goto LSetAgentActivity_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTACTIVITY,
                    2 * sizeof (DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentActivity_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.SetAgentActivity.dwAddressID  =
                pParams->dwAddressID;
            pProxyRequestWrapper->ProxyRequest.SetAgentActivity.dwActivityID =
                pParams->dwActivityID;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentActivity_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP4(
                pRemoteSP->apfn[SP_LINESETAGENTACTIVITY],
                "lineSetAgentActivity",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwAddressID,
                (DWORD) pParams->dwActivityID
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentActivity_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentActivity"
        );
}


void
WINAPI
LSetAgentGroup(
    PTCLIENT                    ptClient,
    PLINESETAGENTGROUP_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentGroup"             // func name

            )) > 0)
    {
        LONG                    lResult;
        DWORD                   dwDeviceID;
        PTLINECLIENT            pProxy;
        LPLINEAGENTGROUPLIST    pGroupList = (LPLINEAGENTGROUPLIST)
                                    (pDataBuf + pParams->dwAgentGroupListOffset);


        //
        // Verify size/offset/string params given our input buffer/size
        //

        if (IsBadStructParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwAgentGroupListOffset
                ))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LSetAgentGroup_epilog;
        }


        //
        // Param verification...
        //

        {
            DWORD                   dwTotalSize = pGroupList->dwTotalSize;


            if (dwTotalSize < sizeof (LINEAGENTGROUPLIST))
            {
                lRequestID = LINEERR_STRUCTURETOOSMALL;
                goto LSetAgentGroup_epilog;
            }

            if (ISBADSIZEOFFSET(
                    dwTotalSize,
                    sizeof (LINEAGENTGROUPLIST),
                    pGroupList->dwListSize,
                    pGroupList->dwListOffset,
                    guiAlignmentFaultEnabled? sizeof(DWORD) : 0,
                    "lineSetAgentGroup",
                    "List"
                    ))
            {
                lRequestID = LINEERR_INVALAGENTGROUP;
                goto LSetAgentGroup_epilog;
            }

            if (pGroupList->dwNumEntries >
                    ((dwTotalSize - sizeof (LINEAGENTGROUPLIST)) /
                        sizeof (LINEAGENTGROUPENTRY)))
            {
                lRequestID = LINEERR_INVALAGENTGROUP;
                goto LSetAgentGroup_epilog;
            }
        }

        if ((lResult = FindProxy(
                ptLineClient,
                pParams->dwAddressID,
                LINEPROXYREQUEST_SETAGENTGROUP,
                &pProxy,
                &dwDeviceID,
                0               // API ver wasn't checked in 2.0
                )))
        {
            lRequestID = lResult;
            goto LSetAgentGroup_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTGROUP,
                    sizeof (DWORD) + pGroupList->dwTotalSize,
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentGroup_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.SetAgentGroup.dwAddressID  =
                pParams->dwAddressID;

            CopyMemory(
                &pProxyRequestWrapper->ProxyRequest.SetAgentGroup.GroupList,
                pGroupList,
                pGroupList->dwTotalSize
                );

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentGroup_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP4(
                pRemoteSP->apfn[SP_LINESETAGENTGROUP],
                "lineSetAgentGroup",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwAddressID,
                (ULONG_PTR) pGroupList
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentGroup_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentGroup"
        );
}


void
WINAPI
LSetAgentMeasurementPeriod(
    PTCLIENT                                ptClient,
    PLINESETAGENTMEASUREMENTPERIOD_PARAMS   pParams,
    DWORD                                   dwParamsBufferSize,
    LPBYTE                                  pDataBuf,
    LPDWORD                                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentMeasurementPeriod" // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID;
        PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_SETAGENTMEASUREMENTPERIOD,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LSetAgentMeasurementPeriod_epilog;
        }


        // Measurement period must be > 0
        if (pParams->dwMeasurementPeriod == 0)
        {
            lRequestID = LINEERR_INVALPARAM;
            goto LSetAgentMeasurementPeriod_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTMEASUREMENTPERIOD,
                    sizeof (DWORD) + sizeof (HAGENT),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentMeasurementPeriod_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.
                SetAgentMeasurementPeriod.hAgent = pParams->hAgent;

            pProxyRequestWrapper->ProxyRequest.
                SetAgentMeasurementPeriod.dwMeasurementPeriod =
                pParams->dwMeasurementPeriod;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentMeasurementPeriod_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP4(
                pRemoteSP->apfn[SP_LINESETAGENTMEASUREMENTPERIOD],
                "lineSetAgentMeasurementPeriod",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->hAgent,
                (DWORD) pParams->dwMeasurementPeriod
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentMeasurementPeriod_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentMeasurementPeriod"
        );
}


void
WINAPI
LSetAgentSessionState(
    PTCLIENT                            ptClient,
    PLINESETAGENTSESSIONSTATE_PARAMS    pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentSessionState"      // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID;
        DWORD           dwAgentState     = pParams->dwAgentState;
        DWORD           dwNextAgentState = pParams->dwNextAgentState;
        PTLINECLIENT    pProxy;


        //
        // Param verification...
        //

        if (dwAgentState == 0  &&  dwNextAgentState == 0)
        {
            lRequestID = LINEERR_INVALAGENTSESSIONSTATE;
            goto LSetAgentSessionState_epilog;
        }

        if (dwAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwAgentState) ||
            dwAgentState & ~AllAgentSessionStates))
        {
            lRequestID = LINEERR_INVALAGENTSESSIONSTATE;
            goto LSetAgentSessionState_epilog;
        }

        if (dwNextAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwNextAgentState) ||
            dwNextAgentState & ~AllAgentSessionStates))
        {
            lRequestID = LINEERR_INVALAGENTSESSIONSTATE;
            goto LSetAgentSessionState_epilog;
        }


        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_SETAGENTSESSIONSTATE,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LSetAgentSessionState_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTSESSIONSTATE,
                    2 * sizeof (DWORD) + sizeof (HAGENT),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentSessionState_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.
                SetAgentSessionState.hAgentSession = pParams->hAgentSession;

            pProxyRequestWrapper->ProxyRequest.
                SetAgentSessionState.dwAgentSessionState = dwAgentState;

            pProxyRequestWrapper->ProxyRequest.
                SetAgentSessionState.dwNextAgentSessionState = dwNextAgentState;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentSessionState_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP5(
                pRemoteSP->apfn[SP_LINESETAGENTSESSIONSTATE],
                "lineSetAgentSessionState",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->hAgentSession,
                (DWORD) pParams->dwAgentState,
                (DWORD) pParams->dwNextAgentState
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentSessionState_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentSessionState"
        );
}


void
WINAPI
LSetAgentState(
    PTCLIENT                    ptClient,
    PLINESETAGENTSTATE_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentState"             // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID,
                        dwAddressID      = pParams->dwAddressID,
                        dwAgentState     = pParams->dwAgentState,
                        dwNextAgentState = pParams->dwNextAgentState;
        PTLINECLIENT    pProxy;


        //
        // Param verification...
        //

        if (dwAgentState == 0  &&  dwNextAgentState == 0)
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentState_epilog;
        }

        if (dwAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwAgentState) ||
            dwAgentState & ~AllAgentStates))
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentState_epilog;
        }

        if (dwNextAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwNextAgentState) ||
            dwNextAgentState & ~AllAgentStates))
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentState_epilog;
        }

        if ((lResult = FindProxy(
                ptLineClient,
                dwAddressID,
                LINEPROXYREQUEST_SETAGENTSTATE,
                &pProxy,
                &dwDeviceID,
                0               // API ver wasn't checked in 2.0
                )))
        {
            lRequestID = lResult;
            goto LSetAgentState_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTSTATE,
                    3 * sizeof (DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentState_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.SetAgentState.dwAddressID =
                dwAddressID;
            pProxyRequestWrapper->ProxyRequest.SetAgentState.dwAgentState =
                dwAgentState;
            pProxyRequestWrapper->ProxyRequest.SetAgentState.dwNextAgentState =
                dwNextAgentState;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentState_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP5(
                pRemoteSP->apfn[SP_LINESETAGENTSTATE],
                "lineSetAgentState",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) dwAddressID,
                (DWORD) dwAgentState,
                (DWORD) dwNextAgentState
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentState_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentState"
        );

}


void
WINAPI
LSetAgentStateEx(
    PTCLIENT                            ptClient,
    PLINESETAGENTSTATEEX_PARAMS         pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


   if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetAgentStateEx"           // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID;
        DWORD           dwAgentState     = pParams->dwAgentState;
        DWORD           dwNextAgentState = pParams->dwNextAgentState;
        PTLINECLIENT    pProxy;


        //
        // Param verification...
        //

        if (dwAgentState == 0  &&  dwNextAgentState == 0)
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentStateEx_epilog;
        }

        if (dwAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwAgentState) ||
            dwAgentState & ~AllAgentStatesEx))
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentStateEx_epilog;
        }

        if (dwNextAgentState != 0 &&
            (!IsOnlyOneBitSetInDWORD (dwNextAgentState) ||
            dwNextAgentState & ~AllAgentStatesEx))
        {
            lRequestID = LINEERR_INVALAGENTSTATE;
            goto LSetAgentStateEx_epilog;
        }


        //
        // Find Proxy
        //

        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_SETAGENTSTATEEX,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LSetAgentStateEx_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETAGENTSTATEEX,
                    2 * sizeof (DWORD) + sizeof (HAGENT),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentStateEx_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.SetAgentStateEx.hAgent = pParams->hAgent;
            pProxyRequestWrapper->ProxyRequest.SetAgentStateEx.dwAgentState = dwAgentState;
            pProxyRequestWrapper->ProxyRequest.SetAgentStateEx.dwNextAgentState = dwNextAgentState;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetAgentStateEx_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP5(
                pRemoteSP->apfn[SP_LINESETAGENTSTATEEX],
                "lineSetAgentStateEx",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->hAgent,
                (DWORD) dwAgentState,
                (DWORD) dwNextAgentState
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetAgentStateEx_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetAgentStateEx"
        );
}


void
WINAPI
LSetAppSpecific(
    PTCLIENT                    ptClient,
    PLINESETAPPSPECIFIC_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetAppSpecific;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETAPPSPECIFIC,      // provider func index
            &pfnTSPI_lineSetAppSpecific,// provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetAppSpecific"            // func name

            )) == 0)
    {
        pParams->lResult = CallSP2(
            pfnTSPI_lineSetAppSpecific,
            "lineSetAppSpecific",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->dwAppSpecific
            );
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetAppSpecific"
        );
}


void
WINAPI
LSetCallData(
    PTCLIENT                ptClient,
    PLINESETCALLDATA_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetCallData;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwCallDataSize,
            pParams->dwCallDataOffset,
            sizeof(DWORD),
            "LSetCallData",
            "pParams->CallData"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
                ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETCALLDATA,         // provider func index
            &pfnTSPI_lineSetCallData,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetCallData"               // func name

            )) > 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineSetCallData,
            "lineSetCallData",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pParams->dwCallDataSize ?
                pDataBuf + pParams->dwCallDataOffset : NULL),
            (DWORD) pParams->dwCallDataSize
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetCallData"
        );
}


void
WINAPI
LSetCallHubTracking(
    PTCLIENT                        ptClient,
    PLINESETCALLHUBTRACKING_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineSetCallHubTracking;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStructParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwTrackingInfoOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult =LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETCALLHUBTRACKING,  // provider func index
            &pfnTSPI_lineSetCallHubTracking,// provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetCallHubTracking"        // func name

            )) == 0  ||

        (pParams->lResult == LINEERR_OPERATIONUNAVAIL))
    {
        BOOL                       bOwnMutex, bSetSPTracking;
        DWORD                      dwNewSPTracking;
        PTLINE                     ptLine;
        PTLINELOOKUPENTRY          pLookupEntry;
        LPLINECALLHUBTRACKINGINFO  pTrackingInfo;


        if (pParams->lResult == LINEERR_OPERATIONUNAVAIL)
        {
            pParams->lResult = 0;
            pfnTSPI_lineSetCallHubTracking = (TSPIPROC) NULL;
        }


        //
        // Check validity of tracking info structure
        //

        pTrackingInfo = (LPLINECALLHUBTRACKINGINFO)
            (pDataBuf + pParams->dwTrackingInfoOffset);

        if (pTrackingInfo->dwTotalSize < sizeof (LINECALLHUBTRACKINGINFO))
        {
            pParams->lResult = LINEERR_STRUCTURETOOSMALL;
            goto LSetCallHubTracking_epilog;
        }


        //
        // Check for invalid options
        //

        switch (pTrackingInfo->dwCurrentTracking)
        {
        case LINECALLHUBTRACKING_NONE:
        case LINECALLHUBTRACKING_ALLCALLS:

            break;

        case (LINECALLHUBTRACKING_ALLCALLS |LINECALLHUBTRACKING_PROVIDERLEVEL):

            if (!pfnTSPI_lineSetCallHubTracking)
            {
                pParams->lResult = LINEERR_INVALPARAM;
                goto LSetCallHubTracking_epilog;
            }
            break;

        default:

            pParams->lResult = LINEERR_INVALPARAM;
            goto LSetCallHubTracking_epilog;
        }


        //
        // Safely get exclusive access to the tLine, then check if:
        //
        //   * new tLineClient tracking state equals current tLineClient
        //     tracking state, in which case we can simply return success, or
        //
        //   * new tLineClient tracking state has no net effect on the
        //     driver's line tracking state due to the existing number
        //     of trackers, in which case we can simply adjust the current
        //     number of trackers and return success, or
        //
        //   * (otherwise) we need to inform driver of new tracking state
        //

        bOwnMutex = FALSE;

        try
        {
            pLookupEntry = GetLineLookupEntry(
                ptLineClient->ptLine->dwDeviceID
                );

            if (!pLookupEntry)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LSetCallHubTracking_epilog;
            }

            WaitForSingleObject (pLookupEntry->hMutex, INFINITE);

            bOwnMutex = TRUE;

            if ((ptLineClient->dwKey != TLINECLIENT_KEY)  ||
                !(ptLine = pLookupEntry->ptLine))
            {
                ReleaseMutex (pLookupEntry->hMutex);
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LSetCallHubTracking_epilog;
            }
        }
        except (EXCEPTION_EXECUTE_HANDLER)
        {
            if (bOwnMutex)
            {
                ReleaseMutex (pLookupEntry->hMutex);
            }

            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LSetCallHubTracking_epilog;
        }

        if (pTrackingInfo->dwCurrentTracking ==
                ptLineClient->dwCurrentTracking)
        {
             goto LSetCallHubTracking_releaseMutex;
        }


        bSetSPTracking = FALSE;

        switch (pTrackingInfo->dwCurrentTracking)
        {
        case LINECALLHUBTRACKING_NONE:
        case LINECALLHUBTRACKING_ALLCALLS:

            if (ptLineClient->dwCurrentTracking &
                    LINECALLHUBTRACKING_PROVIDERLEVEL)
            {
                if (ptLine->dwNumCallHubTrackersSPLevel == 1)
                {
                    //
                    // We're the only one with SP-level tracking
                    // currently enabled, so call SP to turn OFF
                    // tracking
                    //

                    bSetSPTracking  = TRUE;
                    dwNewSPTracking = LINECALLHUBTRACKING_NONE;
                }
            }

            break;

        default : // CALLHUBTRACKING_ALLCALLS | CALLHUBTRACKING_PROVIDERLEVEL

            if (ptLine->dwNumCallHubTrackersSPLevel > 0)
            {
                //
                // We're the only one with SP-level tracking
                // currently enabled, so call SP to turn ON
                // tracking
                //

                bSetSPTracking  = TRUE;
                dwNewSPTracking = LINECALLHUBTRACKING_ALLCALLS |
                    LINECALLHUBTRACKING_PROVIDERLEVEL;
            }

            break;
        }

        if (bSetSPTracking  &&  pfnTSPI_lineSetCallHubTracking)
        {
            LINECALLHUBTRACKINGINFO info;


            info.dwTotalSize         =
            info.dwNeededSize        =
            info.dwUsedSize          = sizeof (info);
            info.dwAvailableTracking = 0;
            info.dwCurrentTracking   = dwNewSPTracking;

            pParams->lResult = CallSP2(
                pfnTSPI_lineSetCallHubTracking,
                "lineSetCallHubTracking",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) &info
                );
        }

        if (pParams->lResult == 0)
        {
            switch (pTrackingInfo->dwCurrentTracking)
            {
            case LINECALLHUBTRACKING_NONE:

                ptLine->dwNumCallHubTrackers--;

                if (ptLineClient->dwCurrentTracking &
                        LINECALLHUBTRACKING_PROVIDERLEVEL)
                {
                    ptLine->dwNumCallHubTrackersSPLevel--;
                }

                break;

            case LINECALLHUBTRACKING_ALLCALLS:

                if (ptLineClient->dwCurrentTracking ==
                        LINECALLHUBTRACKING_NONE)
                {
                    ptLine->dwNumCallHubTrackers++;
                }
                else
                {
                    ptLine->dwNumCallHubTrackersSPLevel--;
                }

                break;

            default: // CALLHUBTRACKING_ALLCALLS |CALLHUBTRACKING_PROVIDERLEVEL

                if (ptLineClient->dwCurrentTracking ==
                        LINECALLHUBTRACKING_NONE)
                {
                    ptLine->dwNumCallHubTrackers++;
                }

                ptLine->dwNumCallHubTrackersSPLevel++;

                break;
            }

            ptLineClient->dwCurrentTracking = pTrackingInfo->dwCurrentTracking;
        }

LSetCallHubTracking_releaseMutex:

        ReleaseMutex (ptLine->hMutex);
    }

LSetCallHubTracking_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetCallHubTracking"
        );
}


void
WINAPI
LSetCallParams(
    PTCLIENT                    ptClient,
    PLINESETCALLPARAMS_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetCallParams;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if ((pParams->dwDialParamsOffset != TAPI_NO_DATA)  &&

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            sizeof (LINEDIALPARAMS),
            pParams->dwDialParamsOffset,
            sizeof(DWORD),
            "LSetCallParams",
            "pParams->DialParams"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETCALLPARAMS,       // provider func index
            &pfnTSPI_lineSetCallParams, // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetCallParams"             // func name

            )) > 0)
    {
        DWORD   dwAPIVersion, dwAllBearerModes,
                dwBearerMode = pParams->dwBearerMode;


        //
        // Safely get the API ver associated with this call & make sure
        // no invalid bearer modes are specified (high 16 bearer mode
        // bits are extensions)
        //

        try
        {
            dwAPIVersion = ptCallClient->ptLineClient->dwAPIVersion;
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSetCallParams_epilog;
        }

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:

            dwAllBearerModes = AllBearerModes1_0;
            break;

        case TAPI_VERSION1_4:

            dwAllBearerModes = AllBearerModes1_4;
            break;

        case TAPI_VERSION2_0:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_2:
        case TAPI_VERSION3_0:
        case TAPI_VERSION_CURRENT:

            dwAllBearerModes = AllBearerModes2_0;
            break;

        default:

            lRequestID = LINEERR_INVALBEARERMODE;
            goto LSetCallParams_epilog;
        }

        if (!IsOnlyOneBitSetInDWORD(dwBearerMode) ||
            (dwBearerMode & ~(dwAllBearerModes | 0xffff0000)))
        {
            lRequestID = LINEERR_INVALBEARERMODE;
            goto LSetCallParams_epilog;
        }

        pParams->lResult = CallSP6(
            pfnTSPI_lineSetCallParams,
            "lineSetCallParams",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->dwBearerMode,
            (DWORD) pParams->dwMinRate,
            (DWORD) pParams->dwMaxRate,
            (ULONG_PTR) (pParams->dwDialParamsOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwDialParamsOffset)
            );
    }

LSetCallParams_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetCallParams"
        );
}


void
WINAPI
LSetCallPrivilege(
    PTCLIENT                        ptClient,
    PLINESETCALLPRIVILEGE_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    PTCALL          ptCall;
    PTCALLCLIENT    ptCallClient;


    if (!(ptCallClient = ReferenceObject(
            ghHandleTable,
            pParams->hCall,
            TCALLCLIENT_KEY
            )))
    {
        pParams->lResult = (TapiGlobals.dwNumLineInits ?
            LINEERR_INVALCALLHANDLE : LINEERR_UNINITIALIZED);
        goto LSetCallPrivilege_epilog;
    }


    //
    // Don't both with LineProlog, since we need a try/except
    // to get the ptCall anyway
    //

    try
    {
        ptCall = ptCallClient->ptCall;

        if (ptCallClient->dwKey != TCALLCLIENT_KEY  ||
            ptCallClient->ptClient != ptClient)
        {
            pParams->lResult = (TapiGlobals.dwNumLineInits ?
                LINEERR_INVALCALLHANDLE : LINEERR_UNINITIALIZED);
            goto LSetCallPrivilege_Dereference;
        }
    }
    myexcept
    {
        pParams->lResult = (TapiGlobals.dwNumLineInits ?
            LINEERR_INVALCALLHANDLE : LINEERR_UNINITIALIZED);
        goto LSetCallPrivilege_Dereference;
    }

    if ((pParams->dwPrivilege != LINECALLPRIVILEGE_MONITOR) &&
        (pParams->dwPrivilege != LINECALLPRIVILEGE_OWNER))
    {
        pParams->lResult = LINEERR_INVALCALLPRIVILEGE;
        goto LSetCallPrivilege_Dereference;
    }

    if (WaitForExclusivetCallAccess (ptCall, TCALL_KEY))
    {
        //
        // Make sure the tCallClient is still valid
        //

        try
        {
            if (ptCallClient->dwKey != TCALLCLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LSetCallPrivilege_UnlocktCall;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LSetCallPrivilege_UnlocktCall;
        }

        if (pParams->dwPrivilege != ptCallClient->dwPrivilege)
        {
//            if (ptCallClient->dwPrivilege == LINECALLPRIVILEGE_OWNER &&
//                ptCall->dwNumOwners == 1 &&
//                ptCall->dwCallState != LINECALLSTATE_IDLE)
//            {
//                pParams->lResult = LINEERR_INVALCALLSTATE;
//                goto LSetCallPrivilege_releaseMutex;
//            }

            if (pParams->dwPrivilege == LINECALLPRIVILEGE_OWNER)
            {
                ptCall->dwNumOwners++;
                ptCall->dwNumMonitors--;
            }
            else
            {
                ptCall->dwNumOwners--;
                ptCall->dwNumMonitors++;
            }

            ptCallClient->dwPrivilege = pParams->dwPrivilege;

            UNLOCKTCALL(ptCall);

            SendMsgToCallClients(
                ptCall,
                ptCallClient,
                LINE_CALLINFO,
                LINECALLINFOSTATE_NUMMONITORS |
                    (pParams->dwPrivilege == LINECALLPRIVILEGE_OWNER ?
                        LINECALLINFOSTATE_NUMOWNERINCR :
                        LINECALLINFOSTATE_NUMOWNERDECR),
                0,
                0
                );

        }
        else
        {

LSetCallPrivilege_UnlocktCall:

            UNLOCKTCALL(ptCall);
        }
    }
    else
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
    }
LSetCallPrivilege_Dereference:

    DereferenceObject (ghHandleTable, pParams->hCall, 1);

LSetCallPrivilege_epilog:

#if DBG

    {
        char szResult[32];

        LOG((TL_TRACE, 
            "LineEpilogSync: (lineSetCallPrivilege) exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }

#else
        LOG((TL_TRACE, 
            "LineEpilogSync: (lineSetCallPrivilege) exit, result=x%x",
            pParams->lResult
            ));
#endif

    return;
}


void
WINAPI
LSetCallQualityOfService(
    PTCLIENT                            ptClient,
    PLINESETCALLQUALITYOFSERVICE_PARAMS pParams,
    DWORD                               dwParamsBufferSize,
    LPBYTE                              pDataBuf,
    LPDWORD                             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetCallQualityOfService;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSendingFlowspecSize,
            pParams->dwSendingFlowspecOffset,
            sizeof(DWORD),
            "LSetCallQualityOfService",
            "pParams->SendingFlowspec"
            )  ||

        ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwReceivingFlowspecSize,
            pParams->dwReceivingFlowspecOffset,
            sizeof(DWORD),
            "LSetCallQualityOfService",
            "pParams->ReceivingFlowspec"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETCALLQUALITYOFSERVICE,         // provider func index
            &pfnTSPI_lineSetCallQualityOfService,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetCallQualityOfService"   // func name

            )) > 0)
    {
        pParams->lResult = CallSP6(
            pfnTSPI_lineSetCallQualityOfService,
            "lineSetCallQualityOfService",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) (pDataBuf + pParams->dwSendingFlowspecOffset),
            (DWORD) pParams->dwSendingFlowspecSize,
            (ULONG_PTR) (pDataBuf + pParams->dwReceivingFlowspecOffset),
            (DWORD) pParams->dwReceivingFlowspecSize
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetCallQualityOfService"
        );
}


void
WINAPI
LSetCallTreatment(
    PTCLIENT                        ptClient,
    PLINESETCALLTREATMENT_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetCallTreatment;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hCall,             // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETCALLTREATMENT,    // provider func index
            &pfnTSPI_lineSetCallTreatment,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetCallTreatment"          // func name

            )) > 0)
    {
        if (pParams->dwTreatment == 0  ||
            (pParams->dwTreatment > LINECALLTREATMENT_MUSIC &&
            pParams->dwTreatment < 0x100))
        {
            lRequestID = LINEERR_INVALPARAM;
            goto LSetCallTreatment_epilog;
        }

        pParams->lResult = CallSP3(
            pfnTSPI_lineSetCallTreatment,
            "lineSetCallTreatment",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->dwTreatment
            );
    }

LSetCallTreatment_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetCallTreatment"
        );
}


void
WINAPI
LSetDefaultMediaDetection(
    PTCLIENT                                ptClient,
    PLINESETDEFAULTMEDIADETECTION_PARAMS    pParams,
    DWORD                                   dwParamsBufferSize,
    LPBYTE                                  pDataBuf,
    LPDWORD                                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineSetDefaultMediaDetection;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETDEFAULTMEDIADETECTION,        // provider func index
            &pfnTSPI_lineSetDefaultMediaDetection,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetDefaultMediaDetection"  // func name

            )) == 0)
    {
        DWORD         dwMediaModes = pParams->dwMediaModes;
        PTLINE        ptLine;


        ptLine = ptLineClient->ptLine;

        if ((dwMediaModes & ptLine->dwOpenMediaModes) != dwMediaModes)
        {
            DWORD dwUnionMediaModes = dwMediaModes |
                ptLine->dwOpenMediaModes;


            if ((pParams->lResult = CallSP2(
                    pfnTSPI_lineSetDefaultMediaDetection,
                    "lineSetDefaultMediaDetection",
                    SP_FUNC_SYNC,
                    (ULONG_PTR) hdLine,
                    (DWORD) dwUnionMediaModes

                    )) == 0)
            {
                ptLine->dwOpenMediaModes = dwUnionMediaModes;
            }

        }

        if (pParams->lResult == 0)
        {
            //
            // For remote clients, give the monitor privilege.  It doesn't
            // matter if we do with (except for increased network traffic),
            // because tapisrv on the client will filter out anything
            // the client clients don't want.
            //

            if (IS_REMOTE_CLIENT (ptClient))
            {
                ptLineClient->dwPrivileges = (dwMediaModes ?
                    LINECALLPRIVILEGE_MONITOR | LINECALLPRIVILEGE_OWNER :
                    LINECALLPRIVILEGE_NONE);
            }
            else
            {
                ptLineClient->dwPrivileges = (dwMediaModes ?
                    LINECALLPRIVILEGE_OWNER : LINECALLPRIVILEGE_NONE);
            }

            ptLineClient->dwMediaModes = dwMediaModes;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetDefaultMediaDetection"
        );
}


void
WINAPI
LSetDevConfig(
    PTCLIENT                    ptClient,
    PLINESETDEVCONFIG_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineSetDevConfig;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwSize,
            pParams->dwDeviceConfigOffset,
            sizeof(DWORD),
            "LSetDevConfig",
            "pParams->DeviceConfig"
            )  ||

        IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDeviceClassOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            0,                          // client widget handle
            &dwDeviceID,                // provider widget handle
            pParams->dwDeviceID,        // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETDEVCONFIG,        // provider func index
            &pfnTSPI_lineSetDevConfig,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "SetDevConfig"              // func name

            )) == 0)
    {
        pParams->lResult = CallSP4(
            pfnTSPI_lineSetDevConfig,
            "lineSetDevConfig",
            SP_FUNC_SYNC,
            (DWORD) dwDeviceID,
            (ULONG_PTR) (pParams->dwSize ?
                pDataBuf + pParams->dwDeviceConfigOffset : NULL),
            (DWORD) pParams->dwSize,
            (ULONG_PTR) (pDataBuf + pParams->dwDeviceClassOffset)
            );
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetDevConfig"
        );
}


void
WINAPI
LSetLineDevStatus(
    PTCLIENT                        ptClient,
    PLINESETLINEDEVSTATUS_PARAMS    pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineSetLineDevStatus;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            pParams->hLine,             // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETLINEDEVSTATUS,    // provider func index
            &pfnTSPI_lineSetLineDevStatus,  // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetLineDevStatus"          // func name

            )) > 0)
    {
        #define AllLineDevStatusFlags         \
            (LINEDEVSTATUSFLAGS_CONNECTED   | \
            LINEDEVSTATUSFLAGS_MSGWAIT      | \
            LINEDEVSTATUSFLAGS_INSERVICE    | \
            LINEDEVSTATUSFLAGS_LOCKED)

        if (pParams->dwStatusToChange == 0 ||
            (pParams->dwStatusToChange & ~AllLineDevStatusFlags) != 0)
        {
            lRequestID = LINEERR_INVALLINESTATE;
        }
        else
        {
            pParams->lResult = CallSP4(
                pfnTSPI_lineSetLineDevStatus,
                "lineSetLineDevStatus",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwStatusToChange,
                (DWORD) pParams->fStatus
                );
        }

    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetLineDevStatus"
        );
}


void
WINAPI
LSetMediaControl(
    PTCLIENT                    ptClient,
    PLINESETMEDIACONTROL_PARAMS pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL        bCloseMutex;
    DWORD       dwWidgetType, hWidget, dwPrivilege;
    HANDLE      hMutex;
    LPVOID      context;
    TSPIPROC    pfnTSPI_lineSetMediaControl;
    DWORD       objectToDereference;
    ULONG_PTR   hdWidget;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (((pParams->dwDigitListOffset != TAPI_NO_DATA)  &&

            ISBADSIZEOFFSET(
                dwParamsBufferSize,
                0,
                pParams->dwDigitListNumEntries,
                    // actually dwNumEntries * sizeof(LINEMEDIACONTROLDIGIT)
                pParams->dwDigitListOffset,
                sizeof(DWORD),
                "LSetMediaControl",
                "pParams->DigitList"
                ))  ||

        ((pParams->dwMediaListOffset != TAPI_NO_DATA)  &&

            ISBADSIZEOFFSET(
                dwParamsBufferSize,
                0,
                pParams->dwMediaListNumEntries,
                    // actually dwNumEntries * sizeof(LINEMEDIACONTROLMEDIA)
                pParams->dwMediaListOffset,
                sizeof(DWORD),
                "LSetMediaControl",
                "pParams->MediaList"
                ))  ||

        ((pParams->dwToneListOffset != TAPI_NO_DATA)  &&

            ISBADSIZEOFFSET(
                dwParamsBufferSize,
                0,
                pParams->dwToneListNumEntries,
                    // actually dwNumEntries * sizeof(LINEMEDIACONTROLTONE)
                pParams->dwToneListOffset,
                sizeof(DWORD),
                "LSetMediaControl",
                "pParams->ToneList"
                ))  ||

         ((pParams->dwCallStateListOffset != TAPI_NO_DATA)  &&

            ISBADSIZEOFFSET(
                dwParamsBufferSize,
                0,
                pParams->dwCallStateListNumEntries,
                    // actually dwNumEntries *sizeof(LINEMEDIACONTROLCALLSTATE)
                pParams->dwCallStateListOffset,
                sizeof(DWORD),
                "LSetMediaControl",
                "pParams->CallStateList"
                )))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if (pParams->dwSelect == LINECALLSELECT_CALL)
    {
        dwWidgetType = ANY_RT_HCALL;
        hWidget      = (DWORD) pParams->hCall;
        dwPrivilege  = LINECALLPRIVILEGE_OWNER;
    }
    else
    {
        dwWidgetType = ANY_RT_HLINE;
        hWidget      = (DWORD) pParams->hLine;
        dwPrivilege  = 0;
    }

    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            dwWidgetType,               // widget type
            (DWORD) hWidget,            // client widget handle
            (LPVOID) &hdWidget,          // provider widget handle
            dwPrivilege,                // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETMEDIACONTROL,     // provider func index
            &pfnTSPI_lineSetMediaControl,   // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &context,                   // context
            "SetMediaControl"           // func name

            )) == 0)
    {
        if (!IsOnlyOneBitSetInDWORD (pParams->dwSelect) ||
            (pParams->dwSelect & ~AllCallSelect))
        {
            pParams->lResult = LINEERR_INVALCALLSELECT;
            goto LSetMediaControl_epilog;
        }

        pParams->lResult = CallSP12(
            pfnTSPI_lineSetMediaControl,
            "lineSetMediaControl",
            SP_FUNC_SYNC,
            (ULONG_PTR) (pParams->dwSelect == LINECALLSELECT_CALL ?
                0 : hdWidget),
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) (pParams->dwSelect == LINECALLSELECT_CALL ?
                hdWidget : 0),
            (DWORD) pParams->dwSelect,
            (ULONG_PTR) (pParams->dwDigitListOffset == TAPI_NO_DATA ? NULL :
               pDataBuf + pParams->dwDigitListOffset),
            (DWORD) pParams->dwDigitListNumEntries /
                sizeof(LINEMEDIACONTROLDIGIT),
            (ULONG_PTR) (pParams->dwMediaListOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwMediaListOffset),
            (DWORD) pParams->dwMediaListNumEntries /
                sizeof(LINEMEDIACONTROLMEDIA),
            (ULONG_PTR) (pParams->dwToneListOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwToneListOffset),
            (DWORD) pParams->dwToneListNumEntries /
                sizeof(LINEMEDIACONTROLTONE),
            (ULONG_PTR) (pParams->dwCallStateListOffset == TAPI_NO_DATA ? NULL :
                pDataBuf + pParams->dwCallStateListOffset),
            (DWORD) pParams->dwCallStateListNumEntries /
                sizeof(LINEMEDIACONTROLCALLSTATE)
            );
    }

LSetMediaControl_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetMediaControl"
        );
}


void
WINAPI
LSetMediaMode(
    PTCLIENT                    ptClient,
    PLINESETMEDIAMODE_PARAMS    pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwAPIVersion, dwSPIVersion, dwAllMediaModes;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetMediaMode;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETMEDIAMODE,        // provider func index
            &pfnTSPI_lineSetMediaMode,  // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetMediaMode"              // func name

            )) == 0)
    {
        try
        {
            dwAPIVersion = ptCallClient->ptLineClient->dwAPIVersion;
            dwSPIVersion = ptCallClient->ptLineClient->ptLine->dwSPIVersion;

            if (ptCallClient->dwKey != TCALLCLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALCALLHANDLE;
                goto LSetMediaMode_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            goto LSetMediaMode_epilog;
        }


        //
        // Check for 0 media mode, and if > 1 bit set without UNKNOWN bit
        //

        if ( (dwAPIVersion <= TAPI_VERSION2_1 ) &&
             !IsOnlyOneBitSetInDWORD (pParams->dwMediaModes) &&
             !(pParams->dwMediaModes & LINEMEDIAMODE_UNKNOWN))
        {

            LOG((TL_ERROR,
                "LSetMediaMode: error, >1 media mode selected without " \
                    "UNKNOWN flag (APIVer=x%x)",
                dwAPIVersion
                ));

            pParams->lResult = LINEERR_INVALMEDIAMODE;
            goto LSetMediaMode_epilog;
        }


        //
        // Now the harder checks
        //

        switch (dwAPIVersion)
        {
        case TAPI_VERSION1_0:

            dwAllMediaModes = AllMediaModes1_0;
            break;


        case TAPI_VERSION1_4:
        case TAPI_VERSION2_0:

            dwAllMediaModes = AllMediaModes1_4;
            break;


        //case TAPI_VERSION2_1:
        //case TAPI_VERSION2_2:
        default: //case TAPI_VERSION_CURRENT:

            dwAllMediaModes = AllMediaModes2_1;
            break;
        }

        if ((pParams->dwMediaModes & (dwAllMediaModes ^ 0x00ffffff)) ||
            (pParams->dwMediaModes == 0))
        {
            pParams->lResult = LINEERR_INVALMEDIAMODE;
            goto LSetMediaMode_epilog;
        }

        pParams->lResult = CallSP2(
            pfnTSPI_lineSetMediaMode,
            "lineSetMediaMode",
            SP_FUNC_SYNC,
            (ULONG_PTR) hdCall,
            (DWORD) pParams->dwMediaModes
            );
    }

LSetMediaMode_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetMediaMode"
        );
}


void
WINAPI
LSetNumRings(
    PTCLIENT                ptClient,
    PLINESETNUMRINGS_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex;
    HANDLE          hMutex;
    HDRVLINE        hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT    ptLineClient;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_NONE,                    // provider func index
            NULL,                       // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetNumRings"               // func name

            )) == 0)
    {
        if (WaitForExclusiveLineClientAccess (ptLineClient))
        {
            DWORD dwNumAddresses = ptLineClient->ptLine->dwNumAddresses;


            if (pParams->dwAddressID >= dwNumAddresses)
            {
                pParams->lResult = LINEERR_INVALADDRESSID;
                goto LSetNumRings_releaseMutex;
            }

            if (ptLineClient->aNumRings == NULL)
            {
                if (!(ptLineClient->aNumRings = ServerAlloc(
                        dwNumAddresses * sizeof (DWORD)
                        )))
                {
                    pParams->lResult = LINEERR_NOMEM;
                    goto LSetNumRings_releaseMutex;
                }

                FillMemory(
                    ptLineClient->aNumRings,
                    dwNumAddresses * sizeof (DWORD),
                    0xff
                    );
            }

            ptLineClient->aNumRings[pParams->dwAddressID] =
                pParams->dwNumRings;

LSetNumRings_releaseMutex:

            UNLOCKTLINECLIENT(ptLineClient);
        }
        else
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetNumRings"
        );
}

void
WINAPI
LSetQueueMeasurementPeriod(
    PTCLIENT                                ptClient,
    PLINESETQUEUEMEASUREMENTPERIOD_PARAMS   pParams,
    DWORD                                   dwParamsBufferSize,
    LPBYTE                                  pDataBuf,
    LPDWORD                                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            0,                          // provider func index
            NULL,                       // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetQueueMeasurementPeriod" // func name

            )) > 0)
    {
        LONG            lResult;
        DWORD           dwDeviceID;
        PTLINECLIENT    pProxy;


        if ((lResult = FindProxy(
                ptLineClient,
                0,
                LINEPROXYREQUEST_SETQUEUEMEASUREMENTPERIOD,
                &pProxy,
                &dwDeviceID,
                TAPI_VERSION2_2
                )))
        {
            lRequestID = lResult;
            goto LSetQueueMeasurementPeriod_epilog;
        }


        // Measurement period must be > 0
        if (pParams->dwMeasurementPeriod == 0)
        {
            lRequestID = LINEERR_INVALPARAM;
            goto LSetQueueMeasurementPeriod_epilog;
        }


        //
        // First check to see if there's a (local) proxy registered
        // for this type of request on this line.  If so, build a
        // request & send it to the proxy.
        //

        if (pProxy)
        {
            LONG                    lResult;
            PPROXYREQUESTWRAPPER    pProxyRequestWrapper;


            if ((lResult = CreateProxyRequest(
                    pProxy,
                    LINEPROXYREQUEST_SETQUEUEMEASUREMENTPERIOD,
                    2 * sizeof (DWORD),
                    pAsyncRequestInfo,
                    &pProxyRequestWrapper
                    )))
            {
                lRequestID = lResult;
                goto LSetQueueMeasurementPeriod_epilog;
            }

            pProxyRequestWrapper->ProxyRequest.
                    SetQueueMeasurementPeriod.dwQueueID = pParams->dwQueueID;

            pProxyRequestWrapper->ProxyRequest.
                    SetQueueMeasurementPeriod.dwMeasurementPeriod =
                    pParams->dwMeasurementPeriod;

            if ((lResult = SendProxyRequest(
                    pProxy,
                    pProxyRequestWrapper,
                    pAsyncRequestInfo
                    )))
            {
                lRequestID = lResult;
                goto LSetQueueMeasurementPeriod_epilog;
            }
            else // success
            {
                pParams->lResult = (LONG) pAsyncRequestInfo->dwLocalRequestID;
            }
        }


        //
        // There's no proxy, so check to see if line is remote and
        // call remotesp if so
        //

        else if ((GetLineLookupEntry (dwDeviceID))->bRemote)
        {
            pParams->lResult = CallSP4(
                pRemoteSP->apfn[SP_LINESETQUEUEMEASUREMENTPERIOD],
                "lineSetQueueMeasurementPeriod",
                SP_FUNC_ASYNC,
                (DWORD) pAsyncRequestInfo->dwLocalRequestID,
                (ULONG_PTR) hdLine,
                (DWORD) pParams->dwQueueID,
                (DWORD) pParams->dwMeasurementPeriod
                );
        }


        //
        // There's no registered proxy & line is not remote, so fail
        //

        else
        {
            lRequestID = LINEERR_OPERATIONUNAVAIL;
        }
    }

LSetQueueMeasurementPeriod_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetQueueManagementPeriod"
        );
}


void
WINAPI
LSetStatusMessages(
    PTCLIENT                        ptClient,
    PLINESETSTATUSMESSAGES_PARAMS   pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL            bCloseMutex, bCloseMutex2;
    HANDLE          hMutex, hMutex2;
    HDRVLINE        hdLine;
    TSPIPROC        pfnTSPI_lineSetStatusMessages;
    DWORD           objectToDereference;
    PTLINECLIENT    ptLineClient, ptLineClient2;


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETSTATUSMESSAGES,   // provider func index
            &pfnTSPI_lineSetStatusMessages, // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "SetStatusMessages"         // func name

            )) == 0)
    {
        DWORD   dwAPIVersion, dwUnionLineStates, dwUnionAddressStates;
        PTLINE  ptLine;


        //
        // Safely get the ptLine & api version
        //

        try
        {
            ptLine = ptLineClient->ptLine;

            dwAPIVersion = ptLineClient->dwAPIVersion;

            if (ptLineClient->dwKey != TLINECLIENT_KEY)
            {
                pParams->lResult = LINEERR_INVALLINEHANDLE;
                goto LSetStatusMessages_epilog;
            }
        }
        myexcept
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
            goto LSetStatusMessages_epilog;
        }


        //
        // Validate the params
        //

        {
            DWORD   dwValidLineStates, dwValidAddressStates;


            switch (dwAPIVersion)
            {
            case TAPI_VERSION1_0:

                dwValidLineStates    = AllLineStates1_0;
                dwValidAddressStates = AllAddressStates1_0;
                break;

            default:

                dwValidLineStates    = AllLineStates1_4;
                dwValidAddressStates = AllAddressStates1_4;
                break;
            }

            if (pParams->dwLineStates & ~dwValidLineStates)
            {
                pParams->lResult = LINEERR_INVALLINESTATE;
                goto LSetStatusMessages_epilog;
            }

            if (pParams->dwAddressStates & ~dwValidAddressStates)
            {
                pParams->lResult = LINEERR_INVALADDRESSSTATE;
                goto LSetStatusMessages_epilog;
            }
        }


        //
        // Make sure the REINIT bit is always set
        //

        pParams->dwLineStates |= LINEDEVSTATE_REINIT;


        //
        // Get exclusive access to the device, determine the
        // new union of all the client's status message settings
        // and call down to the SP as appropriate
        //

        dwUnionLineStates    = pParams->dwLineStates;
        dwUnionAddressStates = pParams->dwAddressStates;

waitForExclAccess:

        if (WaitForExclusivetLineAccess(
                ptLine,
                &hMutex2,
                &bCloseMutex2,
                INFINITE
                ))
        {
            if (ptLine->dwBusy)
            {
                MyReleaseMutex (hMutex2, bCloseMutex2);
                Sleep (50);
                goto waitForExclAccess;
            }

            for(
                ptLineClient2 = ptLine->ptLineClients;
                ptLineClient2;
                ptLineClient2 = ptLineClient2->pNextSametLine
                )
            {
                if (ptLineClient2 != ptLineClient)
                {
                    dwUnionLineStates    |= ptLineClient2->dwLineStates;
                    dwUnionAddressStates |= ptLineClient2->dwAddressStates;
                }
            }

            if ((dwUnionLineStates != ptLine->dwUnionLineStates)  ||
                (dwUnionAddressStates != ptLine->dwUnionAddressStates))
            {
                ptLine->dwBusy = 1;

                MyReleaseMutex (hMutex2, bCloseMutex2);

                pParams->lResult = CallSP3(
                        pfnTSPI_lineSetStatusMessages,
                        "lineSetStatusMessages",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) hdLine,
                        (DWORD) dwUnionLineStates,
                        (DWORD) dwUnionAddressStates
                        );

                if (WaitForExclusivetLineAccess(
                        ptLine,
                        &hMutex2,
                        &bCloseMutex2,
                        INFINITE
                        ))
                {
                    ptLine->dwBusy = 0;

                    if (pParams->lResult == 0)
                    {
                        ptLine->dwUnionLineStates    = dwUnionLineStates;
                        ptLine->dwUnionAddressStates = dwUnionAddressStates;
                    }

                    MyReleaseMutex (hMutex2, bCloseMutex2);
                }
                else
                {
                    pParams->lResult = LINEERR_INVALLINEHANDLE;
                }
            }
            else
            {
                MyReleaseMutex (hMutex2, bCloseMutex2);
            }

            if (pParams->lResult == 0)
            {
                if (WaitForExclusiveLineClientAccess (ptLineClient))
                {
                    ptLineClient->dwLineStates    = pParams->dwLineStates;
                    ptLineClient->dwAddressStates = pParams->dwAddressStates;

                    UNLOCKTLINECLIENT (ptLineClient);
                }
                else
                {
                    //
                    // The client is invalid now, but don't bother
                    // restoring the status msg states (will eventually
                    // get reset correctly & worse case is that SP just
                    // sends some extra msgs that get discarded)
                    //

                    pParams->lResult = LINEERR_INVALLINEHANDLE;
                }
            }
        }
        else
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
        }
    }

LSetStatusMessages_epilog:

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "SetStatusMessages"
        );
}


void
WINAPI
LSetTerminal(
    PTCLIENT                ptClient,
    PLINESETTERMINAL_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    DWORD               dwWidgetType, hWidget, dwPrivilege,
                        dwSelect = pParams->dwSelect;
    HANDLE              hMutex;
    LPVOID              context;
    TSPIPROC            pfnTSPI_lineSetTerminal;
    DWORD               objectToDereference;
    ULONG_PTR           hdWidget;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if (dwSelect == LINECALLSELECT_CALL)
    {
        dwWidgetType = ANY_RT_HCALL;
        hWidget      = (DWORD) pParams->hCall;
        dwPrivilege  = LINECALLPRIVILEGE_MONITOR;
    }
    else
    {
        dwWidgetType = ANY_RT_HLINE;
        hWidget      = (DWORD) pParams->hLine;
        dwPrivilege  = 0;
    }

    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            dwWidgetType,               // widget type
            hWidget,                    // client widget handle
            &hdWidget,                  // provider widget handle
            dwPrivilege,                // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETTERMINAL,         // provider func index
            &pfnTSPI_lineSetTerminal,   // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &context,                   // context
            "SetTerminal"               // func name

            )) > 0)
    {
        DWORD   dwTerminalModes = pParams->dwTerminalModes;


        if (!IsOnlyOneBitSetInDWORD (dwSelect) ||
            (dwSelect & ~AllCallSelects))
        {
            lRequestID = LINEERR_INVALCALLSELECT;
            goto LSetTerminal_epilog;
        }

        if (dwTerminalModes == 0 ||
            (dwTerminalModes & (~AllTerminalModes)))
        {
            lRequestID = LINEERR_INVALTERMINALMODE;
            goto LSetTerminal_epilog;
        }

        pParams->lResult = CallSP8(
            pfnTSPI_lineSetTerminal,
            "lineSetTerminal",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) (dwWidgetType == ANY_RT_HLINE ? hdWidget : 0),
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) (dwWidgetType == ANY_RT_HCALL ? hdWidget : 0),
            (DWORD) dwSelect,
            (DWORD) dwTerminalModes,
            (DWORD) pParams->dwTerminalID,
            (DWORD) pParams->bEnable
            );
    }

LSetTerminal_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetTerminal"
        );
}


void
LSetupConference_PostProcess(
    PASYNCREQUESTINFO   pAsyncRequestInfo,
    PASYNCEVENTMSG      pAsyncEventMsg,
    LPVOID             *ppBuf
    )
{
    PTCALL          ptConfCall    = (PTCALL) pAsyncRequestInfo->dwParam1,
                    ptConsultCall = (PTCALL) pAsyncRequestInfo->dwParam3,
                    ptCall        = (PTCALL) pAsyncRequestInfo->dwParam5;
    HCALL           hpConfCall    = DWORD_CAST(pAsyncRequestInfo->dwParam2,__FILE__,__LINE__);
    HCALL           hpConsultCall = DWORD_CAST(pAsyncRequestInfo->dwParam4,__FILE__,__LINE__);

    PTCALLCLIENT    ptConfCallClient, ptConsultCallClient;


//         LSetupConference_PostProcess: mutex on confCall too?
//
//         Actually, this may be ok as is- the consult call is
//         positioned before the conf call in the tline's list,
//         so if we can safely access the former we ought to be
//         able to safely access the latter too

    if (WaitForExclusivetCallAccess (ptConsultCall, TINCOMPLETECALL_KEY))
    {
        PTCALL      ptConsultCallThen = (PTCALL)*(&pAsyncRequestInfo->dwParam5 + 2);

        //
        // Check to make sure this is the call we think it is (that the
        // pointer wasn't freed by a previous call to lineClose/Shutdown
        // and realloc'd for use as a ptCall again)
        //

        if (ptConsultCall != ptConsultCallThen)
        {
            UNLOCKTCALL(ptConsultCall);
            goto LSetupConference_PostProcess_bad_ptConsultCall;
        }

        ptConfCallClient    = ptConfCall->ptCallClients;
        ptConsultCallClient = ptConsultCall->ptCallClients;

        if (pAsyncEventMsg->Param2 == 0)  // success
        {
            PTCONFERENCELIST    pConfList = ptConfCall->pConfList;


            //
            // Check to see if the app closed the line & left us with
            // 0 call clients (in which case it'll also be taking care of
            // cleaning up this tCall too)
            //

            if (ptConsultCall->ptCallClients == NULL)
            {
                UNLOCKTCALL(ptConsultCall);

                ptConfCallClient = (PTCALLCLIENT) NULL;
                ptConsultCallClient = (PTCALLCLIENT) NULL;

                if (pAsyncEventMsg->Param2 == 0)
                {
                    pAsyncEventMsg->Param2 = LINEERR_INVALLINEHANDLE;
                }

                goto LSetupConference_PostProcess_initMsgParams;
            }


            //
            // Retrieve the various call IDs, then check if call
            // client was destroyed by another thread (due to
            // lineClose/Shutdown) while we were getting the call ID.
            // If so, we'll need to clean up the tCall, since we know
            // the other thread didn't do it because GetCallIDs marks
            // the call as a zombie.
            //
            // Note that we can't use GetCallIDs() because we need
            // to get id's for two calls at once
            //

            {
                PTPROVIDER  ptProvider = ptConfCall->ptProvider;


                ptConfCall->dwKey =
                ptConsultCall->dwKey = TZOMBIECALL_KEY;

                UNLOCKTCALL (ptConsultCall);

                if (ptProvider->apfn[SP_LINEGETCALLIDS])
                {
                    CallSP4(
                        ptProvider->apfn[SP_LINEGETCALLIDS],
                        "lineGetCalIDs",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptConfCall->hdCall,
                        (ULONG_PTR) &ptConfCall->dwAddressID,
                        (ULONG_PTR) &ptConfCall->dwCallID,
                        (ULONG_PTR) &ptConfCall->dwRelatedCallID
                        );

                    CallSP4(
                        ptProvider->apfn[SP_LINEGETCALLIDS],
                        "lineGetCalIDs",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptConsultCall->hdCall,
                        (ULONG_PTR) &ptConsultCall->dwAddressID,
                        (ULONG_PTR) &ptConsultCall->dwCallID,
                        (ULONG_PTR) &ptConsultCall->dwRelatedCallID
                        );
                }
                else
                {
                    DWORD           dwSPIVersion, dwFixedSizeSP;
                    LINECALLINFO    callInfo;


                    //
                    // Determine the fixed size of the structure expected
                    // by the SP
                    //

                    dwSPIVersion = ((PTLINE) ptConfCall->ptLine)->dwSPIVersion;

                    switch (dwSPIVersion)
                    {
                    case TAPI_VERSION1_0:
                    case TAPI_VERSION1_4:

                        dwFixedSizeSP = 296;    // 69 * sizeof(DWORD)
                                                //    + sizeof (HLINE)
                                                //    + sizeof (LINEDIALPARAMS)
                        break;

                    case TAPI_VERSION2_0:
                    case TAPI_VERSION2_1:
                    case TAPI_VERSION2_2:

                        dwFixedSizeSP = 324;    // 76 * sizeof(DWORD)
                                                //    + sizeof (HLINE)
                                                //    + sizeof (LINEDIALPARAMS)
                        break;

                    default: // (fix ppc build wrn) case TAPI_VERSION_CURRENT:

                        dwFixedSizeSP = sizeof (LINECALLINFO);
                        break;
                    }

                    InitTapiStruct(
                        &callInfo,
                        dwFixedSizeSP,
                        dwFixedSizeSP,
                        TRUE
                        );

                    if (ptProvider->apfn[SP_LINEGETCALLINFO] == NULL)
                    {
                        LOCKTCALL (ptConsultCall);
                        goto LSetupConference_PostProcess_cleanupCalls;
                    }

                    CallSP2(
                        ptProvider->apfn[SP_LINEGETCALLINFO],
                        "lineGetCallInfo",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptConfCall->hdCall,
                        (ULONG_PTR) &callInfo
                        );

                    ptConfCall->dwAddressID     = callInfo.dwAddressID;
                    ptConfCall->dwCallID        = callInfo.dwCallID;
                    ptConfCall->dwRelatedCallID = callInfo.dwRelatedCallID;

                    InitTapiStruct(
                        &callInfo,
                        dwFixedSizeSP,
                        dwFixedSizeSP,
                        TRUE
                        );

                    CallSP2(
                        ptProvider->apfn[SP_LINEGETCALLINFO],
                        "lineGetCallInfo",
                        SP_FUNC_SYNC,
                        (ULONG_PTR) ptConsultCall->hdCall,
                        (ULONG_PTR) &callInfo
                        );

                    ptConsultCall->dwAddressID     = callInfo.dwAddressID;
                    ptConsultCall->dwCallID        = callInfo.dwCallID;
                    ptConsultCall->dwRelatedCallID = callInfo.dwRelatedCallID;
                }

                LOCKTCALL (ptConsultCall);
            }

            if (!ptConsultCall->ptCallClients)
            {
                goto LSetupConference_PostProcess_cleanupCalls;
            }


            //
            // Indicate the various call IDs in the adwParams[] field of
            // the ASYNCEVENTMSG.
            //
            // Make sure to increment the dwTotalSize of the ASYNCEVENTMSG
            // as appropriate.  We rely on the fact that CompletionProc()
            // calls us with an AsyncEventMsg buffer that is big enough to
            // handle a few extra DWORDs.
            //

            pAsyncEventMsg->Param3 = ptConfCallClient->hCall;

            *(&pAsyncEventMsg->Param4 + 1) = ptConsultCallClient->hCall;

            pAsyncEventMsg->TotalSize +=
                6 * sizeof (pAsyncEventMsg->Param1);

            *(&pAsyncEventMsg->Param4 + 3) = ptConfCall->dwAddressID;
            *(&pAsyncEventMsg->Param4 + 4) = ptConfCall->dwCallID;
            *(&pAsyncEventMsg->Param4 + 5) = ptConfCall->dwRelatedCallID;
            *(&pAsyncEventMsg->Param4 + 6) = ptConsultCall->dwAddressID;
            *(&pAsyncEventMsg->Param4 + 7) = ptConsultCall->dwCallID;
            *(&pAsyncEventMsg->Param4 + 8) = ptConsultCall->dwRelatedCallID;


            //
            // Mark the calls as valid, the release the mutex
            //

            ptConfCall->dwKey =
            ptConsultCall->dwKey = TCALL_KEY;
            ptConfCallClient->dwKey =
            ptConsultCallClient->dwKey = TCALLCLIENT_KEY;
            pConfList->dwKey = TCONFLIST_KEY;

            UNLOCKTCALL(ptConsultCall);


            //
            // Create monitor tCallClients
            //

            if (ptConsultCallThen == ptConsultCall)
            {
                CreateCallMonitors (ptConfCall, FALSE);
                CreateCallMonitors (ptConsultCall, FALSE);
            }
        }
        else    // error
        {

LSetupConference_PostProcess_cleanupCalls:

            //
            // Invalidate the tCalls, & if there's still tCallClients
            // (might have already been destroyed by a lineClose/Shutdown
            // in another thread) invalidate them too. Then unlock the
            // tCalls & remove the object(s) from the list(s).
            //

            ptConfCall->dwKey = ptConsultCall->dwKey = INVAL_KEY;

            if (ptConfCall->ptCallClients)
            {
                ptConfCallClient->dwKey = INVAL_KEY;
                ptConfCall->lActiveFastCallClients--;
            }
            else
            {
                ptConfCallClient = NULL;
            }

            if (ptConsultCall->ptCallClients)
            {
                ptConsultCallClient->dwKey = INVAL_KEY;
                ptConsultCall->lActiveFastCallClients--;
            }
            else
            {
                ptConsultCallClient = NULL;
            }

            UNLOCKTCALL(ptConsultCall);

            RemoveCallFromLineList (ptConfCall);
            RemoveCallFromLineList (ptConsultCall);

            if (ptConfCallClient)
            {
                DereferenceObject (ghHandleTable, ptConfCallClient->hCall, 1);
                RemoveCallClientFromLineClientList (ptConfCallClient);
            }

            if (ptConsultCallClient)
            {
                DereferenceObject (ghHandleTable,ptConsultCallClient->hCall,1);
                RemoveCallClientFromLineClientList (ptConsultCallClient);
            }


            //
            // Make sure all fast call clients cleaned up before free tCalls
            //

            while ((ptConfCall->lActiveFastCallClients != 0)  ||
                   (ptConsultCall->lActiveFastCallClients != 0))
            {
                Sleep (5);
            }

            FreetCall  (ptConsultCall);

            if (ptCall)
            {
                SetCallConfList (ptCall, NULL, FALSE);
            }

            ServerFree  (ptConfCall->pConfList);
            FreetCall  (ptConfCall);
        }
    }
    else
    {
        //
        // If here we can assume that the call was already destroyed
        // and just fail the request
        //

LSetupConference_PostProcess_bad_ptConsultCall:

        if (pAsyncEventMsg->Param2 == 0)
        {
            pAsyncEventMsg->Param2 = LINEERR_OPERATIONFAILED;
        }
    }


LSetupConference_PostProcess_initMsgParams:

    //
    // Fill in the params to pass to client (important to remotesp in both
    // the success & fail cases so it can either init or clean up drvCall)
    //
    // Make sure to increment the dwTotalSize of the ASYNCEVENTMSG
    // as appropriate.  We rely on the fact that CompletionProc()
    // calls us with an AsyncEventMsg buffer that is big enough to
    // handle a few extra DWORDs.
    //

    pAsyncEventMsg->Param4 = hpConfCall;

    pAsyncEventMsg->TotalSize += 2 * sizeof (pAsyncEventMsg->Param1);

    *(&pAsyncEventMsg->Param4 + 2) = hpConsultCall;
}


void
WINAPI
LSetupConference(
    PTCLIENT                    ptClient,
    PLINESETUPCONFERENCE_PARAMS pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    DWORD               dwPrivilege = LINECALLPRIVILEGE_OWNER;
    HCALL               hCall = pParams->hCall;
    HLINE               hLine = pParams->hLine;
    HANDLE              hMutex;
    LPVOID              context;
    TSPIPROC            pfnTSPI_lineSetupConference;
    DWORD               objectToDereference;
    ULONG_PTR           hdXxx;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,                      // tClient
            (hCall ? ANY_RT_HCALL : ANY_RT_HLINE),  // widget type
            (hCall ? (DWORD) hCall : (DWORD) hLine),// client widget handle
            (LPVOID) &hdXxx,                        // provider widget handle
            (hCall ? dwPrivilege : 0),              // privileges or device ID
            &hMutex,                                // mutex handle
            &bCloseMutex,                           // close hMutex when done
            SP_LINESETUPCONFERENCE,                 // provider func index
            &pfnTSPI_lineSetupConference,           // provider func pointer
            &pAsyncRequestInfo,                     // async request info
            pParams->dwRemoteRequestID,             // client async request ID
            &objectToDereference,                   // object to dereference
            &context,                               // context
            "SetupConference"                       // func name

            )) > 0)
    {
        LONG                lResult;
        DWORD               dwNumParties;
        PTCALL              ptCall, ptConfCall, ptConsultCall;
        //HTAPICALL           htConfCall, htConsultCall;
        PTCALLCLIENT        ptConfCallClient, ptConsultCallClient;
        PTLINECLIENT        ptLineClient;
        LPLINECALLPARAMS    pCallParamsApp, pCallParamsSP;
        PTCONFERENCELIST    pConfList;


        //
        // Verify size/offset/string params given our input buffer/size
        //

        if ((pParams->dwCallParamsOffset != TAPI_NO_DATA)  &&

            IsBadStructParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwCallParamsOffset
                ))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LSetupConference_return;
        }


        //
        // We need two more async request info params than are available,
        // so we'll realloc a larger buf & work with it
        //

        {
            PASYNCREQUESTINFO   pAsyncRequestInfo2;


            if (!(pAsyncRequestInfo2 = ServerAlloc(
                    sizeof (ASYNCREQUESTINFO) + 2 * sizeof (ULONG_PTR)
                    )))
            {
                lRequestID = LINEERR_NOMEM;
                goto LSetupConference_return;
            }

            CopyMemory(
                pAsyncRequestInfo2,
                pAsyncRequestInfo,
                sizeof (ASYNCREQUESTINFO)
                );

            pAsyncRequestInfo2->dwLocalRequestID = (DWORD)NewObject(
                ghHandleTable,
                pAsyncRequestInfo2,
                NULL
                );

            // The following lines are to be removed for BUG 258501(xzhang).
            // When called from RemoteSP, dwRemoteRequestID is used by the RemoteSP
            // to identify the async request, if set to dwLocalRequestID, RemoteSP
            // will get an invalid request ID and discard the call event notification.
            
/*            if (lRequestID != (LONG) pAsyncRequestInfo->dwLocalRequestID)
            {
                lRequestID = (LONG)
                (pAsyncRequestInfo2->dwRemoteRequestID =
                    pAsyncRequestInfo2->dwLocalRequestID);
            }
*/

            DereferenceObject(
                ghHandleTable,
                pAsyncRequestInfo->dwLocalRequestID,
                1
                );

            pAsyncRequestInfo = pAsyncRequestInfo2;

        }

        pCallParamsApp = (LPLINECALLPARAMS)
            (pParams->dwCallParamsOffset == TAPI_NO_DATA ?
                0 : (pDataBuf + pParams->dwCallParamsOffset));


        //
        // Reference the tLineClient if not already
        //

        if (hCall)
        {
            try
            {
                hLine = ((PTCALLCLIENT) context)->ptLineClient->hLine;
            }
            myexcept
            {
                lRequestID = LINEERR_OPERATIONFAILED;
                goto LSetupConference_return;
            }

            if (!(ptLineClient = ReferenceObject(
                    ghHandleTable,
                    hLine,
                    TLINECLIENT_KEY
                    )))
            {
                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LSetupConference_return;
            }
        }
        else
        {
            ptLineClient = (PTLINECLIENT) context;
        }

        if (pCallParamsApp)
        {
            DWORD         dwAPIVersion, dwSPIVersion;


            try
            {
                dwAPIVersion = ptLineClient->dwAPIVersion;
                dwSPIVersion = ptLineClient->ptLine->dwSPIVersion;
            }
            myexcept
            {
                lRequestID = LINEERR_OPERATIONFAILED;
                goto LSetupConference_Dereference;
            }


            if ((lResult = ValidateCallParams(
                    pCallParamsApp,
                    &pCallParamsSP,
                    dwAPIVersion,
                    dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage

                    )) != 0)
            {
                lRequestID = lResult;
                goto LSetupConference_Dereference;
            }
        }
        else
        {
            pCallParamsSP = (LPLINECALLPARAMS) NULL;
        }

        dwNumParties = (pParams->dwNumParties > DEF_NUM_CONF_LIST_ENTRIES ?
            pParams->dwNumParties : DEF_NUM_CONF_LIST_ENTRIES);

        if (!(pConfList = (PTCONFERENCELIST) ServerAlloc(
                sizeof (TCONFERENCELIST) + dwNumParties * sizeof(PTCALL)
                )))
        {
            lRequestID = LINEERR_NOMEM;
            goto LSetupConference_freeCallParams;
        }

        pConfList->dwNumTotalEntries = dwNumParties + 1;
        pConfList->dwNumUsedEntries  = 1;

        if (hCall)
        {
            try
            {
                ptCall = ((PTCALLCLIENT) context)->ptCall;
            }
            myexcept
            {
                lResult = LINEERR_INVALCALLHANDLE;
                goto LSetupConference_freeConfList;
            }

            if ((lResult = SetCallConfList (ptCall, pConfList, FALSE)) != 0)
            {
                goto LSetupConference_freeConfList;
            }
        }
        else
        {
            ptCall = NULL;
        }

        if ((lResult = CreatetCallAndClient(
                ptLineClient,
                &ptConfCall,
                &ptConfCallClient,
                pCallParamsSP,
                NULL

                )) == 0)
        {
            //htConfCall = ptConfCall->htCall;

            pConfList->aptCalls[0] = ptConfCall;

            if ((lResult = CreatetCallAndClient(
                    ptLineClient,
                    &ptConsultCall,
                    &ptConsultCallClient,
                    NULL,
                    ptConfCall

                    ) == 0))
            {
                //htConsultCall = ptConsultCall->htCall;

                ptConfCall->pConfList = pConfList;

                pAsyncRequestInfo->pfnPostProcess =
                    LSetupConference_PostProcess;
                pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptConfCallClient->ptLineClient->ptLine;
                pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptConfCall;
                pAsyncRequestInfo->dwParam2 = pParams->hpConfCall;
                
                pAsyncRequestInfo->dwParam3 = (ULONG_PTR) ptConsultCall;
                pAsyncRequestInfo->dwParam4 = pParams->hpConsultCall;
                pAsyncRequestInfo->dwParam5 = (ULONG_PTR) ptCall;

                *(&pAsyncRequestInfo->dwParam5 + 1) = (ULONG_PTR)ptConfCall;
                *(&pAsyncRequestInfo->dwParam5 + 2) = (ULONG_PTR)ptConsultCall;

                pAsyncRequestInfo->hfnClientPostProcessProc =
                    pParams->hfnPostProcessProc;

                goto LSetupConference_callSP;
            }

            SetDrvCallFlags (ptConfCall, DCF_SPIRETURNED);
            DestroytCall (ptConfCall);
        }

LSetupConference_freeConfList:

        ServerFree (pConfList);
        lRequestID = lResult;
        goto LSetupConference_freeCallParams;

LSetupConference_callSP:

        pParams->lResult = CallSP9(
            pfnTSPI_lineSetupConference,
            "lineSetupConference",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) (hCall ? hdXxx : 0),    // hdCall
            (ULONG_PTR) (hCall ? 0 : hdXxx),    // hdLine
            (ULONG_PTR) ptConfCall,
            (ULONG_PTR) &ptConfCall->hdCall,
            (ULONG_PTR) ptConsultCall,
            (ULONG_PTR) &ptConsultCall->hdCall,
            (DWORD) pParams->dwNumParties,
            (ULONG_PTR) pCallParamsSP
            );

        SetDrvCallFlags(
            ptConfCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

        SetDrvCallFlags(
            ptConsultCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

LSetupConference_freeCallParams:

        if (pCallParamsSP != pCallParamsApp)
        {
            ServerFree (pCallParamsSP);
        }

LSetupConference_Dereference:

        if (hCall)
        {
            DereferenceObject (ghHandleTable, hLine, 1);
        }
    }

LSetupConference_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetupConference"
        );
}


void
WINAPI
LSetupTransfer(
    PTCLIENT                    ptClient,
    PLINESETUPTRANSFER_PARAMS   pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineSetupTransfer;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESETUPTRANSFER,       // provider func index
            &pfnTSPI_lineSetupTransfer, // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "SetupTransfer"             // func name

            )) > 0)
    {
        LONG                lResult;
        HLINE               hLine;
        PTCALL              ptConsultCall;
        //HTAPICALL           htConsultCall;
        PTCALLCLIENT        ptConsultCallClient;
        PTLINECLIENT        ptLineClient;
        LPLINECALLPARAMS    pCallParamsApp, pCallParamsSP;


        //
        // Verify size/offset/string params given our input buffer/size
        //

        if ((pParams->dwCallParamsOffset != TAPI_NO_DATA)  &&

            IsBadStructParam(
                dwParamsBufferSize,
                pDataBuf,
                pParams->dwCallParamsOffset
                ))
        {
            lRequestID = LINEERR_STRUCTURETOOSMALL;
            goto LSetupTransfer_return;
        }


        //
        // Reference the tLineClient
        //

        try
        {
            hLine = ptCallClient->ptLineClient->hLine;
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSetupTransfer_return;
        }

        if (!(ptLineClient = ReferenceObject(
                ghHandleTable,
                hLine,
                TLINECLIENT_KEY
                )))
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSetupTransfer_return;
        }

        pCallParamsApp = (LPLINECALLPARAMS)
            (pParams->dwCallParamsOffset == TAPI_NO_DATA ?
                0 : (pDataBuf + pParams->dwCallParamsOffset));

        if (pCallParamsApp)
        {
            DWORD         dwAPIVersion, dwSPIVersion;


            dwAPIVersion = ptLineClient->dwAPIVersion;

            try
            {
                dwSPIVersion = ptLineClient->ptLine->dwSPIVersion;
            }
            myexcept
            {
                lRequestID = LINEERR_OPERATIONFAILED;
                goto LSetupTransfer_Dereference;
            }

            if ((lResult = ValidateCallParams(
                    pCallParamsApp,
                    &pCallParamsSP,
                    dwAPIVersion,
                    dwSPIVersion,
                    pParams->dwAsciiCallParamsCodePage

                    )) != 0)
            {
                lRequestID = lResult;
                goto LSetupTransfer_Dereference;
            }
        }
        else
        {
            pCallParamsSP = (LPLINECALLPARAMS) NULL;
        }

        if (CreatetCallAndClient(
                ptLineClient,
                &ptConsultCall,
                &ptConsultCallClient,
                NULL,
                NULL

                ) != 0)
        {
            lRequestID = LINEERR_NOMEM;
            goto LSetupTransfer_freeCallParams;
        }

        //htConsultCall = ptConsultCall->htCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptConsultCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptConsultCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpConsultCall;
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptConsultCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP5(
            pfnTSPI_lineSetupTransfer,
            "lineSetupTransfer",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall,
            (ULONG_PTR) ptConsultCall,
            (ULONG_PTR) &ptConsultCall->hdCall,
            (ULONG_PTR) pCallParamsSP
            );

        SetDrvCallFlags(
            ptConsultCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ?
                DCF_DRVCALLVALID : 0)
            );

LSetupTransfer_freeCallParams:

        if (pCallParamsSP != pCallParamsApp)
        {
            ServerFree (pCallParamsSP);
        }

LSetupTransfer_Dereference:

        DereferenceObject (ghHandleTable, hLine, 1);
    }

LSetupTransfer_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SetupTransfer"
        );
}


void
WINAPI
LShutdown(
    PTCLIENT                ptClient,
    PLINESHUTDOWN_PARAMS    pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    pParams->lResult = DestroytLineApp ((HLINEAPP) pParams->hLineApp);

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "lineShutdown: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "lineShutdown: exit, result=x%x",
            pParams->lResult
            ));
#endif
}


void
WINAPI
LSwapHold(
    PTCLIENT                ptClient,
    PLINESWAPHOLD_PARAMS    pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdActiveCall;
    TSPIPROC            pfnTSPI_lineSwapHold;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptActiveCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            pParams->hActiveCall,       // client widget handle
            (LPVOID) &hdActiveCall,     // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINESWAPHOLD,            // provider func index
            &pfnTSPI_lineSwapHold,      // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptActiveCallClient,        // context
            "SwapHold"                  // func name

            )) > 0)
    {
        HDRVCALL        hdHeldCall;
        PTCALLCLIENT    ptHeldCallClient;


        //
        // Verify held call
        //

        if (!(ptHeldCallClient = ReferenceObject(
                ghHandleTable,
                pParams->hHeldCall,
                TCALLCLIENT_KEY
                )))
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSwapHold_epilog;
        }

        if (ptHeldCallClient->ptClient != ptClient)
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSwapHold_Dereference;
        }


        //
        // Safely verify that client has owner privilege to held call,
        // and that calls are on same tLine
        //

        try
        {
            if (!(ptHeldCallClient->dwPrivilege & LINECALLPRIVILEGE_OWNER))
            {
                lRequestID = LINEERR_NOTOWNER;
                goto LSwapHold_Dereference;
            }

            if (ptHeldCallClient->ptCall->ptLine !=
                    ptActiveCallClient->ptCall->ptLine)
            {
                lRequestID = LINEERR_INVALCALLHANDLE;
                goto LSwapHold_Dereference;
            }

            hdHeldCall = ptHeldCallClient->ptCall->hdCall;
        }
        myexcept
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSwapHold_Dereference;
        }


        //
        // Are they the same call?
        //

        if (hdActiveCall == hdHeldCall)
        {
            lRequestID = LINEERR_INVALCALLHANDLE;
            goto LSwapHold_Dereference;
        }

        pParams->lResult = CallSP3(
            pfnTSPI_lineSwapHold,
            "lineSwapHold",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdActiveCall,
            (ULONG_PTR) hdHeldCall
            );

LSwapHold_Dereference:

        DereferenceObject (ghHandleTable, pParams->hHeldCall, 1);

    }

LSwapHold_epilog:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "SwapHold"
        );
}



void
WINAPI
LUncompleteCall(
    PTCLIENT                    ptClient,
    PLINEUNCOMPLETECALL_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineUncompleteCall;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEUNCOMPLETECALL,      // provider func index
            &pfnTSPI_lineUncompleteCall,// provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "UncompleteCall"            // func name

            )) > 0)
    {
        pParams->lResult = CallSP3(
            pfnTSPI_lineUncompleteCall,
            "lineUncompleteCall",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwCompletionID
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "UncompleteCall"
        );
}


void
WINAPI
LUnhold(
    PTCLIENT            ptClient,
    PLINEUNHOLD_PARAMS  pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVCALL            hdCall;
    TSPIPROC            pfnTSPI_lineUnhold;
    DWORD               objectToDereference;
    PTCALLCLIENT        ptCallClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HCALL,               // widget type
            (DWORD) pParams->hCall,     // client widget handle
            (LPVOID) &hdCall,           // provider widget handle
            LINECALLPRIVILEGE_OWNER,    // req'd privileges (call only)
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEUNHOLD,              // provider func index
            &pfnTSPI_lineUnhold,        // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptCallClient,              // context
            "Unhold"                    // func name

            )) > 0)
    {
        pParams->lResult = CallSP2(
            pfnTSPI_lineUnhold,
            "lineUnhold",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdCall
            );
    }

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Unhold"
        );
}


void
WINAPI
LUnpark(
    PTCLIENT            ptClient,
    PLINEUNPARK_PARAMS  pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    LONG                lRequestID;
    HANDLE              hMutex;
    HDRVLINE            hdLine;
    TSPIPROC            pfnTSPI_lineUnpark;
    DWORD               objectToDereference;
    PTLINECLIENT        ptLineClient;
    PASYNCREQUESTINFO   pAsyncRequestInfo;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (IsBadStringParam(
            dwParamsBufferSize,
            pDataBuf,
            pParams->dwDestAddressOffset
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((lRequestID = LINEPROLOG(
            ptClient,          // tClient
            ANY_RT_HLINE,               // widget type
            (DWORD) pParams->hLine,     // client widget handle
            (LPVOID) &hdLine,           // provider widget handle
            0,                          // privileges or device ID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEUNPARK,              // provider func index
            &pfnTSPI_lineUnpark,        // provider func pointer
            &pAsyncRequestInfo,         // async request info
            pParams->dwRemoteRequestID, // client async request ID
            &objectToDereference,       // object to dereference
            &ptLineClient,              // context
            "Unpark"                    // func name

            )) > 0)
    {
        PTCALL          ptCall;
        //HTAPICALL       htCall;
        PTCALLCLIENT    ptCallClient;


        if (CreatetCallAndClient(
                ptLineClient,
                &ptCall,
                &ptCallClient,
                NULL,
                NULL

                ) != 0)
        {
            lRequestID = LINEERR_NOMEM;
            goto LUnpark_return;
        }

        //htCall = ptCall->hCall;

        pAsyncRequestInfo->pfnPostProcess = LMakeCall_PostProcess;
        pAsyncRequestInfo->htXxx    = (ULONG_PTR)ptCallClient->ptLineClient->ptLine;
        pAsyncRequestInfo->dwParam1 = (ULONG_PTR) ptCall;
        pAsyncRequestInfo->dwParam2 = pParams->hpCall;
        pAsyncRequestInfo->dwParam5 = (ULONG_PTR)ptCall;

        pAsyncRequestInfo->hfnClientPostProcessProc =
            pParams->hfnPostProcessProc;

        pParams->lResult = CallSP6(
            pfnTSPI_lineUnpark,
            "lineUnpark",
            SP_FUNC_ASYNC,
            (DWORD) pAsyncRequestInfo->dwLocalRequestID,
            (ULONG_PTR) hdLine,
            (DWORD) pParams->dwAddressID,
            (ULONG_PTR) ptCall,
            (ULONG_PTR) &ptCall->hdCall,
            (ULONG_PTR) (pDataBuf + pParams->dwDestAddressOffset)
            );

        SetDrvCallFlags(
            ptCall,
            DCF_SPIRETURNED | (IS_LRESULT_NOTERROR(pParams->lResult) ? DCF_DRVCALLVALID : 0)
            );
    }

LUnpark_return:

    LINEEPILOGASYNC(
        &pParams->lResult,
        lRequestID,
        hMutex,
        bCloseMutex,
        pAsyncRequestInfo,
        objectToDereference,
        "Unpark"
        );
}


void
WINAPI
TAllocNewID(
    PTCLIENT            ptClient,
    P_ALLOCNEWID_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    HKEY     hKey;
    HKEY     hKey2;
    DWORD    dwDataSize;
    DWORD    dwDataType;
    DWORD    dwNewID;
    DWORD    dwDisposition;


    RegCreateKeyEx(
        HKEY_LOCAL_MACHINE,
        gszRegKeyTelephony,
        0,
        TEXT(""),
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey2,
        &dwDisposition
        );

    RegCreateKeyEx(
        hKey2,
        gszLocations,
        0,
        TEXT(""),
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition
        );

    dwDataSize = sizeof(DWORD);

    //
    // Use 1 as the first ID.
    //

    pParams->dwNewID = 1;
    RegQueryValueEx(
        hKey,
        gszNextID,
        0,
        &dwDataType,
        (LPBYTE)&pParams->dwNewID,
        &dwDataSize
        );


    dwNewID = pParams->dwNewID + 1;

    RegSetValueEx(
        hKey,
        gszNextID,
        0,
        REG_DWORD,
        (LPBYTE)&dwNewID,
        sizeof(DWORD)
        );

    RegCloseKey( hKey );
    RegCloseKey( hKey2);

    *pdwNumBytesReturned = sizeof(ALLOCNEWID_PARAMS);

    return;
}


#define MAX_KEY_LENGTH 256
DWORD RegDeleteKeyNT(HKEY hStartKey , LPCTSTR pKeyName )
{
  DWORD   dwRtn, dwSubKeyLength;
  LPTSTR  pSubKey = NULL;
  TCHAR   szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic.
  HKEY    hKey;

  // Do not allow NULL or empty key name
  if ( pKeyName &&  lstrlen(pKeyName))
  {
     if( (dwRtn=RegOpenKeyEx(hStartKey,pKeyName,
        0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey )) == ERROR_SUCCESS)
     {
        while (dwRtn == ERROR_SUCCESS )
        {
           dwSubKeyLength = MAX_KEY_LENGTH;
           dwRtn=RegEnumKeyEx(
                          hKey,
                          0,       // always index zero
                          szSubKey,
                          &dwSubKeyLength,
                          NULL,
                          NULL,
                          NULL,
                          NULL
                        );

           if(dwRtn == ERROR_NO_MORE_ITEMS)
           {
              dwRtn = RegDeleteKey(hStartKey, pKeyName);
              break;
           }
           else if(dwRtn == ERROR_SUCCESS)
              dwRtn=RegDeleteKeyNT(hKey, szSubKey);
        }
        RegCloseKey(hKey);
        // Do not save return code because error
        // has already occurred
     }
  }
  else
     dwRtn = ERROR_BADKEY;

  return dwRtn;
}



void
WINAPI
TWriteLocations(
    PTCLIENT            ptClient,
    PW_LOCATIONS_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    DWORD           dwLocationID;
    DWORD           dwCount,dwCount2,dwError;
    DWORD           dwDisposition;
    DWORD           dwNumEntries,dwNumRules;
    DWORD           dwVersion;

    TCHAR           szCurrentLocationKey[256];
    TCHAR           szCurrentRuleKey[256];

    HKEY            hAllLocationsKey = NULL;
    HKEY            hLocationKey;
    HKEY            hAllAreaCodeRulesKey;
    HKEY            hAreaCodeRuleKey;
    HKEY            hTelephonyKey;
    HKEY            hUTelephonyKey;
    HKEY            hUAllLocationsKey = NULL;
    HANDLE          CurrentUserKey;
   
    PLOCATIONLIST   pLocationList = NULL;
    PLOCATION       pLocation = NULL;
    PAREACODERULE   pAreaCodeRule = NULL; 

    BOOL            bRelMutex = FALSE;
    HANDLE          hProvidersMutex;

    LOG((TL_TRACE, "TWriteLocations: enter"));

    hProvidersMutex = CreateMutex (
        NULL,
        FALSE,
        TEXT("TapisrvProviderListMutex")
        );
    if (NULL == hProvidersMutex)
    {
        LOG((TL_ERROR,
            "WriteLocation: CreateMutex failed, err=%d",
            GetLastError()
            ));
        goto ExitHere;
    }
    
    WaitForSingleObject (hProvidersMutex, INFINITE);
    bRelMutex = TRUE;
    

    dwVersion = TAPI_LOCATION_LIST_VERSION;

    if (RegCreateKeyEx(
                   HKEY_LOCAL_MACHINE,
                   gszRegKeyTelephony,
                   0,
                   TEXT(""),
                   REG_OPTION_NON_VOLATILE,
                   KEY_ALL_ACCESS,
                   0,
                   &hTelephonyKey,
                   &dwDisposition
                  ) != ERROR_SUCCESS)
    {
        goto ExitHere;
    }
    
    if (RegCreateKeyEx(
                   hTelephonyKey,
                   gszLocations,
                   0,
                   TEXT(""),
                   REG_OPTION_NON_VOLATILE,
                   KEY_ALL_ACCESS,
                   0,
                   &hAllLocationsKey,
                   &dwDisposition
                  ) != ERROR_SUCCESS)
    {
        RegCloseKey(hTelephonyKey);
        goto ExitHere;
    }

    RegSetValueEx( hAllLocationsKey,
                   gszLocationListVersion,
                   0,
                   REG_DWORD,
                   (BYTE *)&dwVersion,
                   sizeof(dwVersion)
                   );

    RegCloseKey( hTelephonyKey );
   
    /////////////////////////////////////////////////////
    // Now open clients key
    //
    if ((dwError=RpcImpersonateClient (0)) != RPC_S_OK)
    {
        LOG((TL_ERROR,
            "WriteLocation: RpcImpersonateClient failed, err=%d",
            dwError
            ));
    }
    else
    {

        if (RtlOpenCurrentUser(KEY_ALL_ACCESS, &CurrentUserKey) 
            != ERROR_SUCCESS)
        {
            RpcRevertToSelf();
            goto ExitHere;
        }

        dwError = RegCreateKeyEx(
                      CurrentUserKey,
                      gszRegKeyTelephony,
                      0,
                      TEXT(""),
                      REG_OPTION_NON_VOLATILE,
                      KEY_ALL_ACCESS,
                      0,
                      &hUTelephonyKey,
                      &dwDisposition
                    );
        if ( dwError != ERROR_SUCCESS )
        {
    
            LOG((TL_ERROR, "Registry can't create/open Users telephony key"));
            NtClose(CurrentUserKey);
            RpcRevertToSelf();
            goto ExitHere;
        }
    
        NtClose(CurrentUserKey);
        
        dwError = RegCreateKeyEx(
                      hUTelephonyKey,
                      gszLocations,
                      0,
                      TEXT(""),
                      REG_OPTION_NON_VOLATILE,
                      KEY_ALL_ACCESS,
                      0,
                      &hUAllLocationsKey,
                      &dwDisposition
                    );
        if ( dwError != ERROR_SUCCESS )
        {
    
            LOG((TL_ERROR, "Registry can't create/open Users Locations key"));
            RegCloseKey( hUTelephonyKey );
            RpcRevertToSelf();
            goto ExitHere;
        }
        RegSetValueEx(  hUAllLocationsKey,
                        gszLocationListVersion,
                        0,
                        REG_DWORD,
                        (BYTE *)&dwVersion,
                        sizeof(dwVersion)
                        );
        RegCloseKey( hUTelephonyKey );
    
        RpcRevertToSelf();
    }



    pLocationList = (PLOCATIONLIST)(pDataBuf + pParams->dwLocationListOffset);


    if ( pParams->dwChangedFlags & CHANGEDFLAGS_CURLOCATIONCHANGED )
    {
        RegSetValueEx(
                       hAllLocationsKey,
                       gszCurrentID,
                       0,
                       REG_DWORD,
                       (LPBYTE)&pLocationList->dwCurrentLocationID,
                       sizeof(DWORD)
                     );
    }     
    
    // Find position of 1st LOCATION structure in the LOCATIONLIST structure 
    pLocation = (PLOCATION) ((BYTE*)(pLocationList) + pLocationList->dwLocationListOffset );           

    // Number of locations ?
    dwNumEntries =  pLocationList->dwNumLocationsInList;

    for (dwCount = 0; dwCount < dwNumEntries ; dwCount++)
    {
        //Form key name for this location
        dwLocationID = pLocation->dwPermanentLocationID;
        wsprintf(szCurrentLocationKey, TEXT("Location%d"), dwLocationID);
    
        // Is Entry to be deleted from reg ?
        if(pLocation->dwLocationNameSize > sizeof(WCHAR) &&
            *(WCHAR *)((BYTE*)(pLocation) + 
                pLocation->dwLocationNameOffset) != 0) // not just NULL
        {
            LOG((TL_INFO, "Location - write %s",szCurrentLocationKey));
            
            // Create or open key for this location
            dwError = RegCreateKeyEx(
                            hAllLocationsKey,
                            szCurrentLocationKey,
                            0,
                            TEXT(""),
                            REG_OPTION_NON_VOLATILE,
                            KEY_ALL_ACCESS,
                            0,
                            &hLocationKey,
                            &dwDisposition
                          );
            if (dwError == ERROR_SUCCESS)
            {
                // Country ID
                if(RegSetValueEx(
                                 hLocationKey,
                                 gszCountry,
                                 0,
                                 REG_DWORD,
                                 (LPBYTE)&pLocation->dwCountryID,
                                 sizeof(DWORD)
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write country"));
                }
        
                // Options (flags)
                if(RegSetValueEx(
                                 hLocationKey,
                                 gszFlags,
                                 0,
                                 REG_DWORD,
                                 (LPBYTE)&pLocation->dwOptions,
                                 sizeof(DWORD)
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write Flags"));
                }
                
    
                // Name
                if(TAPIRegSetValueExW(
                                 hLocationKey,
                                 gszNameW,
                                 0,
                                 REG_SZ,
                                 (BYTE*)(pLocation) + pLocation->dwLocationNameOffset,
                                 pLocation->dwLocationNameSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write Name"));
                }
                
                // AreaCode
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszAreaCodeW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwAreaCodeOffset,
                                pLocation->dwAreaCodeSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write area code"));
                }
    
                // CallWaiting
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszDisableCallWaitingW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwCancelCallWaitingOffset,
                                pLocation->dwCancelCallWaitingSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write call waiting"));
                }

                // LD Carrier Code
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszLongDistanceCarrierCodeW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwLongDistanceCarrierCodeOffset,
                                pLocation->dwLongDistanceCarrierCodeSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write LD carrier code"));
                }

                // International Carrier Code
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszInternationalCarrierCodeW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwInternationalCarrierCodeOffset,
                                pLocation->dwInternationalCarrierCodeSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write International carrier code"));
                }

                // LD Access
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszLongDistanceAccessW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwLongDistanceAccessCodeOffset,
                                pLocation->dwLongDistanceAccessCodeSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write LD access code"));
                }

                // Local Access
                if(TAPIRegSetValueExW(
                                hLocationKey,
                                gszOutsideAccessW,
                                0,
                                REG_SZ,
                                (BYTE*)(pLocation) + pLocation->dwLocalAccessCodeOffset,
                                pLocation->dwLocalAccessCodeSize
                                ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "Location - can't write Local access code"));
                }
                
               // if this is an existing key then delete all the subkeys
               RegDeleteKeyNT(hLocationKey, gszAreaCodeRules );
    
               // create or open the AreaCodeRules key
               dwError = RegCreateKeyEx(
                                         hLocationKey,
                                         gszAreaCodeRules,
                                         0,
                                         TEXT(""),
                                         REG_OPTION_NON_VOLATILE,
                                         KEY_ALL_ACCESS,
                                         0,
                                         &hAllAreaCodeRulesKey,
                                         &dwDisposition
                                        );
                if (dwError == ERROR_SUCCESS)
                {
                    // Find position of 1st AREACODERULE structure in the LOCATION structure 
                    pAreaCodeRule = (PAREACODERULE) ((BYTE*)(pLocation) 
                                                     + pLocation->dwAreaCodeRulesListOffset );           
                   
                    dwNumRules = pLocation->dwNumAreaCodeRules;           
                
                    for (dwCount2 = 0; dwCount2 != dwNumRules; dwCount2++)
                    {
                        //Form key name for this aea code rule
                        wsprintf(szCurrentRuleKey, TEXT("Rule%d"),dwCount2);
        
                        // create or open this Area Code Rule Key
                        dwError = RegCreateKeyEx(
                                                 hAllAreaCodeRulesKey,
                                                 szCurrentRuleKey,
                                                 0,
                                                 TEXT(""),
                                                 REG_OPTION_NON_VOLATILE,
                                                 KEY_ALL_ACCESS,
                                                 0,
                                                 &hAreaCodeRuleKey,
                                                 &dwDisposition
                                                );
                        if (dwError == ERROR_SUCCESS)
                        {
                            // Pull Dataout of AREACODERULE structure
            
                            // Options (flags)
                            if(RegSetValueEx(
                                       hAreaCodeRuleKey,
                                       gszFlags,
                                       0,
                                       REG_DWORD,
                                       (LPBYTE)&pAreaCodeRule->dwOptions,
                                       sizeof(DWORD)
                                           ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't write Flags"));
                            }
                            
                            // AreaCode to call
                            if(TAPIRegSetValueExW(
                                                 hAreaCodeRuleKey,
                                                 gszAreaCodeToCallW,
                                                 0,
                                                 REG_SZ,
                                                 (BYTE*)(pLocation) + pAreaCodeRule->dwAreaCodeOffset,
                                                 pAreaCodeRule->dwAreaCodeSize
                                                 ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't write Area code to call"));
                            }
            
                            //Number to Dial
                            if(TAPIRegSetValueExW(
                                                 hAreaCodeRuleKey,
                                                 gszNumberToDialW,
                                                 0,
                                                 REG_SZ,
                                                 (BYTE*)(pLocation) + pAreaCodeRule->dwNumberToDialOffset,
                                                 pAreaCodeRule->dwNumberToDialSize
                                                  ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't write Number to dial"));
                            }
            
                            //Prefixes List
                            if(TAPIRegSetValueExW(
                                                 hAreaCodeRuleKey,
                                                 gszPrefixesW,
                                                 0,
                                                 REG_MULTI_SZ,
                                                 (BYTE*)(pLocation) + pAreaCodeRule->dwPrefixesListOffset,
                                                 pAreaCodeRule->dwPrefixesListSize
                                                  ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't write Prefixes"));
                            }
            
                            RegCloseKey( hAreaCodeRuleKey );   // Don't need this key anymore...
            
                        }
                        else
                        {
                            LOG((TL_ERROR, "can't create/open hAreaCodeRuleKey"));
                        }
    
                        pAreaCodeRule++;
                    }
                    RegCloseKey(hAllAreaCodeRulesKey);
                }
                else
                {
                    LOG((TL_ERROR, "can't create/open hAllAreaCodeRulesKey"));
                }
            
                RegCloseKey(hLocationKey);


                /////////////////////////////////////////////////////
                // Now do clients location entry
                //

                // Create or open key for this location
                dwError = RegCreateKeyEx(
                                hUAllLocationsKey,
                                szCurrentLocationKey,
                                0,
                                TEXT(""),
                                REG_OPTION_NON_VOLATILE,
                                KEY_ALL_ACCESS,
                                0,
                                &hLocationKey,
                                &dwDisposition
                              );
                if (dwError == ERROR_SUCCESS)
                {
                    // CallingCard
                    if(RegSetValueEx(
                                     hLocationKey,
                                     gszCallingCard,
                                     0,
                                     REG_DWORD,
                                     (LPBYTE)&pLocation->dwPreferredCardID,
                                     sizeof(DWORD)
                                    ) != ERROR_SUCCESS)
                    {
                        LOG((TL_ERROR, "location - can't user preferred card"));
                    }
        
                    RegCloseKey( hLocationKey );   // Don't need this key anymore...
                }

            }
            else
            {
                LOG((TL_ERROR, "can't create/open hLocationKey"));
            }
        }
        else   //Delete this location entry 
        {

            LOG((TL_ERROR, "Location - delete %s",szCurrentLocationKey));
            RegDeleteKeyNT( hAllLocationsKey, szCurrentLocationKey );

            /////////////////////////////////////////////////////
            // Now do clients location entry
            //
            RegDeleteKey(hUAllLocationsKey, szCurrentLocationKey);

        }

        // Try next location in list
        //pEntry++;
        pLocation = (PLOCATION) ((BYTE*)(pLocation) + pLocation->dwUsedSize);           

    }

       //
       // We're inside "if (dwChangedFlags)", so we know _something_ changed...
       //

		LOG((TL_TRACE,  "Sending LINE_LINEDEVSTATE/LINEDEVSTATE_TRANSLATECHANGE msg"));

       SendAMsgToAllLineApps(
               0x80010004,     // (OR with 0x80000000 for >= version)
               LINE_LINEDEVSTATE,
               LINEDEVSTATE_TRANSLATECHANGE,
               0,
               0
             );

       SendAMsgToAllLineApps(
               0x00010003,
               LINE_LINEDEVSTATE,
               LINEDEVSTATE_REINIT,
               LINE_LINEDEVSTATE,
               LINEDEVSTATE_TRANSLATECHANGE
             );


    LOG((TL_TRACE, "TWriteLocations: exit"));

ExitHere:             
    if (hAllLocationsKey != NULL)
    {
        RegCloseKey(hAllLocationsKey);
    }
    if (hUAllLocationsKey != NULL)
    {
        RegCloseKey(hUAllLocationsKey);
    }
    if (bRelMutex && hProvidersMutex)
    {
        ReleaseMutex (hProvidersMutex);
        CloseHandle (hProvidersMutex);
    }
    return;

}



void
WINAPI
TReadLocations(
    PTCLIENT            ptClient,
    PR_LOCATIONS_PARAMS pParams,
    DWORD               dwParamsBufferSize,
    LPBYTE              pDataBuf,
    LPDWORD             pdwNumBytesReturned
    )
{
    PLOCATIONLIST pLocationList = (PLOCATIONLIST)(pDataBuf);

    UINT    n;
    UINT    nNumLocations;
    UINT    nCurrentLocationID;

    TCHAR   szCurrentLocationKey[256]; // Holds "LOCATIONxx" during reads
    TCHAR   szAreaCodeRuleKey[256];    // Holds "Rulexx" during reads
    DWORD   dwDataSize,dwKeySize;

    DWORD   dwNumLocationKeys=0;
    DWORD   dwMaxLocationKeyLength=0;
    DWORD   dwNumAreaCodeKeys=0;
    DWORD   dwMaxAreaCodeKeyLength=0;

    DWORD   dwDataType;
    DWORD   dwNeededSize = 0;
    
    DWORD   dwCount, dwCount2;
    DWORD   dwError;

    HKEY    hAllLocationsKey;
    HKEY    hLocationKey;
    HKEY    hAllAreaCodeRulesKey;
    HKEY    hAreaCodeRuleKey;
    HKEY    hTelephonyKey;
    HKEY    hUTelephonyKey;
    HKEY    hUserAllLocationsKey;
    HANDLE  CurrentUserKey;

    BOOL    bRelMutex = FALSE;
    HANDLE  hProvidersMutex;

    LOG((TL_TRACE, "TReadLocations: enter"));

    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (pParams->dwLocationsTotalSize > dwParamsBufferSize)
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ( pParams->dwParmsToCheckFlags & CHECKPARMS_DWHLINEAPP )
    {
        if ( 0 == pParams->dwhLineApp )
        {
            //
            // NULL is valid for these functions...
            //
        }
        else
        {
            if ( !IsValidLineApp((HLINEAPP)pParams->dwhLineApp, ptClient) )
            {
                 LOG((TL_ERROR, "0x%lx is not a valid hLineApp", pParams->dwhLineApp));
                 pParams->lResult = LINEERR_INVALAPPHANDLE;
                 goto CLEANUP_ERROR;
            }
        }
    }


    if ( pParams->dwParmsToCheckFlags & CHECKPARMS_DWDEVICEID )
    {
        if (  pParams->dwhLineApp
            &&
              (pParams->dwDeviceID != 0)
            &&
              (pParams->dwDeviceID >= TapiGlobals.dwNumLines)
           )
        {
             LOG((TL_ERROR, "%ld is not a valid dwDeviceID", pParams->dwDeviceID));
             pParams->lResult = LINEERR_BADDEVICEID;
             goto CLEANUP_ERROR;
        }
    }


    if ( pParams->dwParmsToCheckFlags & CHECKPARMS_DWAPIVERSION )
    {
        switch (pParams->dwAPIVersion)
        {
        case TAPI_VERSION_CURRENT:
        case TAPI_VERSION3_0:
        case TAPI_VERSION2_2:
        case TAPI_VERSION2_1:
        case TAPI_VERSION2_0:
        case TAPI_VERSION1_4:
        case TAPI_VERSION1_0:

            break;

        default:

            LOG((TL_ERROR,
                "TReadLocations: invalid API version x%x",
                pParams->dwAPIVersion
                ));

            pParams->lResult = LINEERR_INCOMPATIBLEAPIVERSION;
            goto CLEANUP_ERROR;
        }
    }

    if ( pParams->dwParmsToCheckFlags & CHECKPARMS_ONLY )
    {

        LOG((TL_INFO, "TReadLocations: Check only, no data transfer"));

        pParams->lResult = 0;

        if (pParams->dwLocationsTotalSize >= 3 * sizeof(DWORD))
        {
            pLocationList->dwTotalSize = pParams->dwLocationsTotalSize;
            pLocationList->dwNeededSize =
            pLocationList->dwUsedSize = 3 * sizeof(DWORD);
            *pdwNumBytesReturned = sizeof (TAPI32_MSG) + (3 * sizeof(DWORD));
        }
        else
        {
            *pdwNumBytesReturned = sizeof (TAPI32_MSG);
        }

        pParams->dwLocationsOffset = 0;

        goto CLEANUP_ERROR;
    }

    hProvidersMutex = CreateMutex (
        NULL,
        FALSE,
        TEXT("TapisrvProviderListMutex")
        );
    if (NULL == hProvidersMutex)
    {
        LOG((TL_ERROR,
                "TReadLocations: CreateMutex failed, err=%d",
                GetLastError()
                ));

        pParams->lResult = LINEERR_OPERATIONFAILED;
        goto CLEANUP_ERROR;
    }

    WaitForSingleObject (hProvidersMutex, INFINITE);
    bRelMutex = TRUE;

    dwError = RegOpenKeyEx(
                  HKEY_LOCAL_MACHINE,
                  gszRegKeyTelephony,
                  0,
                  KEY_READ,
                  &hTelephonyKey
                );

    if (dwError == ERROR_SUCCESS)
    {
        dwError =  RegOpenKeyEx(
                            hTelephonyKey,
                            gszLocations,
                            0,
                            KEY_READ,
                            &hAllLocationsKey
                           );
        RegCloseKey( hTelephonyKey );   // Don't need this key anymore...
    }

    if ( dwError != ERROR_SUCCESS )
    {
        LOG((TL_ERROR, "ReadLocation: Registry can't open Locations key"));
        pParams->lResult = LINEERR_INIFILECORRUPT;
        goto CLEANUP_ERROR;
    }

    

    // Test the version of the Machine Locations key. Upgrade if necessary.
    if(IsLocationListInOldFormat(hAllLocationsKey))
    {
        LOG((TL_INFO, "ReadLocation: Upgrade the Machine Locations key"));
        dwError = ConvertLocations();

        if(dwError != ERROR_SUCCESS)
        {
            LOG((TL_ERROR, "ReadLocation: Cannot convert the Machine Locations key"));
        }
    }



    /////////////////////////////////////////////////////
    // Now open clients key
    //
    if ((dwError=RpcImpersonateClient (0)) != RPC_S_OK)
    {
        LOG((TL_ERROR,
            "ReadLocation: RpcImpersonateClient failed, err=%d",
            dwError
            ));
    }
    else
    {
        RtlOpenCurrentUser(KEY_ALL_ACCESS, &CurrentUserKey);
    
        dwError = RegOpenKeyEx(
                      CurrentUserKey,
                      gszRegKeyTelephony,
                      0,
                      KEY_READ,
                      &hUTelephonyKey
                    );

        if ( dwError != ERROR_SUCCESS )
        {
            LOG((TL_ERROR, "  Registry can't open Users Locations key"));
            
            RpcRevertToSelf();
            RegCloseKey( hAllLocationsKey );
            NtClose(CurrentUserKey);
            pParams->lResult = LINEERR_OPERATIONFAILED;
            goto CLEANUP_ERROR;
        }
    
        if (RegOpenKeyEx(
            hUTelephonyKey,
            gszLocations,
            0,
            KEY_READ,
            &hUserAllLocationsKey
            ) != ERROR_SUCCESS)
        {
            hUserAllLocationsKey = NULL;
        }
        
        RegCloseKey( hUTelephonyKey );   // Don't need this key anymore...
    
        RpcRevertToSelf();

        // Test the version of the User Locations key. Upgrade if necessary.
        if(hUserAllLocationsKey && IsLocationListInOldFormat(hUserAllLocationsKey))
        {
            dwError = ConvertUserLocations(CurrentUserKey);

            if(dwError != ERROR_SUCCESS)
            {
                LOG((TL_ERROR, "  Cannot convert the User Locations key"));
            }
        }

        NtClose(CurrentUserKey);
    }



    dwDataSize = sizeof(nCurrentLocationID);
    nCurrentLocationID = 0;
    RegQueryValueEx(
                   hAllLocationsKey,
                   gszCurrentID,
                   0,
                   &dwDataType,
                   (LPBYTE)&nCurrentLocationID,
                   &dwDataSize
                 );



    // query some info about the Locations key in order to allocate memory
    RegQueryInfoKey(hAllLocationsKey,
                    NULL,
                    NULL,
                    NULL,
                    &dwNumLocationKeys,
                    &dwMaxLocationKeyLength,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL
                   );

    //
    // It's _REALLY_ bad if NumLocations is zero for any
    // reason.  Should probably fail the function on the spot...
    //
    if ( 0 == dwNumLocationKeys )
    {
        LOG((TL_ERROR, "  Registry says there are 0 locations"));
        pParams->lResult = LINEERR_INIFILECORRUPT;
        RegCloseKey( hAllLocationsKey );
        if (hUserAllLocationsKey)
        {
            RegCloseKey(hUserAllLocationsKey);
        }

        goto CLEANUP_ERROR;
    }




    // Need to work out need size, so start with static part of list
    dwNeededSize = ALIGN(sizeof(LOCATIONLIST));

    // go though locations to work out needed size
    for (dwCount=0; dwCount < dwNumLocationKeys ; dwCount++)
    {
        dwKeySize = sizeof(szCurrentLocationKey)/sizeof(TCHAR);
        dwError =  RegEnumKeyEx( hAllLocationsKey,
                                 dwCount,
                                 szCurrentLocationKey,
                                 &dwKeySize,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL
                               );

        if(dwError == ERROR_NO_MORE_ITEMS)
        {
            break;
        }
        if (dwError != ERROR_SUCCESS )
        {
            continue;
        }

        // Open this Location Key
        dwError = RegOpenKeyEx(
                                hAllLocationsKey,
                                szCurrentLocationKey,
                                0,
                                KEY_ALL_ACCESS,
                                &hLocationKey
                               );
        if (dwError == ERROR_SUCCESS)
        {
            //Static part then strings
            dwNeededSize += ALIGN(sizeof(LOCATION));

            // Name
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszNameW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);

            // AreaCode
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszAreaCodeW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);

            //CallWaiting
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszDisableCallWaitingW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);
            
            //LD Carrier
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszLongDistanceCarrierCodeW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);                            
            
            //International Carrier
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszInternationalCarrierCodeW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);                            
            
            //LD Access
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszLongDistanceAccessW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);                            

            //Local Access
            if (TAPIRegQueryValueExW(
                             hLocationKey,
                             gszOutsideAccessW,
                             NULL,
                             NULL,
                             NULL,
                             &dwDataSize
                           ) != ERROR_SUCCESS)
            {
                dwDataSize = sizeof(WCHAR);
            }
            dwNeededSize +=  ALIGN(dwDataSize);
        

            dwError =  RegOpenKeyEx( hLocationKey,
                                     gszAreaCodeRules,
                                     0,
                                     KEY_READ,
                                     &hAllAreaCodeRulesKey
                                   );
            if (dwError == ERROR_SUCCESS)
            {
            // query info about the AreaCodeRules key 
            RegQueryInfoKey(hAllAreaCodeRulesKey,
                            NULL,
                            NULL,
                            NULL,
                            &dwNumAreaCodeKeys,
                            &dwMaxAreaCodeKeyLength,
                            NULL,
                            NULL,
                            NULL,
                            NULL,
                            NULL,
                            NULL
                           );


                // go though this location's area code rules
                for (dwCount2=0; dwCount2 < dwNumAreaCodeKeys; dwCount2++)
                {
                    dwKeySize = sizeof(szAreaCodeRuleKey)/sizeof(TCHAR);
                    dwError =  RegEnumKeyEx( hAllAreaCodeRulesKey,
                                             dwCount2,
                                             szAreaCodeRuleKey,
                                             &dwKeySize,
                                             NULL,
                                             NULL,
                                             NULL,
                                             NULL
                                            );
                    if(dwError == ERROR_NO_MORE_ITEMS)
                    {
                        break;
                    }
                    if (dwError != ERROR_SUCCESS )
                    {
                        continue;
                    }
            
                    // Open this Aeea Code Rule Key
                    dwError = RegOpenKeyEx(
                                            hAllAreaCodeRulesKey,
                                            szAreaCodeRuleKey,
                                            0,
                                            KEY_ALL_ACCESS,
                                            &hAreaCodeRuleKey
                                           );
                    if (dwError == ERROR_SUCCESS)
                    {
                        //Static part then strings
                        dwNeededSize += ALIGN(sizeof(AREACODERULE));
                        
                        // AreaCode to call
                        TAPIRegQueryValueExW(
                                         hAreaCodeRuleKey,
                                         gszAreaCodeToCallW,
                                         NULL,
                                         NULL,
                                         NULL,
                                         &dwDataSize
                                       );
                        dwNeededSize +=  ALIGN(dwDataSize);
            
                        //Number to Dial
                        TAPIRegQueryValueExW(
                                         hAreaCodeRuleKey,
                                         gszNumberToDialW,
                                         NULL,
                                         NULL,
                                         NULL,
                                         &dwDataSize
                                       );
                        dwNeededSize +=  ALIGN(dwDataSize);
    
                        //Number to Dial
                        TAPIRegQueryValueExW(
                                         hAreaCodeRuleKey,
                                         gszPrefixesW,
                                         NULL,
                                         NULL,
                                         NULL,
                                         &dwDataSize
                                       );
                        dwNeededSize +=  ALIGN(dwDataSize);

                        RegCloseKey( hAreaCodeRuleKey );   // Don't need this key anymore...
                    }
                }
                
                RegCloseKey( hAllAreaCodeRulesKey );   // Don't need this key anymore...
            }
        }
        RegCloseKey( hLocationKey );   // Don't need this key anymore...
    }

    //
    // Do we have enough space?
    //
    if ( pParams->dwLocationsTotalSize <  dwNeededSize )
    {

        LOG((TL_ERROR, "(0x%08lx) is not enough room for sizeof( 0x%08lx )",
                   pParams->dwLocationsTotalSize, dwNeededSize ));

        //
        // Buffer not large enough
        //

        pLocationList->dwTotalSize          = pParams->dwLocationsTotalSize;
        pLocationList->dwNeededSize         = dwNeededSize;
        pLocationList->dwUsedSize           = sizeof(LOCATIONLIST);
        pLocationList->dwNumLocationsInList = 0;
        pLocationList->dwLocationListSize   = 0;
        pLocationList->dwLocationListOffset = 0;

        pParams->lResult = 0;
        pParams->dwLocationsOffset = 0;
    }
    else  // Big enough buffer, now fill it
    {
        DWORD           dwLocationOffset, dwOffset;
        PLOCATION       pLocation;
        PAREACODERULE   pAreaCodeRule;
        DWORD           dwMaxValueLength;

        // buffer size 
        pLocationList->dwTotalSize  = pParams->dwLocationsTotalSize;
        pLocationList->dwNeededSize = dwNeededSize;
        pLocationList->dwUsedSize   = dwNeededSize;

        // Results
        pParams->lResult = 0;
        pParams->dwLocationsOffset = 0;

        pLocationList->dwCurrentLocationID     = nCurrentLocationID;
        pLocationList->dwNumLocationsAvailable = dwNumLocationKeys;
        
        //list size & offset
        dwLocationOffset   = ALIGN(sizeof(LOCATIONLIST));

        pLocationList->dwNumLocationsInList = dwNumLocationKeys;
        pLocationList->dwLocationListSize   = dwNeededSize - sizeof(LOCATIONLIST);
        pLocationList->dwLocationListOffset = dwLocationOffset;



        
        // go through locations 
        for (dwCount=0; dwCount < dwNumLocationKeys ; dwCount++)
        {
            dwKeySize = dwMaxLocationKeyLength + 1;
            dwError =  RegEnumKeyEx( hAllLocationsKey,
                                     dwCount,
                                     szCurrentLocationKey,
                                     &dwKeySize,
                                     NULL,
                                     NULL,
                                     NULL,
                                     NULL
                                   );
    
            if(dwError == ERROR_NO_MORE_ITEMS)
            {
                break;
            }
            
            
            pLocation = (PLOCATION)(((LPBYTE)pLocationList) + dwLocationOffset);
    
            // Open this Location Key
            dwError = RegOpenKeyEx(
                                    hAllLocationsKey,
                                    szCurrentLocationKey,
                                    0,
                                    KEY_ALL_ACCESS,
                                    &hLocationKey
                                   );
            if (dwError == ERROR_SUCCESS)
            {
                LOG((TL_INFO, "Location - read %S",szCurrentLocationKey));

                // Find out how big is our biggest value
                dwMaxValueLength = 256;
                RegQueryInfoKey(hLocationKey,0,0,0,0,0,0,0,0,&dwMaxValueLength,0,0);
    
    
                /////////////////////////////////////////////////////////////////////
                // Process fized part of Location info
                dwOffset = ALIGN(sizeof(LOCATION));
            
                //pLocation->dwPreferredCardID = m_dwPreferredCardID;
    
    
                // Location ID (is included in the key name)
                pLocation->dwPermanentLocationID = 0;
                if(dwKeySize >= ARRAYSIZE(gszLocation)) // minimum a Location_ key
                {
                    pLocation->dwPermanentLocationID = (DWORD)_ttol(szCurrentLocationKey + (ARRAYSIZE(gszLocation))-1);
                }
                else
                {
                    LOG((TL_ERROR, "location - can't determine ID"));
                }
    
                // Country ID
                dwDataSize = sizeof(DWORD);
                pLocation->dwCountryID = 1;
                if(RegQueryValueEx(
                                 hLocationKey,
                                 gszCountry,
                                 0,
                                 &dwDataType,
                                 (LPBYTE)&pLocation->dwCountryID,
                                 &dwDataSize
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read CountryID"));
                }
    
                // Options (flags)
                dwDataSize = sizeof(DWORD);
                pLocation->dwOptions = 0;
                if(RegQueryValueEx(
                                 hLocationKey,
                                 gszFlags,
                                 0,
                                 &dwDataType,
                                 (LPBYTE)&pLocation->dwOptions,
                                 &dwDataSize
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read Flags"));
                }
            
                // Name
                pLocation->dwLocationNameSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszNameW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwLocationNameSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read Name"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwLocationNameSize = sizeof(WCHAR);
                }
                pLocation->dwLocationNameOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwLocationNameSize);
                
                // AreaCode
                pLocation->dwAreaCodeSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszAreaCodeW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwAreaCodeSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read Area code"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwAreaCodeSize = sizeof(WCHAR);
                }
                pLocation->dwAreaCodeOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwAreaCodeSize);
    
                // CallWaiting
                pLocation->dwCancelCallWaitingSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszDisableCallWaitingW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwCancelCallWaitingSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read Callwaiting"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwCancelCallWaitingSize = sizeof(WCHAR);
                }
                pLocation->dwCancelCallWaitingOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwCancelCallWaitingSize);

                // LD Carrier
                pLocation->dwLongDistanceCarrierCodeSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszLongDistanceCarrierCodeW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwLongDistanceCarrierCodeSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read LD carrier code"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwLongDistanceCarrierCodeSize = sizeof(WCHAR);
                }
                pLocation->dwLongDistanceCarrierCodeOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwLongDistanceCarrierCodeSize);
                
                // International Carrier
                pLocation->dwInternationalCarrierCodeSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszInternationalCarrierCodeW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwInternationalCarrierCodeSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read International carrier code"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwInternationalCarrierCodeSize = sizeof(WCHAR);
                }
                pLocation->dwInternationalCarrierCodeOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwInternationalCarrierCodeSize);
                
                // LD Access
                pLocation->dwLongDistanceAccessCodeSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszLongDistanceAccessW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwLongDistanceAccessCodeSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read LD access code"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwLongDistanceAccessCodeSize = sizeof(WCHAR);
                }
                pLocation->dwLongDistanceAccessCodeOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwLongDistanceAccessCodeSize);
    
                // Local Access
                pLocation->dwLocalAccessCodeSize = dwMaxValueLength;          
                if(TAPIRegQueryValueExW(     
                                 hLocationKey,
                                 gszOutsideAccessW,
                                 NULL,              
                                 NULL,                         
                                 (LPBYTE)pLocation + dwOffset, 
                                 &pLocation->dwLocalAccessCodeSize                  
                                 ) != ERROR_SUCCESS)
                {
                    LOG((TL_ERROR, "location - can't read local access code"));
                    *(LPWSTR)((LPBYTE)pLocation + dwOffset) = 0;
                    pLocation->dwLocalAccessCodeSize = sizeof(WCHAR);
                }
                pLocation->dwLocalAccessCodeOffset = dwOffset;                             
                dwOffset += ALIGN(pLocation->dwLocalAccessCodeSize);
    
            
                ///////////////////////////////////////////////////////////////////
                // Do the Area Code Rules
    
                dwError =  RegOpenKeyEx( hLocationKey,
                                         gszAreaCodeRules,
                                         0,
                                         KEY_READ,
                                         &hAllAreaCodeRulesKey
                                       );
                if (dwError == ERROR_SUCCESS)
                {
                    // Find out how many keys & how long is a key
                    RegQueryInfoKey(hAllAreaCodeRulesKey,0,0,0,&dwNumAreaCodeKeys,&dwMaxAreaCodeKeyLength,0,0,0,0,0,0);
                    
                    pLocation->dwNumAreaCodeRules = dwNumAreaCodeKeys;
                    // pLocation->dwAreaCodeRulesListSize;
                    pLocation->dwAreaCodeRulesListOffset = dwOffset;  
        
                    // point to the 1st rule
                    pAreaCodeRule = (PAREACODERULE)(((LPBYTE)pLocation) + dwOffset);
            
    
                    //point strings past rule area
                    dwOffset += ALIGN(( sizeof(AREACODERULE) * dwNumAreaCodeKeys ));
        
    
                    // go though this location's area code rules
                    for (dwCount2=0; dwCount2 < dwNumAreaCodeKeys; dwCount2++)
                    {
                        dwKeySize = dwMaxAreaCodeKeyLength + 1;
      
                        dwError =  RegEnumKeyEx( hAllAreaCodeRulesKey,
                                                 dwCount2,
                                                 szAreaCodeRuleKey,
                                                 &dwKeySize,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 NULL
                                                );
                        if(dwError == ERROR_NO_MORE_ITEMS)
                        {
                            break;
                        }
                        if(dwError != ERROR_SUCCESS)
                        {
                            continue;
                        }

                        // Open this Area Code Rule Key
                        dwError = RegOpenKeyEx(
                                                hAllAreaCodeRulesKey,
                                                szAreaCodeRuleKey,
                                                0,
                                                KEY_ALL_ACCESS,
                                                &hAreaCodeRuleKey
                                               );
                        if (dwError == ERROR_SUCCESS)
                        {
                            LOG((TL_ERROR, "ReadLocation - areacode %s",szAreaCodeRuleKey));
                            // Find out how big is our biggest value
                            RegQueryInfoKey(hAreaCodeRuleKey,0,0,0,0,0,0,0,0,&dwMaxValueLength,0,0);
    
                            //Static part then strings
                            dwDataSize = sizeof(DWORD);
                            pAreaCodeRule->dwOptions = 0;
                            if(RegQueryValueEx(
                                             hAreaCodeRuleKey,
                                             gszFlags,
                                             0,
                                             &dwDataType,
                                             (LPBYTE)&pAreaCodeRule->dwOptions,
                                             &dwDataSize
                                             ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't read Flags"));
                            }
                            
                            // AreaCode to call
                            pAreaCodeRule->dwAreaCodeSize = dwMaxValueLength;          
                            if(TAPIRegQueryValueExW(     
                                             hAreaCodeRuleKey,
                                             gszAreaCodeToCallW,
                                             NULL,              
                                             NULL,                         
                                             (LPBYTE)pLocation + dwOffset, 
                                             &pAreaCodeRule->dwAreaCodeSize                  
                                             ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't read Area code to call"));
                            }
                            pAreaCodeRule->dwAreaCodeOffset = dwOffset;                             
                            dwOffset += ALIGN(pAreaCodeRule->dwAreaCodeSize);
    
                            //Number to Dial
                            pAreaCodeRule->dwNumberToDialSize = dwMaxValueLength;          
                            if(TAPIRegQueryValueExW(     
                                             hAreaCodeRuleKey,
                                             gszNumberToDialW,
                                             NULL,              
                                             NULL,                         
                                             (LPBYTE)pLocation + dwOffset, 
                                             &pAreaCodeRule->dwNumberToDialSize                  
                                             ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't read number to dial"));
                            }
                            pAreaCodeRule->dwNumberToDialOffset = dwOffset;                             
                            dwOffset += ALIGN(pAreaCodeRule->dwNumberToDialSize);
        
                            //Prefixes List
                            pAreaCodeRule->dwPrefixesListSize = dwMaxValueLength;          
                            if(TAPIRegQueryValueExW(     
                                             hAreaCodeRuleKey,
                                             gszPrefixesW,
                                             NULL,              
                                             NULL,                         
                                             (LPBYTE)pLocation + dwOffset, 
                                             &pAreaCodeRule->dwPrefixesListSize                  
                                             ) != ERROR_SUCCESS)
                            {
                                LOG((TL_ERROR, "Area code rule - can't read prefixes"));
                            }
                            pAreaCodeRule->dwPrefixesListOffset = dwOffset;                             
                            dwOffset += ALIGN(pAreaCodeRule->dwPrefixesListSize);
    
                            RegCloseKey( hAreaCodeRuleKey );   // Don't need this key anymore...
                            pAreaCodeRule++;
                        }
                    
                    }
                    
                    RegCloseKey( hAllAreaCodeRulesKey );   // Don't need this key anymore...
                }
                // offset gives how many bytes we used
                pLocation->dwUsedSize = dwOffset;
                dwLocationOffset += dwOffset;
     
            }
            RegCloseKey( hLocationKey );   

            /////////////////////////////////////////////////////
            // Now do clients location entry
            //

            pLocation->dwPreferredCardID = 0;
            if (hUserAllLocationsKey)
            {
                // Open this Location Key
                dwError = RegOpenKeyEx(
                                    hUserAllLocationsKey,
                                    szCurrentLocationKey,
                                    0,
                                    KEY_ALL_ACCESS,
                                    &hLocationKey
                                   );
                if (dwError == ERROR_SUCCESS)
                {
                    // Preferred Card ID
                    dwDataSize = sizeof(DWORD);
                    if(RegQueryValueEx(
                                   hLocationKey,
                                   gszCallingCard,
                                   0,
                                   &dwDataType,
                                   (LPBYTE)&pLocation->dwPreferredCardID,
                                   &dwDataSize
                                 ) != ERROR_SUCCESS)
                    {
                        LOG((TL_ERROR, "location - can't read users PreferredCardID"));
                    }
    
                    RegCloseKey( hLocationKey );   // Don't need this key anymore...
                }
                else
                {
        
                    LOG((TL_ERROR, "location - can't read users location key"));
                }
            }
        }
    }

    *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pLocationList->dwUsedSize;

    RegCloseKey(hAllLocationsKey);
    if (hUserAllLocationsKey)
    {
        RegCloseKey(hUserAllLocationsKey);
    }

CLEANUP_ERROR:

#if DBG
    {
        char szResult[32];


        LOG((TL_TRACE, 
            "TReadLocations: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
    }
#else
        LOG((TL_TRACE, 
            "TReadLocations: exit, result=x%x",
            pParams->lResult
            ));
#endif

    if (bRelMutex && hProvidersMutex)
    {
        ReleaseMutex (hProvidersMutex);
        CloseHandle (hProvidersMutex);
    }

    return;
}


void
WINAPI
LReceiveMSPData(
    PTCLIENT                    ptClient,
    PLINERECEIVEMSPDATA_PARAMS  pParams,
    DWORD                       dwParamsBufferSize,
    LPBYTE                      pDataBuf,
    LPDWORD                     pdwNumBytesReturned
    )
{
    TSPIPROC        pfnTSPI_lineReceiveMSPData;
    PTPROVIDER      ptProvider;
    PTLINECLIENT    ptLineClient;
    PTCALLCLIENT    ptCallClient = NULL;
    HDRVMSPLINE     hdMSPLine;
    HDRVCALL        hdCall;
    HDRVLINE        hdLine;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (ISBADSIZEOFFSET(
            dwParamsBufferSize,
            0,
            pParams->dwBufferSize,
            pParams->dwBufferOffset,
            sizeof(DWORD),
            "LReceiveMSPData",
            "pParams->Buffer"
            ))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }

    if (!(ptLineClient = ReferenceObject(
            ghHandleTable,
            pParams->hLine,
            TLINECLIENT_KEY
            )))
    {
        pParams->lResult = LINEERR_INVALLINEHANDLE;
        return;
    }

    if (pParams->hCall)
    {
        if (!(ptCallClient = ReferenceObject(
                ghHandleTable,
                pParams->hCall,
                TCALLCLIENT_KEY
                )))
        {
            DereferenceObject (ghHandleTable, pParams->hLine, 1);
            pParams->lResult = LINEERR_INVALCALLHANDLE;
            return;
        }
    }
    
    hdMSPLine = ptLineClient->hdMSPLine;

    try
    {
        hdLine = ptLineClient->ptLine->hdLine;
        ptProvider = ptLineClient->ptLine->ptProvider;

        hdCall = (ptCallClient ? ptCallClient->ptCall->hdCall : 0);

        if (ptLineClient->dwKey != TLINECLIENT_KEY)
        {
            pParams->lResult = LINEERR_INVALLINEHANDLE;
        }
    }
    myexcept
    {
        pParams->lResult = LINEERR_INVALCALLHANDLE;
    }

    DereferenceObject (ghHandleTable, pParams->hLine, 1);
    
    if (ptCallClient)
    {
        DereferenceObject (ghHandleTable, pParams->hCall, 1);
    }

    if (pParams->lResult == 0)
    {
        if ((pfnTSPI_lineReceiveMSPData =
                ptProvider->apfn[SP_LINERECEIVEMSPDATA]))
        {
         PBYTE pTemp = pDataBuf + pParams->dwBufferOffset;
#if WIN64
            if (!(ALIGNED (pTemp)))
            {
                pTemp = ServerAlloc (pParams->dwBufferSize);
                if (NULL == pTemp)
                {
                    pParams->lResult = LINEERR_NOMEM;
                    goto LReceiveMSPData_Return;
                }

                CopyMemory (pTemp, pDataBuf + pParams->dwBufferOffset, pParams->dwBufferSize);
            }
#endif //WIN64
            pParams->lResult = CallSP5(
                pfnTSPI_lineReceiveMSPData,
                "lineReceiveMSPData",
                SP_FUNC_SYNC,
                (ULONG_PTR) hdLine,
                (ULONG_PTR) hdCall,
                (ULONG_PTR) hdMSPLine,
                (ULONG_PTR) pTemp,
                (DWORD) pParams->dwBufferSize
                );
#if WIN64
            if (pTemp != pDataBuf + pParams->dwBufferOffset)
            {
                ServerFree (pTemp);
            }
#endif //WIN64
        }
        else
        {
            pParams->lResult = LINEERR_OPERATIONUNAVAIL;
        }
    }

#if WIN64
LReceiveMSPData_Return:
#endif //WIN64
    {
        char szResult[32];

#if DBG
        LOG((TL_TRACE, 
            "ReceiveMSPData: exit, result=%s",
            MapResultCodeToText (pParams->lResult, szResult)
            ));
#else
        LOG((TL_TRACE, 
            "ReceiveMSPData: exit, result=x%x",
            pParams->lResult
            ));
#endif //DBG
    }
}

void
WINAPI
LMSPIdentify(
    PTCLIENT                ptClient,
    PLINEMSPIDENTIFY_PARAMS pParams,
    DWORD                   dwParamsBufferSize,
    LPBYTE                  pDataBuf,
    LPDWORD                 pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID = pParams->dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_lineMSPIdentify;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (dwParamsBufferSize < sizeof (GUID))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            0,                          // client widget handle
            (LPVOID) &dwDeviceID,       // provider widget handle
            pParams->dwDeviceID,        // privileges or deviceID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_LINEMSPIDENTIFY,         // provider func index
            &pfnTSPI_lineMSPIdentify,   // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "MSPIdentify"               // func name

            )) == 0)
    {
        if ((pParams->lResult = CallSP2(
                pfnTSPI_lineMSPIdentify,
                "lineMSPIdentify",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (ULONG_PTR) pDataBuf

                )) == 0)
        {
            
            
            pParams->dwCLSIDOffset = 0;

            pParams->dwCLSIDSize = sizeof (GUID);

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pParams->dwCLSIDSize;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "MSPIdentify"
        );
}


void
WINAPI
PrivateFactoryIdentify(
    PTCLIENT                        ptClient,
    PPRIVATEFACTORYIDENTIFY_PARAMS  pParams,
    DWORD                           dwParamsBufferSize,
    LPBYTE                          pDataBuf,
    LPDWORD                         pdwNumBytesReturned
    )
{
    BOOL                bCloseMutex;
    DWORD               dwDeviceID = pParams->dwDeviceID;
    HANDLE              hMutex;
    TSPIPROC            pfnTSPI_providerPrivateFactoryIdentify;
    DWORD               objectToDereference;
    PTLINELOOKUPENTRY   pLookupEntry;


    //
    // Verify size/offset/string params given our input buffer/size
    //

    if (dwParamsBufferSize < sizeof (GUID))
    {
        pParams->lResult = LINEERR_OPERATIONFAILED;
        return;
    }


    if ((pParams->lResult = LINEPROLOG(
            ptClient,          // tClient
            DEVICE_ID,                  // widget type
            0,                          // client widget handle
            (LPVOID) &dwDeviceID,       // provider widget handle
            pParams->dwDeviceID,        // privileges or deviceID
            &hMutex,                    // mutex handle
            &bCloseMutex,               // close hMutex when finished
            SP_PROVIDERPRIVATEFACTORYIDENTIFY,          // provider func index
            &pfnTSPI_providerPrivateFactoryIdentify,    // provider func pointer
            NULL,                       // async request info
            0,                          // client async request ID
            &objectToDereference,       // object to dereference
            &pLookupEntry,              // context
            "PrivateFactoryIdentify"    // func name

            )) == 0)
    {
        if ((pParams->lResult = CallSP2(
                pfnTSPI_providerPrivateFactoryIdentify,
                "providerPrivateFactoryIdentify",
                SP_FUNC_SYNC,
                (DWORD) dwDeviceID,
                (ULONG_PTR) pDataBuf

                )) == 0)
        {
            pParams->dwCLSIDOffset = 0;

            pParams->dwCLSIDSize = sizeof (GUID);

            *pdwNumBytesReturned = sizeof (TAPI32_MSG) + pParams->dwCLSIDSize;
        }
    }

    LINEEPILOGSYNC(
        &pParams->lResult,
        hMutex,
        bCloseMutex,
        objectToDereference,
        "PrivateFactoryIdentify"
        );
}

LPBYTE 
NewToOldLineforwardlist( 
    LPLINEFORWARDLIST pFwdList3_1 
    )
{

    DWORD           dwOffset3_0, dwOffset3_1, i;
    DWORD           dwTotalSize;
    DWORD           dwSizeofLFwdList3_0 = sizeof (LINEFORWARDLIST) - 2 * sizeof(DWORD);
    DWORD           dwSizeofLFwd3_0     = sizeof (LINEFORWARD) - 2 * sizeof(DWORD);
    LPLINEFORWARD   pFwdEntry3_1        = NULL;
    LPBYTE          pFwdEntry3_0        = NULL;
    LPBYTE          pFwdList3_0         = NULL;

    //
    // Alloc a buffer for storing the converted FORWARDLIST
    //

    dwTotalSize = pFwdList3_1->dwTotalSize - 2 * sizeof(DWORD) * pFwdList3_1->dwNumEntries;
    pFwdList3_0 = ServerAlloc (dwTotalSize);
    if (!pFwdList3_0)
        return NULL;
    memset (pFwdList3_0, 0, dwTotalSize);


    ((LPLINEFORWARDLIST)pFwdList3_0)->dwTotalSize = dwTotalSize;
    ((LPLINEFORWARDLIST)pFwdList3_0)->dwNumEntries = pFwdList3_1->dwNumEntries;


    dwOffset3_1 = sizeof (LINEFORWARDLIST) +
                    (pFwdList3_1->dwNumEntries - 1) * sizeof (LINEFORWARD);

    dwOffset3_0 = dwSizeofLFwdList3_0 + 
                    (pFwdList3_1->dwNumEntries - 1) * dwSizeofLFwd3_0;


    CopyMemory (pFwdList3_0 + dwOffset3_0, (LPBYTE)pFwdList3_1 + dwOffset3_1, 
                    pFwdList3_1->dwTotalSize - dwOffset3_1);

    pFwdEntry3_1 = pFwdList3_1->ForwardList;
    pFwdEntry3_0 = (LPBYTE)((LPLINEFORWARDLIST)pFwdList3_0)->ForwardList;

    for (i = 0; i < pFwdList3_1->dwNumEntries; i++, pFwdEntry3_1++)
    {
        CopyMemory (pFwdEntry3_0, pFwdEntry3_1, dwSizeofLFwd3_0);

        if (pFwdEntry3_1->dwCallerAddressSize)
        {
            ((LPLINEFORWARD)pFwdEntry3_0)->dwCallerAddressOffset = 
                pFwdEntry3_1->dwCallerAddressOffset - 2 * sizeof(DWORD) * pFwdList3_1->dwNumEntries;
        }

        if (pFwdEntry3_1->dwDestAddressSize)
        {
            ((LPLINEFORWARD)pFwdEntry3_0)->dwDestAddressOffset = 
                pFwdEntry3_1->dwDestAddressOffset - 2 * sizeof(DWORD) * pFwdList3_1->dwNumEntries;
        }

        pFwdEntry3_0 += dwSizeofLFwd3_0;
                
    }

    return pFwdList3_0;
  
}

LPLINEFORWARDLIST 
OldToNewLineforwardlist( 
    LPLINEFORWARDLIST pFwdList3_0 
    )
{

    DWORD               dwOffset3_0, dwOffset3_1, i;
    DWORD               dwSizeofLFwdList3_0 = sizeof (LINEFORWARDLIST) - 2 * sizeof(DWORD);
    DWORD               dwSizeofLFwd3_0     = sizeof (LINEFORWARD) - 2 * sizeof(DWORD);
    DWORD               dwTotalSize;
    LPLINEFORWARD       pFwdEntry3_1        = NULL;
    LPBYTE              pFwdEntry3_0        = NULL;
    LPLINEFORWARDLIST   pFwdList3_1         = NULL;

    //
    // Alloc a buffer for storing the converted FORWARDLIST
    //

    dwTotalSize = pFwdList3_0->dwTotalSize + 2 * sizeof(DWORD) * pFwdList3_0->dwNumEntries;
    pFwdList3_1 = ServerAlloc (dwTotalSize);
    if (!pFwdList3_1)
        return NULL;
    memset (pFwdList3_1, 0, dwTotalSize);


    pFwdList3_1->dwTotalSize = dwTotalSize;
    pFwdList3_1->dwNumEntries = pFwdList3_0->dwNumEntries;


    dwOffset3_1 = sizeof (LINEFORWARDLIST) +
                    (pFwdList3_0->dwNumEntries - 1) * sizeof (LINEFORWARD);

    dwOffset3_0 = dwSizeofLFwdList3_0 + 
                    (pFwdList3_0->dwNumEntries - 1) * dwSizeofLFwd3_0;


    CopyMemory ((LPBYTE)pFwdList3_1 + dwOffset3_1, (LPBYTE)pFwdList3_0 + dwOffset3_0, 
                    pFwdList3_0->dwTotalSize - dwOffset3_0);

    pFwdEntry3_1 = pFwdList3_1->ForwardList;
    pFwdEntry3_0 = (LPBYTE)(pFwdList3_0->ForwardList);

    for (i = 0; i < pFwdList3_0->dwNumEntries; i++, pFwdEntry3_1++)
    {
        CopyMemory (pFwdEntry3_1, pFwdEntry3_0, dwSizeofLFwd3_0);

        if ( ((LPLINEFORWARD)pFwdEntry3_0)->dwCallerAddressSize )
        {
            pFwdEntry3_1->dwCallerAddressOffset = 
                ((LPLINEFORWARD)pFwdEntry3_0)->dwCallerAddressOffset + 
                2 * sizeof(DWORD) * pFwdList3_0->dwNumEntries;
        }

        if ( ((LPLINEFORWARD)pFwdEntry3_0)->dwDestAddressSize )
        {
            pFwdEntry3_1->dwDestAddressOffset = 
                ((LPLINEFORWARD)pFwdEntry3_0)->dwDestAddressOffset +
                2 * sizeof(DWORD) * pFwdList3_0->dwNumEntries;
        }

        pFwdEntry3_0 += dwSizeofLFwd3_0;
    }

    return pFwdList3_1;
  
}


LPWSTR WaveDeviceIdToStringId(DWORD dwDeviceId, LPWSTR pwszDeviceType)
{
    LPWSTR pwszStringID = NULL;
    DWORD       dwSize;
    DWORD_PTR   dwParam;

    if (!pwszDeviceType)
        return NULL;

    do 
    {
        if ( !_wcsicmp(pwszDeviceType, L"wave/in") )
        {
            HWAVEIN     hWaveIn;

            *(DWORD_PTR*)&hWaveIn = dwDeviceId;

            // get the needed size
            if (MMSYSERR_NOERROR != waveInMessage(
                                    hWaveIn,
                                    DRV_QUERYSTRINGIDSIZE,
                                    (DWORD_PTR)&dwSize,
                                    0))
                break;

            assert (dwSize != 0);
            pwszStringID = ServerAlloc (dwSize);
            if(!pwszStringID)
                break;

            dwParam = dwSize;
            // get the wave string ID
            if (MMSYSERR_NOERROR != waveInMessage(
                                    hWaveIn,
                                    DRV_QUERYSTRINGID,
                                    (DWORD_PTR)pwszStringID,
                                    dwParam))
            {
                ServerFree(pwszStringID);
                pwszStringID = NULL;
            }

        } else if (!_wcsicmp(pwszDeviceType, L"wave/out"))
        {
            HWAVEOUT     hWaveOut;
            *(DWORD_PTR*)&hWaveOut = dwDeviceId;

            // get the needed size
            if (MMSYSERR_NOERROR != waveOutMessage(
                                    hWaveOut,
                                    DRV_QUERYSTRINGIDSIZE,
                                    (DWORD_PTR)&dwSize,
                                    0))
                break;

            assert (dwSize != 0);
            pwszStringID = ServerAlloc (dwSize);
            if(!pwszStringID)
                break;

            dwParam = dwSize;
            // get the wave string ID
            if (MMSYSERR_NOERROR != waveOutMessage(
                                    hWaveOut,
                                    DRV_QUERYSTRINGID,
                                    (DWORD_PTR)pwszStringID,
                                    dwParam))
            {
                ServerFree(pwszStringID);
                pwszStringID = NULL;
            }
        } else if (!_wcsicmp(pwszDeviceType, L"midi/in"))
        {
            HMIDIIN     hMidiIn;
            *(DWORD_PTR*)&hMidiIn = dwDeviceId;

            // get the needed size
            if (MMSYSERR_NOERROR != midiInMessage(
                                    hMidiIn,
                                    DRV_QUERYSTRINGIDSIZE,
                                    (DWORD_PTR)&dwSize,
                                    0))
                break;

            assert (dwSize != 0);
            pwszStringID = ServerAlloc (dwSize);
            if(!pwszStringID)
                break;

            dwParam = dwSize;
            // get the wave string ID
            if (MMSYSERR_NOERROR != midiInMessage(
                                    hMidiIn,
                                    DRV_QUERYSTRINGID,
                                    (DWORD_PTR)pwszStringID,
                                    dwParam))
            {
                ServerFree(pwszStringID);
                pwszStringID = NULL;
            }
        }  else if (!_wcsicmp(pwszDeviceType, L"midi/out"))
        {
            HMIDIOUT     hMidiOut;
            *(DWORD_PTR*)&hMidiOut = dwDeviceId;

            // get the needed size
            if (MMSYSERR_NOERROR != midiOutMessage(
                                    hMidiOut,
                                    DRV_QUERYSTRINGIDSIZE,
                                    (DWORD_PTR)&dwSize,
                                    0))
                break;

            assert (dwSize != 0);
            pwszStringID = ServerAlloc (dwSize);
            if(!pwszStringID)
                break;

            dwParam = dwSize;
            // get the wave string ID
            if (MMSYSERR_NOERROR != midiOutMessage(
                                    hMidiOut,
                                    DRV_QUERYSTRINGID,
                                    (DWORD_PTR)pwszStringID,
                                    dwParam))
            {
                ServerFree(pwszStringID);
                pwszStringID = NULL;
            }
        } 

    } while (0);

    return pwszStringID;

}
