//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//------------------------------------------------------------------------------
// 
//      Bluetooth SDP Name Space Layer
// 
// 
// Module Name:
// 
//      sdpns.cxx
// 
// Abstract:
// 
//      This file implements SDP Name Space handling and inquiry.
// 
// 
//------------------------------------------------------------------------------



#include "common.h"
#include <winsock2.h>
#include <svsutil.hxx>
#include <bt_debug.h>

// declarations and global data
static int bthns_ServiceSearch_Out(void *pCallContext, unsigned long status, unsigned short cReturnedHandles, unsigned long *pHandles);
static int bthns_AttributeSearch_Out(void *pCallContext, unsigned long status, unsigned char *pOutBuf, unsigned long cOutBuf);
static int bthns_ServiceAttributeSearch_Out(void *pCallContext, unsigned long status, unsigned char *pOutBuf, unsigned long cOutBuf);
static int bthns_StackEvent(void *pUserContext, int iEvent, void *pEventContext);
static int bthns_Connect_Out(void *pCallContext, unsigned long status, unsigned short cid);
static int bthns_Disconnect_Out(void *pCallContext, unsigned long status);


// On searching, number of milliseconds to wait for a response before canceling search.
#define SDP_SEARCH_DEFAULT_TIMEOUT      30000

// Timeouts for connect/disconnect SDP connection
#define SDP_CONNECT_TIMEOUT             10000
#define SDP_DISCONNECT_TIMEOUT          5000

static DWORD         g_dwSearchTimeout;
static const TCHAR   g_szSDPBaseKey[] = TEXT("Software\\Microsoft\\Bluetooth\\SDP");
static const TCHAR   g_szTimeoutVal[] = TEXT("SearchTimeOut");

struct Call {
    Call          *pNext;
    HANDLE        hEvent;

    // Return values to SDP calls.  Note: Call does NOT free buffers
    int               iResult;
    unsigned short cid;
    unsigned char  *pClientBuf;       // client output buffer
    unsigned long  cClientBuf;        // sizeof client output buffer.
};

#define INQUIRY_PENDING     1
#define INQUIRY_INPROGRESS  2
#define INQUIRY_COMPLETED   3

struct BthNsHandle {
    // BthNsHandle internals
    BthNsHandle             *pNext;
    Call                    *pCall;

    // BthNsHandle's initial values.
    DWORD                   dwFlags;
    PBTHNS_RESTRICTIONBLOB  pResBlob;
    BD_ADDR                 b;

    // ServiceClassId and Range are only used when lpServiceClassId != NULL and no RestrcitionBlob set in WSALookupServiceBegin()
    GUID                    ServiceClassId;

    unsigned int            fLocal             : 1; // search to be done on local device
    unsigned int            fNetSearchComplete : 1; // has search been sent across the wire
    unsigned int            fNoMoreData        : 1; // when all data has been sent up to Winsock level.
    unsigned int            fXPCompatMode      : 1; // TRUE if we use XP bluetooth data structures, FALSE if we use legacy WinCE structs.
    unsigned int            fInquiryStatus     : 2; // PENDING/INPROGRESS/DONE
    unsigned char           *pClientBuf;            // client output buffer
    unsigned long           cClientBuf;             // sizeof client output buffer.

    unsigned long           LAP;
    unsigned int            cDuration;
    unsigned int            cMaxResp;

    // results from a device inquiry.
    BthInquiryResult        *pInquiryResults;
    unsigned int            cDevices;
    unsigned int            iDeviceIterator; // when iterating through device list in BthNsLookupServiceNext
};

enum BTHNS_STAGE {
    JustCreated            = 0,
    Initializing,
    Running,
    ShuttingDown
};

class BthNs : public SVSSynch, public SVSRefObj {
public:
    FixedMemDescr           *pfmdBthNsHandles;
    FixedMemDescr           *pfmdNSCAlls;
    
    BthNsHandle             *pBthNsHandles;
    Call                    *pCalls;
    

    SDP_INTERFACE           sdp_if;
    HANDLE                  hSDP;
    BTHNS_STAGE             eStage;

    void ReInit(void) {
        pfmdBthNsHandles    = NULL;
        pfmdNSCAlls         = NULL;
        pBthNsHandles       = NULL;
        pCalls              = NULL;

        hSDP                = NULL;
        memset(&sdp_if,0,sizeof(sdp_if));

        eStage              = JustCreated;
    }

    BthNs(void) {
        ReInit();
    }
};

BthNs *gpBthNS = NULL;


static BOOL DeleteBthNsHandle(BthNsHandle *pBthNsHandle)  {
    SVSUTIL_ASSERT(gpBthNS->IsLocked());

    BOOL fRet = TRUE;
    
    if (pBthNsHandle == gpBthNS->pBthNsHandles)
        gpBthNS->pBthNsHandles = pBthNsHandle->pNext;
    else {
        BthNsHandle *pParent = gpBthNS->pBthNsHandles;
        while (pParent && (pParent->pNext != pBthNsHandle))
            pParent = pParent->pNext;

        if (pParent)
            pParent->pNext = pBthNsHandle->pNext;
        else
            fRet = FALSE;
    }

    if (fRet) {
        if (pBthNsHandle->pClientBuf)
            ExFreePool(pBthNsHandle->pClientBuf);

        if (pBthNsHandle->pResBlob)
            g_funcFree(pBthNsHandle->pResBlob,g_pvAllocData);

        if (pBthNsHandle->pInquiryResults)
            g_funcFree(pBthNsHandle->pInquiryResults,g_pvAllocData);

        svsutil_FreeFixed(pBthNsHandle, gpBthNS->pfmdBthNsHandles);
    }
    return fRet;
}

static void DeleteCall(Call *pCall) {
    BOOL fFound = TRUE;

    if (pCall == gpBthNS->pCalls)
        gpBthNS->pCalls = pCall->pNext;
    else {
        Call *pParent = gpBthNS->pCalls;
        while (pParent && (pParent->pNext != pCall))
            pParent = pParent->pNext;

        if (pParent)
            pParent->pNext = pCall->pNext;
        else
            fFound = FALSE;
    }

    if (fFound) {
        CloseHandle(pCall->hEvent);
        if (pCall->pClientBuf)
            g_funcFree(pCall->pClientBuf,g_pvAllocData);

        svsutil_FreeFixed(pCall, gpBthNS->pfmdNSCAlls);
    }
}

static BOOL ConvertStringToBdAddr(WCHAR *szAddress, BD_ADDR *pb) {
    DWORD          i;
    LPWSTR         p = szAddress;
    
    for (i=0; i<6 && *p; i++) {
        if (i==0 && *p==L'(') {
            p++;
        }
        
        while (*p && ((*p>=L'0' && *p<=L'9') ||
              (*p>=L'a' && *p<=L'f') ||
              (*p>=L'A' && *p<=L'F')))
        {
            ((PUCHAR)pb)[5-i] *= 16;

            if (*p>=L'0' && *p<=L'9') {
                ((PUCHAR)pb)[5-i] += *p - L'0';
            } 
            else if (*p>=L'a' && *p<=L'f') {
                ((PUCHAR)pb)[5-i] += *p + 10 - L'a';
            }
            else {
                ((PUCHAR)pb)[5-i] += *p + 10 - L'A';
            }
            p++;
        }

        if (*p==L'(') {
            // This can occur at the start of the string
            // But we're not at the start
            return FALSE;
        } 
        else if (*p==L')') {
            // This can occur at the end only
            if (i<5) {
                return FALSE;
            }
            p++;
        } 
        else if (*p==L'.' || *p==L':') {
            p++;
        } 
        else if (!*p) {
            if (i<5) {
                return FALSE;
            }
        } 
        else {
            return WSAEINVAL;
        }
    }
    return TRUE;
}

