如何使用 WSASetService 在 Windows 设备上使用 WSAQUERYSET 结构来宣传蓝牙服务

How to use WSASetService to advertise bluetooth service using WSAQUERYSET structure on Windows devices

我从 https://docs.microsoft.com/en-us/windows/win32/bluetooth/bluetooth-and-wsasetservice 了解到有两种方法可以将服务提交到本地 SDP 服务器:

对我来说,第一个是首选,但我现在能找到的所有代码示例都使用第二个。我想大概是在WSAQUERYSET成员中设置服务的地址、端口、服务uuid​​等信息:

    // Service Information
    int port = 4;
    BTH_ADDR address = xxx;
    GUID serviceUuid = {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx};
    char* serviceName = "xxx";
    //....

    // Putting service information into WSAQUERYSET
    WSAQUERYSET Service;
    memset( &Service, 0, sizeof( Service ) );
    Service.dwSize      = sizeof( Service );
    Service.dwNameSpace = NS_BTH;
    //Service.xxx         = xxx
    //...
    
    if ( WSASetService( &Service, RNRSERVICE_REGISTER, 0 ) == SOCKET_ERROR ) {
        return WSAGetLastError();
    }
    else {
        return 0;
    }

目前,我正在猜测如何设置这些属性,但不知何故我无法成功宣传可以检测到的服务。我真的希望有一些可行的代码示例。

首先你不需要硬编码port = 4;,而是使用bind获取动态空闲端口,使用getsockname获取端口号。例如(没有错误检查)

SOCKADDR_BTH asi = { AF_BTH, 0, {}, BT_PORT_ANY };
SOCKET s = WSASocket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM, 
    0, 0, WSA_FLAG_NO_HANDLE_INHERIT|WSA_FLAG_OVERLAPPED);
bind(s, (PSOCKADDR)&asi, sizeof(asi));
int len = sizeof(asi);
getsockname(s, (PSOCKADDR)&asi, &len);

然后您需要调用 WSASetService,如 Bluetooth and WSAQUERYSET for Set Service 中所述。这里描述了 WSAQUERYSET 中使用的所有参数。只有一个例外 - lpszServiceInstanceName 描述为

Optional, but recommended.

但如果不使用它 - 我们得到 提供了无效参数。所以这是强制参数。

ULONG BthRegisterService(LPCGUID lpServiceClassId, PCWSTR ServiceName, PSOCKADDR_BTH asi)
{
    CSADDR_INFO csa = { 
        { (PSOCKADDR)asi, sizeof(SOCKADDR_BTH) }, {}, SOCK_STREAM, BTHPROTO_RFCOMM 
    };

    WSAQUERYSET wqs = { sizeof(wqs) };
    wqs.lpszServiceInstanceName = const_cast<PWSTR>(ServiceName);
    wqs.lpServiceClassId = const_cast<PGUID>(lpServiceClassId);
    wqs.dwNameSpace = NS_BTH;
    wqs.dwNumberOfCsAddrs = 1;
    wqs.lpcsaBuffer = &csa;

    return WSASetService(&wqs, RNRSERVICE_REGISTER, 0) ? WSAGetLastError() : NOERROR;
}

这种方式的缺点 - 我们无法取回 HANDLE 我们注册的记录,用于延迟调用 WSASetServiceRNRSERVICE_DELETE - 为此需要使用 BLOB指向 BTH_SET_SERVICE。为此,我们需要通过自己的原始 SDP 记录流进行格式化。

所以调用BthRegisterService我们可以这样:

struct __declspec(uuid("00112233-4455-6677-8899-aabbccddeeff")) MyServiceClass;
BthRegisterService(&__uuidof(MyServiceClass), L"*", &asi);

但是,如果我们可以格式化SDP记录,我们也可以直接使用IOCTL_BTH_SDP_SUBMIT_RECORD for register and later IOCTL_BTH_SDP_REMOVE_RECORD注销

打开蓝牙设备需要使用 CM_Get_Device_Interface_ListWGUID_BTHPORT_DEVICE_INTERFACE

由 NspSetService 格式化的 SDP 记录看起来像

