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



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

  • 应用程序可以让系统公布一个简单的蓝牙SDP服务记录,该记录由WSAQUERYSET结构中的标准成员构建。

  • 应用程序可以让系统通过在WSAQUERYSET结构的lpBlob成员中传递BTH_SET_SERVICE结构来通告他们自己的蓝牙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,如蓝牙和WSAQUERYSET中所述,用于设置服务。这里描述了WSAQUERYSET中使用的所有参数。只有一个例外-lpszServiceInstanceName被描述为

可选,但推荐使用

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

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,对于用RNRSERVICE_DELETE晚调用WSASetService——为此需要使用指向BTH_SET_SERVICEBLOB。为此,我们需要通过自原始SDP记录流进行格式化。

所以我们可以用whis方式调用BthRegisterService

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

然而,如果我们可以格式化SDP记录,我们也可以直接使用IOCTL_BTH_SDP_SUBMIT_RECORD进行注册,然后使用IOCTL_BTH_SDP_REMOVE_RECORD来注销

对于开放式蓝牙设备,需要使用CM_Get_Device_Interface_ListWGUID_BTHPORT_DEVICE_INTERFACE

NspService格式化的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也会为其添加两个属性:

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

最新更新