static BthNsHandle * AllocBthNsHandle(LPWSAQUERYSET pQuerySet, DWORD dwFlags, int *piError)  {
    SVSUTIL_ASSERT(gpBthNS->IsLocked());
    PBTHNS_RESTRICTIONBLOB pResBlob = (PBTHNS_RESTRICTIONBLOB) ( pQuerySet->lpBlob ? pQuerySet->lpBlob->pBlobData : NULL);
    BOOL fSucces = FALSE;

    BthNsHandle *pBthNsHandle = (BthNsHandle *)svsutil_GetFixed(gpBthNS->pfmdBthNsHandles);
    if (!pBthNsHandle)
        goto done;

    memset(pBthNsHandle,0,sizeof(*pBthNsHandle));


    pBthNsHandle->dwFlags = dwFlags;

    // Get the remote address
    if (dwFlags & LUP_RES_SERVICE) {
        pBthNsHandle->fLocal = TRUE;
    } 
    else if (! (dwFlags & LUP_CONTAINERS)) {
        if (pQuerySet->lpcsaBuffer) {
            // Legacy CE mode, in which lpcsaBuffer is passed.
            SOCKADDR_BTH *pSockBT = (SOCKADDR_BTH *) pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr;
#if defined (DEBUG) || defined (_DEBUG)
            if (! __SDP_IS_ALIGNED(pSockBT, 8))
                DebugOut(DEBUG_SDP_TRACE, L"SDP: Unaligned remote address in structure - FIX THE APP!\n");
#endif
            memcpy(&pBthNsHandle->b,(void *)&pSockBT->btAddr,sizeof(pBthNsHandle->b));
        }
        else {    
            // On Windows XP, address is passed as a readable string in lpszContext.  Implement for compat.
            if (! ConvertStringToBdAddr(pQuerySet->lpszContext,&pBthNsHandle->b))
                goto done;

            pBthNsHandle->fXPCompatMode = TRUE;
        }
    }

    // figure out what to query on remote device
    if (! (dwFlags & LUP_CONTAINERS)) {
        if (pResBlob) {
            if (NULL == (pBthNsHandle->pResBlob = (PBTHNS_RESTRICTIONBLOB) g_funcAlloc(pQuerySet->lpBlob->cbSize,g_pvAllocData))) 
                goto done;

            memcpy(pBthNsHandle->pResBlob,pResBlob,pQuerySet->lpBlob->cbSize);
        }
        else {
            memcpy(&pBthNsHandle->ServiceClassId,pQuerySet->lpServiceClassId,sizeof(GUID));
        }
    }

    pBthNsHandle->pNext    = gpBthNS->pBthNsHandles;
    gpBthNS->pBthNsHandles = pBthNsHandle;
    fSucces              = TRUE;
done:
    if (!fSucces) {
        if (pBthNsHandle) {
            SVSUTIL_ASSERT(pBthNsHandle != gpBthNS->pBthNsHandles);
            SVSUTIL_ASSERT(!pBthNsHandle->pResBlob);

            svsutil_FreeFixed(pBthNsHandle, gpBthNS->pfmdBthNsHandles);
            pBthNsHandle = NULL;
            *piError = WSAEINVAL;
        }
        else
            *piError = WSA_NOT_ENOUGH_MEMORY;
    }

    return pBthNsHandle;
}

static Call * AllocCall(BthNsHandle *pBthNsHandle) {
    SVSUTIL_ASSERT(gpBthNS->IsLocked());

    Call *pCall = (Call *)svsutil_GetFixed(gpBthNS->pfmdNSCAlls);
    if (!pCall)
        return NULL;

    memset(pCall,0,sizeof(*pCall));

    if (NULL == (pCall->hEvent = CreateEvent(NULL,FALSE,FALSE,NULL))) {
        svsutil_FreeFixed(pCall, gpBthNS->pfmdNSCAlls);
    }

    pCall->pNext      = gpBthNS->pCalls;
    gpBthNS->pCalls   = pCall;
    pBthNsHandle->pCall = pCall;

    return pCall;
}


static BthNsHandle * FindBthNsHandle (BthNsHandle *pBthNsHandle) {
    BthNsHandle *pTrav = gpBthNS->pBthNsHandles;
    while (pTrav && pBthNsHandle != pTrav)
        pTrav = pTrav->pNext;

    return pTrav;
}

static Call * FindCall (Call *pCall) {
    Call *pTrav = gpBthNS->pCalls;
    while (pTrav && pCall != pTrav)
        pTrav = pTrav->pNext;

    return pTrav;
}


//
// Driver Interface functions
//
int bthns_InitializeOnce(void) {
    IFDBG(DebugOut(DEBUG_SDP_INIT | DEBUG_SDP_TRACE,L"bthns_InitializeOnce()\r\n"));

    if (gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_InitializeOnce:: ERROR_ALREADY_EXISTS\r\n"));
        return ERROR_ALREADY_EXISTS;
    }

    gpBthNS = new BthNs;
    if (!gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_InitializeOnce:: ERROR_OUTOFMEMORY\r\n"));
        return ERROR_OUTOFMEMORY;
    }

    return ERROR_SUCCESS;
}

int bthns_CreateDriverInstance(void)  {
    HKEY hKey;
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"bthns_CreateDriverInstance entered\r\n"));

    if (! gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_CreateDriverInstance:: ERROR_SERVICE_DOES_NOT_EXIST\n"));
        return ERROR_SERVICE_DOES_NOT_EXIST;
    }

    gpBthNS->Lock();
    if (gpBthNS->eStage != JustCreated) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_CreateDriverInstance:: ERROR_SERVICE_ALREADY_RUNNING\n"));
        gpBthNS->Unlock();
        return ERROR_SERVICE_ALREADY_RUNNING;
    }
    gpBthNS->eStage = Initializing;

    if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, g_szSDPBaseKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        DWORD dwType;
        DWORD dwSize;

        if ((RegQueryValueEx (hKey, g_szTimeoutVal, 0, &dwType, (LPBYTE)&g_dwSearchTimeout, &dwSize) != ERROR_SUCCESS) ||
            (dwType != REG_DWORD) || (dwSize != sizeof(g_dwSearchTimeout))) {
            g_dwSearchTimeout = SDP_SEARCH_DEFAULT_TIMEOUT;
        }
        RegCloseKey (hKey);
    }
    else
        g_dwSearchTimeout = SDP_SEARCH_DEFAULT_TIMEOUT;

    IFDBG(DebugOut(DEBUG_SDP_INIT,L"SDP Default search timeout = 0x%08x milliseconds\r\n",g_dwSearchTimeout));
    SVSUTIL_ASSERT(g_dwSearchTimeout);

    SVSUTIL_ASSERT(! (gpBthNS->pfmdBthNsHandles && gpBthNS->pfmdNSCAlls && 
                      gpBthNS->pBthNsHandles    && gpBthNS->pCalls));

    gpBthNS->pfmdBthNsHandles = svsutil_AllocFixedMemDescr(sizeof(BthNsHandle), 10);
    gpBthNS->pfmdNSCAlls   = svsutil_AllocFixedMemDescr(sizeof(Call), 10);
    
    if (!gpBthNS->pfmdBthNsHandles || !gpBthNS->pfmdNSCAlls) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_CreateDriverInstance:: ERROR_OUTOFMEMORY\n"));
        gpBthNS->Unlock();
        return ERROR_OUTOFMEMORY;
    }

    SDP_CALLBACKS          c;
    SDP_EVENT_INDICATION   ei;

    memset(&c,0,sizeof(c));
    c.sdp_Connect_Out                = bthns_Connect_Out;
    c.sdp_ServiceSearch_Out          = bthns_ServiceSearch_Out;
    c.sdp_AttributeSearch_Out        = bthns_AttributeSearch_Out;
    c.sdp_ServiceAttributeSearch_Out = bthns_ServiceAttributeSearch_Out;
    c.sdp_Disconnect_Out             = bthns_Disconnect_Out;
   
    ei.sdp_StackEvent                = bthns_StackEvent;

    int iErr = SDP_EstablishDeviceContext(NULL,&ei,&c,&gpBthNS->sdp_if,&gpBthNS->hSDP);
    if (iErr != ERROR_SUCCESS) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_CreateDriverInstance couldn't plug into SDP, err=0x%08x\n",iErr));
        gpBthNS->Unlock();
        return iErr;
    }

    SVSUTIL_ASSERT(gpBthNS->hSDP);

    // SDP lower layer determines whether or not the lower stack is running or not.
    gpBthNS->eStage = Running;
    gpBthNS->Unlock();
    
    return ERROR_SUCCESS;
}