UCHAR SdpRecordTmplt[] = {
    // [//////////////////////////////////////////////////////////////////////////////////////////////////////////
    0x35, 0x33,                                                                                                 //
    //
    // UINT16:SDP_ATTRIB_CLASS_ID_LIST                                                                          //
    0x09, 0x00, 0x01,                                                                                           //
    //      [/////////////////////////////////////////////////////////////////////////////////////////////////  //
    0x35, 0x11,                                                                                             //  //
    // UUID128:{guid}                                                                                       //  //
    0x1c, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,   //  //
    //      ]/////////////////////////////////////////////////////////////////////////////////////////////////  //
    //
    // UINT16:SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST                                                               //
    0x09, 0x00, 0x04,                                                                                           //
    //      [/////////////////////////////////                                                                  //
    0x35, 0x0f,                             //                                                                  //
    // (L2CAP, PSM=PSM_RFCOMM)              //                                                                  //
    //          [/////////////////////////  //                                                                  //
    0x35, 0x06,                         //  //                                                                  //
    // UUID16:L2CAP_PROTOCOL_UUID16     //  //                                                                  //
    0x19, 0x01, 0x00,                   //  //                                                                  //
    // UINT16:PSM_RFCOMM                //  //                                                                  //
    0x09, 0x00, 0x03,                   //  //                                                                  //
    //          ]/////////////////////////  //                                                                  //
    // (RFCOMM, CN=Port)                    //                                                                  //
    //          [/////////////////////////  //                                                                  //
    0x35, 0x05,                         //  //                                                                  //
    // UUID16:RFCOMM_PROTOCOL_UUID16    //  //                                                                  //
    0x19, 0x00, 0x03,                   //  //                                                                  //
    // UINT8:CN                         //  //                                                                  //
    0x08, 0x**,                         //  //                                                                  //
    //          ]/////////////////////////  //                                                                  //
    //      ]/////////////////////////////////                                                                  //
    //
    // UINT16:SDP_ATTRIB_SERVICE_NAME                                                                           //
    0x09, 0x01, 0x00,                                                                                           //
    // STR:4 "ABCD"                                                                                             //
    0x25, 0x04, 0x41, 0x42, 0x43, 0x44                                                                          //
    // ]//////////////////////////////////////////////////////////////////////////////////////////////////////////
};

这里只有UUID128(ServiceClassId),rfcomm端口号和名字都改了


即使我们自己格式化原始 SDP 记录 - windows 添加 2 个属性:

(UINT16:ServiceRecordHandle) (UINT32:xxxxx)

(UINT16:BrowseGroupList) [ (UUID16:PublicBrowseRoot) ]

例如,如果我们传递下一个 sdp 记录:

[ // 30
    (UINT16:ServiceClassIDList)
    [ // 11
        (UUID128:00112233-4455-6677-8899-aabbccddeeff)
    ]
    (UINT16:ProtocolDescriptorList)
    [ // C
        [ // 3
            (UUID16:L2CAP_PROTOCOL_UUID16)
        ]
        [ // 5
            (UUID16:PSM_RFCOMM)
            (UINT8:CN=Port)
        ]
    ]
    (UINT16:ServiceName)
    ("ABCD")
]

windows 将其转换(扩展)为

[ // 40
    (UINT16:ServiceRecordHandle) (UINT32:10001)
    (UINT16:ServiceClassIDList)
    [ // 11
        (UUID128:00112233-4455-6677-8899-aabbccddeeff)
    ]
    (UINT16:ProtocolDescriptorList)
    [ // C
        [ // 3
            (UUID16:L2CAP_PROTOCOL_UUID16)
        ]
        [ // 5
            (UUID16:PSM_RFCOMM)
            (UINT8:CN=Port)
        ]
    ]
    (UINT16:BrowseGroupList)
    [ // 3
        (UUID16:PublicBrowseRoot)
    ]
    (UINT16:ServiceName)
    ("ABCD")
]

或原始字节 - 记录:

UCHAR sdp[] = {
     0x35 ,0x30 ,0x09 ,0x00 ,0x01 ,0x35 ,0x11 ,0x1c
    ,0x00 ,0x11 ,0x22 ,0x33 ,0x44 ,0x55 ,0x66 ,0x77
    ,0x88 ,0x99 ,0xaa ,0xbb ,0xcc ,0xdd ,0xee ,0xff
    ,0x09 ,0x00 ,0x04 ,0x35 ,0x0c ,0x35 ,0x03 ,0x19
    ,0x01 ,0x00 ,0x35 ,0x05 ,0x19 ,0x00 ,0x03 ,0x08
    ,0x04 ,0x09 ,0x01 ,0x00 ,0x25 ,0x04 ,0x41 ,0x42
    ,0x43 ,0x44
};

转换为

UCHAR sdp_final[] = {
     0x35 ,0x40 ,0x09 ,0x00 ,0x00 ,0x0a ,0x00 ,0x01
    ,0x00 ,0x01 ,0x09 ,0x00 ,0x01 ,0x35 ,0x11 ,0x1c
    ,0x00 ,0x11 ,0x22 ,0x33 ,0x44 ,0x55 ,0x66 ,0x77
    ,0x88 ,0x99 ,0xaa ,0xbb ,0xcc ,0xdd ,0xee ,0xff
    ,0x09 ,0x00 ,0x04 ,0x35 ,0x0c ,0x35 ,0x03 ,0x19
    ,0x01 ,0x00 ,0x35 ,0x05 ,0x19 ,0x00 ,0x03 ,0x08
    ,0x04 ,0x09 ,0x00 ,0x05 ,0x35 ,0x03 ,0x19 ,0x10
    ,0x02 ,0x09 ,0x01 ,0x00 ,0x25 ,0x04 ,0x41 ,0x42
    ,0x43 ,0x44
};