int bthns_CloseDriverInstance(void)  {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BTHNS_CloseDriverInstance entered\r\n"));
    SDP_CloseDeviceContext(gpBthNS->hSDP);

    if (! gpBthNS)  {
        IFDBG(DebugOut(DEBUG_ERROR,L"sdp_CloseDriverInstance: ERROR_SERVICE_NOT_ACTIVE\r\n"));
        return ERROR_SERVICE_NOT_ACTIVE;
    }    

    gpBthNS->Lock();
    if ((gpBthNS->eStage != Running) && (gpBthNS->eStage != Initializing)) {
        IFDBG(DebugOut(DEBUG_ERROR, L"SDP Close Driver Instance:: ERROR_SERVICE_NOT_ACTIVE\n"));
        gpBthNS->Unlock();
        return ERROR_SERVICE_NOT_ACTIVE;
    }
    gpBthNS->eStage   = ShuttingDown;

    while (gpBthNS->pCalls) {
        SetEvent(gpBthNS->pCalls->hEvent);
        gpBthNS->pCalls->iResult = ERROR_CANCELLED;
        gpBthNS->pCalls = gpBthNS->pCalls->pNext;
    }

    while (gpBthNS->pBthNsHandles) {
        DeleteBthNsHandle(gpBthNS->pBthNsHandles);
    }

    while (gpBthNS->GetRefCount() > 1) {
        IFDBG(DebugOut (DEBUG_SDP_TRACE, L"Waiting for ref count in bthns_CloseDriverInstance\n"));
        gpBthNS->Unlock ();
        Sleep (200);
        gpBthNS->Lock ();
    }

    if (gpBthNS->hSDP)
        SDP_CloseDeviceContext(gpBthNS->hSDP);

    if (gpBthNS->pfmdBthNsHandles)
        svsutil_ReleaseFixedNonEmpty(gpBthNS->pfmdBthNsHandles);
        
    if (gpBthNS->pfmdNSCAlls)
        svsutil_ReleaseFixedNonEmpty(gpBthNS->pfmdNSCAlls);

    gpBthNS->ReInit();
    gpBthNS->Unlock();
    return ERROR_SUCCESS;
}

int bthns_UninitializeOnce(void) {
    IFDBG(DebugOut(DEBUG_SDP_INIT | DEBUG_SDP_TRACE,L"bthns_UninitializeOnce()\r\n"));
    SVSUTIL_ASSERT(gpBthNS);

    if (! gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_UninitializeOnce:: ERROR_SERVICE_DOES_NOT_EXIST\n"));
        return ERROR_SERVICE_DOES_NOT_EXIST;
    }
    gpBthNS->Lock();

    if (gpBthNS->eStage != JustCreated) {
        IFDBG(DebugOut(DEBUG_ERROR, L"bthns_UninitializeOnce:: ERROR_DEVICE_IN_USE\n"));
        gpBthNS->Unlock();
        return ERROR_DEVICE_IN_USE;
    }

    BthNs *pBthNs = gpBthNS;
    gpBthNS = NULL;
    pBthNs->Unlock();
    delete pBthNs;

    IFDBG(DebugOut(DEBUG_SDP_INIT, L"bthns_UninitializeOnce:: ERROR_SUCCESS\n"));
    return ERROR_SUCCESS;
}


static DWORD WINAPI StackDisconnect (LPVOID pArg) {
    IFDBG(DebugOut (DEBUG_SDP_TRACE, L"StackDisconnect\n"));
    bthns_CloseDriverInstance ();
    return NULL;
}

static int bthns_StackEvent(void *pUserContext, int iEvent, void *pEventContext) {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"bthns_StackEvent:: event = %d\r\n"));

    // We only handle BTH_STACK_DISCONNECT at this layer.  We just BthNsHandle into SDP
    // in all cases and let it keep track of stack state beyond this.
    if (iEvent == BTH_STACK_DISCONNECT)
        btutil_ScheduleEvent (StackDisconnect, NULL);

    return ERROR_SUCCESS;
}

//
// WSA Interface helpers.
//
void SDPAbortCall(HANDLE h, Call *pCall) {
    DebugOut(DEBUG_SDP_TRACE,L"SDPAbortCall (0x%08x)\r\n",pCall);
    BT_LAYER_ABORT_CALL  pCallback = gpBthNS->sdp_if.sdp_AbortCall;

    __try {
        pCallback(h,pCall);
    }
    __except (1) {
        ;
    }
}

int SDPConnectBlocking(Call *pCall, BD_ADDR *pba)  {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"SDPConnectBlocking (0x%04x 0x%08x)\r\n",pba->NAP,pba->SAP));
    int iRet;
    HANDLE hEvent = pCall->hEvent;
    HANDLE h      = gpBthNS->hSDP;

    SDP_Connect_In pCallback = gpBthNS->sdp_if.sdp_Connect_In;

    gpBthNS->AddRef();
    gpBthNS->Unlock();

    __try {
        iRet = pCallback(h,pCall,pba);
    }
    __except (1) {
        ;
    }

    if (ERROR_SUCCESS == iRet) {
        if (WAIT_OBJECT_0 != WaitForSingleObject(hEvent,SDP_CONNECT_TIMEOUT)) {
            IFDBG(DebugOut(DEBUG_ERROR,L"SDPConnectBlocking: Wait timed out or failed!\n"));
            iRet = ERROR_OPERATION_ABORTED;
            SDPAbortCall(h,pCall);
        }
    }

    gpBthNS->Lock();
    gpBthNS->DelRef();
    return iRet;
}

int SDPDisconnect(Call *pCall, unsigned short cid) {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"SDPDisconnectBlocking (0x%08x,0x%04x)\r\n",pCall,cid));
    int iRet;
    HANDLE h = gpBthNS->hSDP;
    HANDLE hEvent = pCall ? pCall->hEvent : 0;

    SDP_Disconnect_In pCallback = gpBthNS->sdp_if.sdp_Disconnect_In;

    gpBthNS->AddRef();
    gpBthNS->Unlock();

    __try {
        iRet = pCallback(h,pCall,cid);
    } __except(1)  {
        ;
    }

    if ((ERROR_SUCCESS == iRet) && hEvent) {
        if (WAIT_OBJECT_0 != WaitForSingleObject(hEvent,SDP_DISCONNECT_TIMEOUT)) {
            IFDBG(DebugOut(DEBUG_ERROR,L"SDPDisconnect: Wait timed out or failed!\n"));
            iRet = ERROR_OPERATION_ABORTED;
            SDPAbortCall(h,pCall);
        }
    }

    gpBthNS->Lock();
    gpBthNS->DelRef();
    return iRet;
}

int ServiceSearchBlocking(Call *pCall, unsigned short cid, SdpQueryUuid* pUUIDs, unsigned short cMaxHandles)  {
    SVSUTIL_ASSERT(gpBthNS->eStage == Running);
    HANDLE hEvent = pCall->hEvent;
    HANDLE h      = gpBthNS->hSDP;
    int iRet;

    SDP_ServiceSearch_In pCallback = gpBthNS->sdp_if.sdp_ServiceSearch_In;

    gpBthNS->AddRef();
    gpBthNS->Unlock();

    __try {
        iRet = pCallback(h,pCall,cid,pUUIDs,cMaxHandles);
    } __except(1) {
        ;
    }

    if (ERROR_SUCCESS == iRet) {
        if (WAIT_TIMEOUT == WaitForSingleObject(hEvent,g_dwSearchTimeout)) {
            iRet = ERROR_OPERATION_ABORTED;
            SDPAbortCall(h,pCall);
        }
    }

    gpBthNS->Lock();
    gpBthNS->DelRef();
    return iRet;
}


int AttributeSearchBlocking(Call *pCall, unsigned short cid, unsigned long recordHandle, SdpAttributeRange *pAttribRange, int numAttributes)  {
    SVSUTIL_ASSERT(gpBthNS->eStage == Running);
    HANDLE hEvent = pCall->hEvent;
    HANDLE h      = gpBthNS->hSDP;
    int iRet;

    SDP_AttributeSearch_In pCallback = gpBthNS->sdp_if.sdp_AttributeSearch_In;
    
    gpBthNS->AddRef();
    gpBthNS->Unlock();

    __try {
        iRet = pCallback(h,pCall,cid,recordHandle,pAttribRange,numAttributes);
    } __except(1) {
        ;
    }

    if (ERROR_SUCCESS == iRet) {
        if (WAIT_TIMEOUT == WaitForSingleObject(hEvent,g_dwSearchTimeout)) {
            iRet = ERROR_OPERATION_ABORTED;
            SDPAbortCall(h,pCall);
        }
    }

    gpBthNS->Lock();
    gpBthNS->DelRef();
    return iRet;
}

int ServiceAttributeSearchBlocking(Call *pCall, unsigned short cid, SdpQueryUuid* pUUIDs, SdpAttributeRange *pAttribRange, int numAttributes)  {
    SVSUTIL_ASSERT(gpBthNS->eStage == Running);
    HANDLE hEvent = pCall->hEvent;
    HANDLE h      = gpBthNS->hSDP;
    int iRet;

    SDP_ServiceAttributeSearch_In pCallback = gpBthNS->sdp_if.sdp_ServiceAttributeSearch_In;

    gpBthNS->AddRef();
    gpBthNS->Unlock();

    __try {
        iRet = pCallback(h,pCall,cid,pUUIDs,pAttribRange,numAttributes);
    } __except(1) {
        ;
    }

    if (ERROR_SUCCESS == iRet) {
        if (WAIT_TIMEOUT == WaitForSingleObject(hEvent,g_dwSearchTimeout)) {
            iRet = ERROR_OPERATION_ABORTED;
            SDPAbortCall(h,pCall);
        }
    }

    gpBthNS->Lock();
    gpBthNS->DelRef();
    return iRet;
}

typedef enum  {
    BTHNS_SET_SERVICE,
    BTHNS_LOOKUP_SERVICE_BEGIN,
    BTHNS_LOOKUP_SERVICE_NEXT
} BTHNS_WSA_CALL;

#define SDP_WSA_SERVICE_BEGIN_FLAGS   (LUP_RES_SERVICE | LUP_CONTAINERS)

static BOOL CheckQuerySet(LPWSAQUERYSET pQuerySet, BTHNS_WSA_CALL BthNsHandleer, DWORD dwFlags, WSAESETSERVICEOP op=RNRSERVICE_DEREGISTER)  {
    if (!pQuerySet || pQuerySet->dwSize != sizeof(WSAQUERYSETA) || pQuerySet->dwNameSpace != NS_BTH)
        return FALSE;

    switch (BthNsHandleer)  {
        case BTHNS_SET_SERVICE: {
            if (op != RNRSERVICE_REGISTER && op != RNRSERVICE_DELETE)
                return FALSE;

            if (dwFlags)
                return FALSE;

//            if (!pQuerySet->dwNumberOfCsAddrs || !pQuerySet->lpcsaBuffer)
//                return FALSE;

            PBTHNS_SETBLOB pSetBlob = (PBTHNS_SETBLOB) (pQuerySet->lpBlob ? pQuerySet->lpBlob->pBlobData : NULL);
            if (!pSetBlob || !pSetBlob->pSdpVersion || (*pSetBlob->pSdpVersion != BTH_SDP_VERSION) || !pSetBlob->pRecordHandle || ((op == RNRSERVICE_REGISTER) && !pSetBlob->ulRecordLength))
                return FALSE;
        }
        break;

        case BTHNS_LOOKUP_SERVICE_BEGIN:  {
            PBTHNS_RESTRICTIONBLOB pResBlob = (PBTHNS_RESTRICTIONBLOB) (pQuerySet->lpBlob ?  pQuerySet->lpBlob->pBlobData : NULL);
            if (dwFlags & ~SDP_WSA_SERVICE_BEGIN_FLAGS)
                return FALSE;

            if (dwFlags & LUP_CONTAINERS) {
                LPBLOB lpBlob = pQuerySet->lpBlob;
                if (lpBlob && (lpBlob->cbSize != sizeof(BTHNS_INQUIRYBLOB)))
                    return FALSE;

                return TRUE; // it's OK for lpBlob to be NULL or non-NULL when doing device inquiry.
            }

            // pQuerySet->lpServiceClassId can be set on Service or ServiceAttribute requests.
            // If pResBlob is set we use it, it has more interesting info.
            if (!pQuerySet->lpServiceClassId && !pResBlob)
                return FALSE;

            if (!(dwFlags & LUP_RES_SERVICE)) {
                // lpcsaBuffer contains an AddrInfo and is supported only on WinCE, specifies remote device to query.
                // pQuerySet->lpszContext contains a string representation of a bluetooth address of remote device to query, and is for XP compat.  
                if (!pQuerySet->lpszContext && !pQuerySet->lpcsaBuffer)
                    return FALSE;

                // lpszContext will be checked when we try to do conversion, just do lpcsaBuffer now.
                if (pQuerySet->lpcsaBuffer) {
                    if ((!pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr || (pQuerySet->lpcsaBuffer->RemoteAddr.iSockaddrLength != sizeof(SOCKADDR_BTH))))  {
                        return FALSE;
                    }

#if defined (DEBUG) || defined (_DEBUG)
                    if (! __SDP_IS_ALIGNED(pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr, 8))
                        DebugOut(DEBUG_SDP_TRACE, L"SDP: Unaligned remote address in structure - FIX THE APP!!\n");
#endif
                }
            }

            if (pResBlob && !(dwFlags&LUP_CONTAINERS))  {
                switch (pResBlob->type) {
                    case SDP_SERVICE_SEARCH_REQUEST:            
                    break;

                    case SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST:
                    case SDP_SERVICE_ATTRIBUTE_REQUEST:
                        if (!pResBlob->numRange ||  ((pQuerySet->lpBlob->cbSize - sizeof(BTHNS_RESTRICTIONBLOB)) % sizeof(SdpAttributeRange))  ||
                           (((pQuerySet->lpBlob->cbSize - sizeof(BTHNS_RESTRICTIONBLOB)) / sizeof(SdpAttributeRange) + 1) != pResBlob->numRange))
                            return FALSE;
                    break;

                    default:
                        return FALSE;
                }
            }
        }
        break;

        case BTHNS_LOOKUP_SERVICE_NEXT:  {
            if (!pQuerySet)
                return FALSE;
        }
        break;

        default:
            SVSUTIL_ASSERT(0);
    }
    return TRUE;
}


int MapErrorCodeToWinsockErr(int iError, BOOL fFromSetService=FALSE) {
    switch (iError) {

    // either lower layer failed, SDP timed out, or BthNsLookupServiceEnd() 
    // was called on pending search thread.
    case ERROR_OPERATION_ABORTED:
    case ERROR_CONNECTION_ABORTED:
    case ERROR_SHUTDOWN_IN_PROGRESS:
    case ERROR_CANCELLED:
        return WSA_E_CANCELLED;

    case ERROR_INTERNAL_ERROR:
    case ERROR_INVALID_PARAMETER:
        return WSAEINVAL;

    case ERROR_OUTOFMEMORY:
        return WSA_NOT_ENOUGH_MEMORY;

    case ERROR_NOT_FOUND:
        return fFromSetService ? WSA_INVALID_HANDLE : WSANO_DATA;

    case ERROR_SERVICE_NOT_ACTIVE:
        return WSASERVICE_NOT_FOUND;

    case ERROR_CONNECTION_UNAVAIL:
        return WSAENOTCONN;

    default:
        SVSUTIL_ASSERT(0);
    }
    return WSAEINVAL;
}

//
//  Public interface, equivalent to WSA Set/Lookup functions.
//
//  NOTE: This is preliminary, pending official data structures from NT.
//
int BthNsSetService(LPWSAQUERYSET pSet, WSAESETSERVICEOP op, DWORD dwFlags) {    
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsSetService entered\r\n"));

    if (! gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR, L"BthNsSetService:: ERROR_SERVICE_DOES_NOT_EXIST\n"));
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }
    
    gpBthNS->Lock();
    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR, L"BthNsSetService:: ERROR_SERVICE_NOT_ACTIVE\n"));
        gpBthNS->Unlock();
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }

    if (!CheckQuerySet(pSet,BTHNS_SET_SERVICE,dwFlags,op))  {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsSetService: ERROR_INVALID_PARAMETER\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSAEINVAL);
        return SOCKET_ERROR;
    }

    PBTHNS_SETBLOB pSetBlob = (PBTHNS_SETBLOB) pSet->lpBlob->pBlobData;
    int iRet;

    // Since these BthNsHandles operate loBthNsHandley, they are blocking.
    if (op == RNRSERVICE_REGISTER)
        iRet = gpBthNS->sdp_if.sdp_AddRecord(gpBthNS->hSDP,pSetBlob->pRecord,pSetBlob->ulRecordLength,(HANDLE*)(pSetBlob->pRecordHandle));
    else {
        SVSUTIL_ASSERT(op == RNRSERVICE_DELETE);
        iRet = gpBthNS->sdp_if.sdp_RemoveRecord(gpBthNS->hSDP,(HANDLE)(*(pSetBlob->pRecordHandle)));
    }

    gpBthNS->Unlock();
    if (iRet != ERROR_SUCCESS) {
        iRet = MapErrorCodeToWinsockErr(iRet,TRUE);
        SetLastError(iRet);
        return SOCKET_ERROR;
    }
    
    return ERROR_SUCCESS;
}

//
// WSA Interface functions
//

#define MAX_SEARCH_HANDLES             0xFFFF
#define INQUIRY_GIAC_LAP               0x9e8b33  // unlimited inquiry access mode (GIAC)
#define INQUIRY_DURATION               0x08
#define INQUIRY_MAX_RESPONSES          0x20

int BthNsLookupServiceBegin(LPWSAQUERYSET pQuerySet, DWORD dwFlags, LPHANDLE lphLookup)   {
    int iRes = ERROR_INTERNAL_ERROR;
    unsigned short cid = 0;
    BthNsHandle *pBthNsHandle = NULL;
    
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsLookupServiceBegin entered\r\n"));

    if (! gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR, L"BthNsLookupServiceBegin:: WSASERVICE_NOT_FOUND\n"));
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }    

    gpBthNS->Lock();
    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR, L"BthNsLookupServiceBegin:: WSASERVICE_NOT_FOUND\n"));
        gpBthNS->Unlock();
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }

    if (!CheckQuerySet(pQuerySet,BTHNS_LOOKUP_SERVICE_BEGIN,dwFlags) || !lphLookup)  {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceBegin: WSAEINVAL\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSAEINVAL);
        return SOCKET_ERROR;
    }

    if (NULL == (pBthNsHandle = AllocBthNsHandle(pQuerySet, dwFlags,&iRes)))   {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceBegin: ERROR_OUTOFMEMORY\r\n"));
        gpBthNS->Unlock();
        SetLastError(iRes);
        return SOCKET_ERROR;
    }

    if (dwFlags & LUP_CONTAINERS) {
        // device inquiry
        // If pInquiryBlob is NULL, then use default values.
        PBTHNS_INQUIRYBLOB pInquiryBlob = (PBTHNS_INQUIRYBLOB) ( pQuerySet->lpBlob ? pQuerySet->lpBlob->pBlobData : NULL);
        pBthNsHandle->LAP        = pInquiryBlob ? pInquiryBlob->LAP           : INQUIRY_GIAC_LAP;
        pBthNsHandle->cDuration  = pInquiryBlob ? pInquiryBlob->length        : INQUIRY_DURATION;

        // WinXP doesn't support pInquiryBlob->num_responses field.  To avoid
        // potential porting issues (i.e. an app on XP doesn't set this and on porting
        // it's a garbage value on CE) we ignore it.
        // pBthNsHandle->cMaxResp   = pInquiryBlob ? pInquiryBlob->num_responses : INQUIRY_MAX_RESPONSES;
        pBthNsHandle->cMaxResp   = INQUIRY_MAX_RESPONSES;
        pBthNsHandle->fInquiryStatus = INQUIRY_PENDING;
    }
    gpBthNS->Unlock();

    *lphLookup = (HANDLE) pBthNsHandle;

    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsLookupServiceBegin returns ERROR_SUCCESS\r\n"));
    return ERROR_SUCCESS;
}

int PerformInquiry (BthNsHandle *pBthNsHandle) {
    unsigned long LAP = pBthNsHandle->LAP;
    unsigned int cMaxResp = pBthNsHandle->cMaxResp;
    unsigned int cDuration = pBthNsHandle->cDuration;
    unsigned int cDevices = 0;

    BthInquiryResult *pInquiryResults = (BthInquiryResult *) g_funcAlloc(cMaxResp*sizeof(BthInquiryResult),g_pvAllocData);

    if (!pInquiryResults) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceBegin: ERROR_OUTOFMEMORY\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSA_NOT_ENOUGH_MEMORY);
        return SOCKET_ERROR;
    }

    memset(pInquiryResults,0,cMaxResp*sizeof(BthInquiryResult));

    pBthNsHandle->fInquiryStatus = INQUIRY_INPROGRESS;

    gpBthNS->AddRef();
    gpBthNS->Unlock();

    int iResult = BthPerformInquiry(LAP,cDuration,0,
                                    cMaxResp,&cDevices,pInquiryResults);

    gpBthNS->Lock();
    gpBthNS->DelRef();

    if (! FindBthNsHandle(pBthNsHandle)) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceBegin: handle (0x%08x) invalid after BthPerformInquiry()\r\n",pBthNsHandle));
        g_funcFree(pInquiryResults,g_pvAllocData);
        gpBthNS->Unlock();
        SetLastError(WSA_E_CANCELLED);
        return SOCKET_ERROR;
    }

    pBthNsHandle->fInquiryStatus = INQUIRY_COMPLETED;

    if ((ERROR_SUCCESS != iResult)) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceBegin: BthPerformInquiry returns 0x%08x\r\n",iResult));
        g_funcFree(pInquiryResults,g_pvAllocData);
        gpBthNS->Unlock();
        SetLastError(WSAENETDOWN);
        return SOCKET_ERROR;
    }

    if (cDevices > cMaxResp)
        cDevices = cMaxResp;

    pBthNsHandle->cDevices        = cDevices;
    pBthNsHandle->pInquiryResults = pInquiryResults;

    return ERROR_SUCCESS;
}

// supported Contairer flags for dwFlags param in BthNsLookupServiceNext
#define BTHNS_LOOKUP_SERVICE_NEXT_CONTAINER_FLAGS     (LUP_RETURN_NAME | LUP_RETURN_ADDR | LUP_RETURN_BLOB | BTHNS_LUP_RESET_ITERATOR | BTHNS_LUP_NO_ADVANCE)

int BthNsLookupServiceNext(HANDLE hLookup, DWORD dwFlags, LPDWORD lpdwBufferLength, LPWSAQUERYSET pResults) {
    int                   iRes = ERROR_INTERNAL_ERROR;
    unsigned short        cid  = 0;
    BOOL                  fLocal;
    DWORD                 cbRequired  = 0;
    PBTHNS_RESTRICTIONBLOB  pResBlob    = NULL;
    ULONG                 type;
    BOOL                  fDisconnect = FALSE;
    Call                  *pCall      = NULL;

    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsLookupServiceNext entered\r\n"));

    if (!lpdwBufferLength)  {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: params misformatted, ERROR_INVALID_PARAMETER\r\n"));
        SetLastError(WSAEINVAL);
        return SOCKET_ERROR;
    }

    if (!gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext:: ERROR_SERVICE_DOES_NOT_EXIST\r\n"));
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }
    gpBthNS->Lock();

    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext:: ERROR_SERVICE_NOT_ACTIVE\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }

    BthNsHandle *pBthNsHandle = FindBthNsHandle((BthNsHandle *) hLookup);
    if (!pBthNsHandle) { 
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsSetService: BthNsHandle not found, ERROR_INVALID_PARAMETER\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSA_INVALID_HANDLE);
        return SOCKET_ERROR;
    }

    //
    // Device Inquiry
    //
    if (pBthNsHandle->dwFlags & LUP_CONTAINERS) {
        if (pBthNsHandle->fInquiryStatus == INQUIRY_PENDING) {
            if (PerformInquiry (pBthNsHandle) == SOCKET_ERROR)    // If error, it unlocks
                return SOCKET_ERROR;
        }

        if (! __SDP_IS_ALIGNED(pResults, 8))
            DebugOut(DEBUG_SDP_TRACE, L"SDP: Unaligned remote address in structure - FIX THE APP!\n");

        if (dwFlags & ~(BTHNS_LOOKUP_SERVICE_NEXT_CONTAINER_FLAGS)) {
            IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext flags are invalid (=0x%08x)",dwFlags));
            gpBthNS->Unlock();
            SetLastError(WSAEFAULT);
            return SOCKET_ERROR;
        }

        // WinCE allows iterator list to be reset, allowing list to be traveresed again.
        if (dwFlags & BTHNS_LUP_RESET_ITERATOR) {
            pBthNsHandle->iDeviceIterator = 0;
            gpBthNS->Unlock();
            return ERROR_SUCCESS;
        }

        if (pBthNsHandle->iDeviceIterator >= pBthNsHandle->cDevices) {
            gpBthNS->Unlock();
            SetLastError(WSA_E_NO_MORE); 
            return SOCKET_ERROR;
        }

        cbRequired = sizeof(WSAQUERYSET);

        if (dwFlags & LUP_RETURN_NAME)
            cbRequired += BD_MAXNAME * sizeof(WCHAR);

        if (dwFlags & LUP_RETURN_ADDR) {
            cbRequired = __SDP_ALIGN(cbRequired, 4);
            cbRequired = __SDP_ALIGN((cbRequired + sizeof(CSADDR_INFO)), 8) + 2 * sizeof(SOCKADDR_BTH);
        }

        // Note: WinCE returns a different data structure on this call than NT, by design.
        if (dwFlags & LUP_RETURN_BLOB) {
            cbRequired = __SDP_ALIGN(cbRequired, 4);
            cbRequired = __SDP_ALIGN((cbRequired + sizeof(LPBLOB)), 8) + sizeof(BthInquiryResult);
        }

        if (cbRequired > *lpdwBufferLength) {
            IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsLookupServiceNext: %d bytes passed in, %d required\r\n",*lpdwBufferLength,cbRequired));
            *lpdwBufferLength = cbRequired;
            gpBthNS->Unlock();
            SetLastError(WSA_NOT_ENOUGH_MEMORY);
            return SOCKET_ERROR;
        }

        if (!pResults) {
            IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: params misformatted, ERROR_INVALID_PARAMETER\r\n"));
            gpBthNS->Unlock();
            SetLastError(WSAEINVAL);
            return SOCKET_ERROR;
        }

        __try {
            CHAR *pBase = (CHAR*)pResults;
            unsigned int uiOffsetCounter = sizeof(WSAQUERYSET);

            memset(pResults,0,sizeof(*pResults));
            pResults->dwSize = sizeof(WSAQUERYSETW);
            pResults->lpServiceClassId = 0;
            pResults->dwNameSpace = NS_BTH;

            if (dwFlags & LUP_RETURN_NAME) {
                // we don't query for names in WSALookupBegin, so we need to do it here.
                BT_ADDR ba = pBthNsHandle->pInquiryResults[pBthNsHandle->iDeviceIterator].ba;
                unsigned int cbReceived = 0;
                WCHAR   wszDeviceName[BD_MAXNAME];

                memset (wszDeviceName, 0, sizeof(wszDeviceName));

                gpBthNS->AddRef();
                gpBthNS->Unlock();

                iRes = BthRemoteNameQuery(&ba, SVSUTIL_ARRLEN(wszDeviceName), &cbReceived, wszDeviceName);

                gpBthNS->Lock();
                gpBthNS->DelRef();

                if (! FindBthNsHandle((BthNsHandle *) hLookup)) {
                    IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: handle (0x%08x) invalid after BthRemoteNameQuery()\r\n",hLookup));
                    gpBthNS->Unlock();
                    SetLastError(WSA_E_CANCELLED);
                    return SOCKET_ERROR;
                }

                // in the event of error, don't fail, just leave field NULL.
                if (iRes == ERROR_SUCCESS) {
                    pResults->lpszServiceInstanceName = (WCHAR *)(pBase + uiOffsetCounter);
                    wcsncpy (pResults->lpszServiceInstanceName, wszDeviceName, BD_MAXNAME);
                }
                uiOffsetCounter += BD_MAXNAME * sizeof(WCHAR);
            }

            if (dwFlags & LUP_RETURN_ADDR) {
                int uiOffsetCounterStart = uiOffsetCounter = __SDP_ALIGN(uiOffsetCounter, 4);

                PCSADDR_INFO pAddrInfo = (PCSADDR_INFO)(pBase + uiOffsetCounter);

                pResults->lpcsaBuffer = pAddrInfo;
                pResults->dwNumberOfCsAddrs = 1;

                uiOffsetCounter = __SDP_ALIGN(uiOffsetCounter + sizeof(CSADDR_INFO), 8);

                PSOCKADDR_BTH pLocal = (PSOCKADDR_BTH)(pBase + uiOffsetCounter);
                PSOCKADDR_BTH pRemote = pLocal + 1;

                uiOffsetCounter += 2 * sizeof(SOCKADDR_BTH);
                memset (pBase + uiOffsetCounterStart, 0, uiOffsetCounter - uiOffsetCounterStart);

                pAddrInfo->iSocketType = SOCK_STREAM;
                pAddrInfo->LocalAddr.lpSockaddr = (PSOCKADDR)pLocal;
                pAddrInfo->LocalAddr.iSockaddrLength = sizeof(*pLocal);
                pLocal->addressFamily = AF_BT;

                BthReadLocalAddr (&pLocal->btAddr);

                pAddrInfo->RemoteAddr.lpSockaddr = (PSOCKADDR)pRemote;
                pAddrInfo->RemoteAddr.iSockaddrLength = sizeof(*pRemote);
                pRemote->addressFamily = AF_BT;
                memcpy(&pRemote->btAddr, &pBthNsHandle->pInquiryResults[pBthNsHandle->iDeviceIterator].ba, sizeof(BT_ADDR));
            }

            if (dwFlags & LUP_RETURN_BLOB) {
                uiOffsetCounter = __SDP_ALIGN(uiOffsetCounter, 4);

                pResults->lpBlob = (LPBLOB)(pBase + uiOffsetCounter);
                pResults->lpBlob->cbSize    = sizeof(BthInquiryResult);

                uiOffsetCounter = __SDP_ALIGN(uiOffsetCounter + sizeof(BLOB), 8);

                pResults->lpBlob->pBlobData = (PBYTE) (pBase + uiOffsetCounter);
                memcpy(pResults->lpBlob->pBlobData,(BthInquiryResult *)&pBthNsHandle->pInquiryResults[pBthNsHandle->iDeviceIterator],sizeof(BthInquiryResult));
                uiOffsetCounter += sizeof(BthInquiryResult);
            }

            SVSUTIL_ASSERT (uiOffsetCounter == cbRequired);
        } __except (1) {
            IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: Exception!\n"));
            gpBthNS->Unlock();
            return ERROR_INVALID_PARAMETER;
        }
        // CE offers the option of not advancing iterator, so app may query for
        // more detailed info on this BT addr if it so desires.
        if (! (dwFlags & BTHNS_LUP_NO_ADVANCE))
            pBthNsHandle->iDeviceIterator++;

        gpBthNS->Unlock();

        return ERROR_SUCCESS;
    }

    //
    // Service Search
    //
    if (pBthNsHandle->fNoMoreData) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: WSA_E_NO_MORE\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSA_E_NO_MORE);
        return SOCKET_ERROR;
    }

    if (! pBthNsHandle->fNetSearchComplete) {
        fLocal = pBthNsHandle->fLocal;

        if (NULL == (pCall = AllocCall(pBthNsHandle))) {
            IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: WSA_NOT_ENOUGH_MEMORY\r\n"));
            gpBthNS->Unlock();
            SetLastError(WSA_NOT_ENOUGH_MEMORY);
            return SOCKET_ERROR;
        }

        // Below this point we may have to disconnect, so goto done for common exit pt.
        if (!fLocal) {
            iRes = SDPConnectBlocking(pCall,&pBthNsHandle->b);
            SVSUTIL_ASSERT(gpBthNS->IsLocked());

            if (iRes != ERROR_SUCCESS) {
                iRes = WSASERVICE_NOT_FOUND;
                goto done;
            }
            if (NULL == (pCall = FindCall(pCall)) || (pCall->iResult != ERROR_SUCCESS))   {
                iRes = pCall ? WSASERVICE_NOT_FOUND : WSA_E_CANCELLED;
                goto done;
            }
            fDisconnect = TRUE;
            cid = pCall->cid;

            if (NULL == (pBthNsHandle = FindBthNsHandle(pBthNsHandle))) {
                iRes = WSA_E_CANCELLED;
                goto done;
            }
        }
        pResBlob = pBthNsHandle->pResBlob;

        // To determine which type of inquiery to perform, if we have pResBlob do
        // what's specifed there.  If we don't we use the GUID set in ServiceClassId on
        // BthNsLookupServiceBegin call.  On CE to keep bc we assume SDP_SERVICE_SEARCH_REQUEST,
        // however if we're in XP compat mode then we do a SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST
        // with all attributes.
        if (pResBlob)
            type = pResBlob->type;
        else if (pBthNsHandle->fXPCompatMode)
            type = SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST;
        else
            type = SDP_SERVICE_SEARCH_REQUEST;

        SVSUTIL_ASSERT( (fLocal && !cid) || (!fLocal && cid));

        // If no restrictions were set, use ServiceClassId to fill out a default 
        // restriction blob.
        SdpQueryUuid UUID[2];
        SdpQueryUuid *pUUIDs;

        SdpAttributeRange Range;
        SdpAttributeRange *pRange;
        int numAttributes;

        if ((type == SDP_SERVICE_SEARCH_REQUEST) || (type == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST)) {
            if (pResBlob) {
                pUUIDs = pResBlob->uuids;
            }
            else {
                memcpy(&UUID[0].u.uuid128,&pBthNsHandle->ServiceClassId,sizeof(GUID));
                UUID[0].uuidType = SDP_ST_UUID128;
                // This is so SdpInterface::VerifyServiceSearch knows where to stop
                memset(&UUID[1],0,sizeof(SdpQueryUuid));
                pUUIDs = UUID;
            }
        }

        if (type == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) {
            if (pResBlob) {
                pRange = pResBlob->pRange;
                numAttributes = pResBlob->numRange;
            }
            else {
                SVSUTIL_ASSERT(pBthNsHandle->fXPCompatMode);
                Range.minAttribute = 0;
                Range.maxAttribute = 0xffff;
                pRange = &Range;
                numAttributes = 1;
            }
        }

        switch (type) {
            case SDP_SERVICE_SEARCH_REQUEST:  {
                if (ERROR_SUCCESS != (iRes = ServiceSearchBlocking(pCall,cid, pUUIDs, MAX_SEARCH_HANDLES)))
                    iRes = MapErrorCodeToWinsockErr(iRes);
            }
            break;

            case SDP_SERVICE_ATTRIBUTE_REQUEST: {
                numAttributes = pResBlob->numRange;
                if (ERROR_SUCCESS != (iRes = AttributeSearchBlocking(pCall, cid, pResBlob->serviceHandle,pResBlob->pRange,numAttributes)))
                    iRes = MapErrorCodeToWinsockErr(iRes);
            }
            break;

            case SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST: {
                if (ERROR_SUCCESS != (iRes = ServiceAttributeSearchBlocking(pCall, cid, pUUIDs,pRange,numAttributes)))
                    iRes = MapErrorCodeToWinsockErr(iRes);
            }
            break;

            default:
                SVSUTIL_ASSERT(0);
        }
        SVSUTIL_ASSERT(gpBthNS->IsLocked());

        if (iRes != ERROR_SUCCESS)
            goto done;

        if (NULL == (pCall = FindCall(pCall)) || pCall->iResult != ERROR_SUCCESS || 
            NULL == (pBthNsHandle = FindBthNsHandle(pBthNsHandle)))  {
            iRes = MapErrorCodeToWinsockErr((pCall && pBthNsHandle) ? pCall->iResult : ERROR_OPERATION_ABORTED);
            goto done;
        }

        pBthNsHandle->pClientBuf = pCall->pClientBuf;
        pBthNsHandle->cClientBuf = pCall->cClientBuf;
        pBthNsHandle->fNetSearchComplete  = TRUE;
        pCall->pClientBuf      = NULL;
        pCall->cClientBuf      = 0;
    }

    if (!pBthNsHandle->cClientBuf) {
        SVSUTIL_ASSERT(!pBthNsHandle->pClientBuf);
        iRes = WSA_E_NO_MORE;
        goto done;
    }
    cbRequired = pBthNsHandle->cClientBuf + sizeof(WSAQUERYSET) + sizeof(BLOB);

    if (*lpdwBufferLength < cbRequired || !pResults) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext: WSA_NOT_ENOUGH_MEMORY.  *lpdwBufferLength=%d,cbRequired=%d\r\n",*lpdwBufferLength,cbRequired));
        *lpdwBufferLength = cbRequired;
        iRes =  WSA_NOT_ENOUGH_MEMORY;
        goto done;
    }

    *lpdwBufferLength = cbRequired;
    pBthNsHandle->fNoMoreData = TRUE;

#if defined (UNDER_CE)
    __try {
        memset(pResults,0,sizeof(WSAQUERYSETA));
        pResults->dwSize      = sizeof(WSAQUERYSETA);
        pResults->dwNameSpace = NS_BTH;
        pResults->lpBlob = (LPBLOB) ((char*)pResults + sizeof(WSAQUERYSET));
        pResults->lpBlob->cbSize    = pBthNsHandle->cClientBuf;
        pResults->lpBlob->pBlobData = (PBYTE) ((char*)pResults->lpBlob + sizeof(BLOB));

        if (pBthNsHandle->pClientBuf)
            memcpy(pResults->lpBlob->pBlobData,pBthNsHandle->pClientBuf,pBthNsHandle->cClientBuf);
    } __except (1) {
        iRes = ERROR_INVALID_USER_BUFFER;
    }
#endif

    iRes = ERROR_SUCCESS;
done:

#if defined (DEBUG) || defined (_DEBUG)
    if (iRes != ERROR_SUCCESS)
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext:: fails, err=0x%08x\r\n",iRes));
#endif

    if (fDisconnect) {
        SDPDisconnect(pCall,cid);
        SVSUTIL_ASSERT(gpBthNS->IsLocked());
    }

    if (pBthNsHandle)
        pBthNsHandle->pCall = NULL;

    if (pCall)
        DeleteCall(pCall);

    if (iRes != ERROR_SUCCESS) {
        SetLastError(iRes);
        iRes = SOCKET_ERROR;
    }

    gpBthNS->Unlock();
    return iRes;
}

int BthNsLookupServiceEnd(HANDLE hLookup)  {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"BthNsLookupServiceEnd entered\r\n"));

    if (!gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext:: ERROR_SERVICE_DOES_NOT_EXIST\r\n"));
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }

    gpBthNS->Lock();

    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR,L"BthNsLookupServiceNext:: ERROR_SERVICE_NOT_ACTIVE\r\n"));
        gpBthNS->Unlock();
        SetLastError(WSASERVICE_NOT_FOUND);
        return SOCKET_ERROR;
    }

    int fCancelInquiry = ((DWORD)hLookup == BTHNS_ABORT_CURRENT_INQUIRY);

    if (fCancelInquiry) {
        gpBthNS->Unlock();
        BthCancelInquiry ();

        return ERROR_SUCCESS;
    }

    BthNsHandle *pBthNsHandle = FindBthNsHandle((BthNsHandle *) hLookup);

    if (pBthNsHandle) {
        if (pBthNsHandle->fInquiryStatus == INQUIRY_INPROGRESS)
            fCancelInquiry = TRUE;

        // If the call is currently executing then terminate it.
        if (pBthNsHandle->pCall)
            SetEvent(pBthNsHandle->pCall->hEvent);

        DeleteBthNsHandle(pBthNsHandle);
    }

    gpBthNS->Unlock();

    if (!pBthNsHandle) {
        SetLastError(WSA_INVALID_HANDLE);
        return SOCKET_ERROR;
    }

    if (fCancelInquiry)
        BthCancelInquiry ();

    return ERROR_SUCCESS;
}

// BthNsHandleback functions
static int bthns_Connect_Out(void *pBthNsHandleContext, unsigned long status, unsigned short cid)  {
    if (!gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_Connect_Out:: ERROR_SERVICE_DOES_NOT_EXIST\r\n"));
        return ERROR_SERVICE_DOES_NOT_EXIST;
    }
    gpBthNS->Lock();

    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_Connect_Out:: ERROR_SERVICE_NOT_ACTIVE\r\n"));
        gpBthNS->Unlock();
        return ERROR_SERVICE_NOT_ACTIVE;
    }
    
    Call *pCall = FindCall ((Call*) pBthNsHandleContext);
    if (pCall) {
        pCall->iResult = status;
        pCall->cid     = cid;
        SetEvent(pCall->hEvent);
    }

    gpBthNS->Unlock();
    return ERROR_SUCCESS;
}

static int bthns_Disconnect_Out(void *pBthNsHandleContext, unsigned long status) {
    if (!gpBthNS) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_Connect_Out:: ERROR_SERVICE_DOES_NOT_EXIST\r\n"));
        return ERROR_SERVICE_DOES_NOT_EXIST;
    }
    gpBthNS->Lock();

    if (gpBthNS->eStage != Running) {
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_Connect_Out:: ERROR_SERVICE_NOT_ACTIVE\r\n"));
        gpBthNS->Unlock();
        return ERROR_SERVICE_NOT_ACTIVE;
    }
    
    Call *pCall = FindCall ((Call*) pBthNsHandleContext);
    if (pCall)
        SetEvent(pCall->hEvent);

    gpBthNS->Unlock();
    return ERROR_SUCCESS;
}

static int bthns_ServiceSearch_Out(void *pBthNsHandleContext, unsigned long status, unsigned short cReturnedHandles, unsigned long *pHandles)  {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"bthns_ServiceSearch_Out pContext=0x%08x, status=%d, cRetHandles=%d, pHandles=0x%08x\r\n",
                                  pBthNsHandleContext, status, cReturnedHandles, pHandles));

    int iRet = ERROR_INTERNAL_ERROR;
    BOOL fFreeLock = FALSE;
    Call *pCall;

#if defined (DEBUG) || defined (_DEBUG)
    if (pHandles && cReturnedHandles)  {
        int i;

        for (i = 0; i < cReturnedHandles; i++)
            IFDBG(DebugOut(DEBUG_SDP_PACKETS,L"SDP Handle[%d] = 0x%08x\r\n",i,pHandles[i]));
    }
#endif

    if (!gpBthNS) {
        iRet = ERROR_SERVICE_DOES_NOT_EXIST;
        goto done;
    }

    gpBthNS->Lock();
    fFreeLock = TRUE;

    if (gpBthNS->eStage != Running) {
        iRet = ERROR_SERVICE_NOT_ACTIVE;
        goto done;
    }

    if (NULL == (pCall = FindCall ((Call*) pBthNsHandleContext)))  {
        iRet = ERROR_INVALID_PARAMETER;
        goto done;
    }
    pCall->iResult    = status;
    pCall->cClientBuf = cReturnedHandles * sizeof (DWORD);
    pCall->pClientBuf = (unsigned char*) pHandles;
    SetEvent(pCall->hEvent);
    iRet = ERROR_SUCCESS;
done:

#if defined (DEBUG) || defined (_DEBUG)
    if (iRet != ERROR_SUCCESS)
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_ServiceSearch_Out: 0x%08x\r\n",iRet));
#endif

    if (iRet != ERROR_SUCCESS && pHandles) {
        ExFreePool(pHandles);
    }
    if (fFreeLock)
        gpBthNS->Unlock();
    
    return iRet;
}

static int bthns_AttributeSearch_Out(void *pBthNsHandleContext, unsigned long status, unsigned char *pOutBuf, unsigned long cOutBuf) {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"bthns_AttributeSearch_Out pContext=0x%08x, status=%d, pOutBuf=0x%08x, cOutBuf=0x%08x\r\n",
                                  pBthNsHandleContext, status, pOutBuf, cOutBuf));


    int iRet = ERROR_INTERNAL_ERROR;
    BOOL fFreeLock = FALSE;
    Call *pCall;

    if (!gpBthNS) {
        iRet = ERROR_SERVICE_DOES_NOT_EXIST;
        goto done;
    }
    gpBthNS->Lock();
    fFreeLock = TRUE;

    if (gpBthNS->eStage != Running) {
        iRet = ERROR_SERVICE_NOT_ACTIVE;
        goto done;
    }

    if (NULL == (pCall = FindCall((Call*) pBthNsHandleContext)))  {
        iRet = ERROR_INVALID_PARAMETER;
        goto done;
    }
    pCall->iResult    = status;
    pCall->pClientBuf = pOutBuf;
    pCall->cClientBuf = cOutBuf;
    SetEvent(pCall->hEvent);
    iRet = ERROR_SUCCESS;
done:

#if defined (DEBUG) || defined (_DEBUG)
    if (iRet != ERROR_SUCCESS)
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_AttributeSearch_Out: 0x%08x\r\n",iRet));
#endif

    if (iRet != ERROR_SUCCESS && pOutBuf) {
        ExFreePool(pOutBuf);
    }
    if (fFreeLock)
        gpBthNS->Unlock();    
    
    return iRet;
}

static int bthns_ServiceAttributeSearch_Out(void *pBthNsHandleContext, unsigned long status, unsigned char *pOutBuf, unsigned long cOutBuf) {
    IFDBG(DebugOut(DEBUG_SDP_TRACE,L"bthns_ServiceAttributeSearch_Out pContext=0x%08x, status=%d, pOutBuf=0x%08x, cOutBuf=0x%08x\r\n",
                                  pBthNsHandleContext, status, pOutBuf, cOutBuf));

    int iRet = ERROR_INTERNAL_ERROR;
    BOOL fFreeLock = FALSE;
    Call *pCall;

    if (!gpBthNS) {
        iRet = ERROR_SERVICE_DOES_NOT_EXIST;
        goto done;
    }
    gpBthNS->Lock();
    fFreeLock = TRUE;

    if (gpBthNS->eStage != Running) {
        iRet = ERROR_SERVICE_NOT_ACTIVE;
        goto done;
    }
    if (NULL == (pCall = FindCall((Call*) pBthNsHandleContext)))  {
        iRet = ERROR_INVALID_PARAMETER;
        goto done;
    }
    pCall->iResult    = status;
    pCall->pClientBuf = pOutBuf;
    pCall->cClientBuf = cOutBuf;
    SetEvent(pCall->hEvent);
    iRet = ERROR_SUCCESS;
done:

#if defined (DEBUG) || defined (_DEBUG)
    if (iRet != ERROR_SUCCESS)
        IFDBG(DebugOut(DEBUG_ERROR,L"bthns_AttributeSearch_Out: 0x%08x\r\n",iRet));
#endif

    if (iRet != ERROR_SUCCESS && pOutBuf) {
        ExFreePool(pOutBuf);
    }

    if (fFreeLock)
        gpBthNS->Unlock();
    
    return iRet;
}

#if defined (UNDER_CE)

BOOL       g_fNameSpaceInstalled = FALSE;
HINSTANCE  g_hWS2Lib             = NULL;
typedef int (WINAPI *PFN_WSCINSTALLNAMESPACE) (LPWSTR lpszIdentifier, LPWSTR lpszPathName, DWORD dwNameSpace, DWORD dwVersion, LPGUID lpProviderId);
typedef int (WINAPI *PFN_WSCUNINSTALLNAMESPACE) (LPGUID lpProviderId);


// {371AE22A-2144-4b26-94D8-90C47DD240D0}
static const GUID NsId = {0x371ae22a, 0x2144, 0x4b26, {0x94, 0xd8, 0x90, 0xc4, 0x7d, 0xd2, 0x40, 0xd0}};

// to install in the 1st place, WCSInstallNameSpace().  Only if ws2.dll is installed.
WCHAR szProviderName[] = L"Windows CE Bluetooth Name Space Provider";
WCHAR szProviderPath[] = L"btdrt.dll";

// Installs BTHNS as a Winsock 2 name space provider if Winsock 2 is installed.
void sdp_LoadWSANameSpaceProvider(void) {
    if (g_fNameSpaceInstalled)
        return;

    // wether we suceeed or fail, never run through this code again.
    g_fNameSpaceInstalled = TRUE;

    g_hWS2Lib = LoadLibrary(L"ws2.dll");
    if (!g_hWS2Lib)
        return;

    PFN_WSCINSTALLNAMESPACE pfnInstall = (PFN_WSCINSTALLNAMESPACE) GetProcAddress(g_hWS2Lib,L"WSCInstallNameSpace");
    if (!pfnInstall) {
        SVSUTIL_ASSERT(0);
        return;
    }

    pfnInstall(szProviderName,szProviderPath,NS_BTH,0,(GUID*)&NsId);
}

void sdp_UnLoadWSANameSpaceProvider(void) {
    if (!g_hWS2Lib)
        return;

    PFN_WSCUNINSTALLNAMESPACE pfnUnInstall = (PFN_WSCUNINSTALLNAMESPACE) GetProcAddress(g_hWS2Lib,L"WSCUninstallNameSpace");
    if (!pfnUnInstall) {
        SVSUTIL_ASSERT(0);
        return;
    }

    pfnUnInstall((GUID*)&NsId);
    FreeLibrary(g_hWS2Lib);

    g_hWS2Lib                = NULL;
    g_fNameSpaceInstalled    = FALSE;
}
#endif // UNDER_CE
