1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <QDebug>
32#include <QLoggingCategory>
33#include <QVariant>
34#include <QList>
35
36#include <qbluetoothaddress.h>
37#include <qbluetoothdevicediscoveryagent.h>
38#include <qbluetoothservicediscoveryagent.h>
39#include <qbluetoothlocaldevice.h>
40#include <qbluetoothserver.h>
41#include <qbluetoothserviceinfo.h>
42
43QT_USE_NAMESPACE
44
45// Maximum time to for bluetooth device scan
46const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms
47
48class tst_QBluetoothServiceDiscoveryAgent : public QObject
49{
50 Q_OBJECT
51
52public:
53 tst_QBluetoothServiceDiscoveryAgent();
54 ~tst_QBluetoothServiceDiscoveryAgent();
55
56public slots:
57 void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info);
58 void serviceDiscoveryDebug(const QBluetoothServiceInfo &info);
59 void serviceError(const QBluetoothServiceDiscoveryAgent::Error err);
60
61private slots:
62 void initTestCase();
63
64 void tst_invalidBtAddress();
65 void tst_serviceDiscovery_data();
66 void tst_serviceDiscovery();
67 void tst_serviceDiscoveryStop();
68 void tst_serviceDiscoveryAdapters();
69
70private:
71 QList<QBluetoothDeviceInfo> devices;
72 bool localDeviceAvailable;
73};
74
75tst_QBluetoothServiceDiscoveryAgent::tst_QBluetoothServiceDiscoveryAgent()
76{
77 QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
78
79 // start Bluetooth if not started
80#ifndef Q_OS_OSX
81 QBluetoothLocalDevice *device = new QBluetoothLocalDevice();
82 localDeviceAvailable = device->isValid();
83 if (localDeviceAvailable) {
84 device->powerOn();
85 // wait for the device to switch bluetooth mode.
86 QTest::qWait(ms: 1000);
87 }
88 delete device;
89#else
90 QBluetoothLocalDevice device;
91 localDeviceAvailable = QBluetoothLocalDevice().hostMode() != QBluetoothLocalDevice::HostPoweredOff;
92#endif
93
94 qRegisterMetaType<QBluetoothDeviceInfo>();
95 qRegisterMetaType<QList<QBluetoothUuid> >();
96 qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
97 qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
98
99}
100
101tst_QBluetoothServiceDiscoveryAgent::~tst_QBluetoothServiceDiscoveryAgent()
102{
103}
104
105void tst_QBluetoothServiceDiscoveryAgent::deviceDiscoveryDebug(const QBluetoothDeviceInfo &info)
106{
107 qDebug() << "Discovered device:" << info.address().toString() << info.name();
108}
109
110
111void tst_QBluetoothServiceDiscoveryAgent::serviceError(const QBluetoothServiceDiscoveryAgent::Error err)
112{
113 qDebug() << "Service discovery error" << err;
114}
115
116void tst_QBluetoothServiceDiscoveryAgent::initTestCase()
117{
118 if (localDeviceAvailable) {
119 QBluetoothDeviceDiscoveryAgent discoveryAgent;
120
121 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
122 QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
123 QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
124 // connect(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
125 // this, SLOT(deviceDiscoveryDebug(QBluetoothDeviceInfo)));
126
127 discoveryAgent.start();
128
129 // Wait for up to MaxScanTime for the scan to finish
130 int scanTime = MaxScanTime;
131 while (finishedSpy.count() == 0 && scanTime > 0) {
132 QTest::qWait(ms: 1000);
133 scanTime -= 1000;
134 }
135 // qDebug() << "Scan time left:" << scanTime;
136
137 // Expect finished signal with no error
138 QVERIFY(finishedSpy.count() == 1);
139 QVERIFY(errorSpy.isEmpty());
140
141 devices = discoveryAgent.discoveredDevices();
142 }
143}
144
145void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscoveryStop()
146{
147 if (!localDeviceAvailable)
148 QSKIP("This test requires Bluetooth adapter in powered ON state");
149
150 QBluetoothServiceDiscoveryAgent discoveryAgent;
151 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
152 QSignalSpy canceledSpy(&discoveryAgent, SIGNAL(canceled()));
153
154 // Verify we get the correct signals on start-stop
155 discoveryAgent.start(mode: QBluetoothServiceDiscoveryAgent::FullDiscovery);
156 QVERIFY(discoveryAgent.isActive());
157 discoveryAgent.stop();
158 QTRY_COMPARE(canceledSpy.count(), 1);
159 QVERIFY(!discoveryAgent.isActive());
160 // Wait a bit to see that there are no latent signals
161 QTest::qWait(ms: 200);
162 QCOMPARE(canceledSpy.count(), 1);
163 QCOMPARE(finishedSpy.count(), 0);
164}
165
166
167void tst_QBluetoothServiceDiscoveryAgent::tst_invalidBtAddress()
168{
169#ifdef Q_OS_OSX
170 if (!localDeviceAvailable)
171 QSKIP("On OS X this test requires Bluetooth adapter in powered ON state");
172#endif
173 QBluetoothServiceDiscoveryAgent *discoveryAgent = new QBluetoothServiceDiscoveryAgent(QBluetoothAddress("11:11:11:11:11:11"));
174
175 QCOMPARE(discoveryAgent->error(), QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError);
176 discoveryAgent->start();
177 QCOMPARE(discoveryAgent->isActive(), false);
178 delete discoveryAgent;
179
180 discoveryAgent = new QBluetoothServiceDiscoveryAgent(QBluetoothAddress());
181 QCOMPARE(discoveryAgent->error(), QBluetoothServiceDiscoveryAgent::NoError);
182 if (QBluetoothLocalDevice::allDevices().count() > 0) {
183 discoveryAgent->start();
184 QCOMPARE(discoveryAgent->isActive(), true);
185 }
186 delete discoveryAgent;
187}
188
189void tst_QBluetoothServiceDiscoveryAgent::serviceDiscoveryDebug(const QBluetoothServiceInfo &info)
190{
191 qDebug() << "Discovered service on"
192 << info.device().name() << info.device().address().toString();
193 qDebug() << "\tService name:" << info.serviceName() << "cached" << info.device().isCached();
194 qDebug() << "\tDescription:"
195 << info.attribute(attributeId: QBluetoothServiceInfo::ServiceDescription).toString();
196 qDebug() << "\tProvider:" << info.attribute(attributeId: QBluetoothServiceInfo::ServiceProvider).toString();
197 qDebug() << "\tL2CAP protocol service multiplexer:" << info.protocolServiceMultiplexer();
198 qDebug() << "\tRFCOMM server channel:" << info.serverChannel();
199}
200
201static void dumpAttributeVariant(const QVariant &var, const QString indent)
202{
203 if (!var.isValid()) {
204 qDebug(msg: "%sEmpty", indent.toLocal8Bit().constData());
205 return;
206 }
207
208 if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
209 qDebug(msg: "%sSequence", indent.toLocal8Bit().constData());
210 const QBluetoothServiceInfo::Sequence *sequence = static_cast<const QBluetoothServiceInfo::Sequence *>(var.data());
211 for (const QVariant &v : *sequence)
212 dumpAttributeVariant(var: v, indent: indent + '\t');
213 } else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
214 qDebug(msg: "%sAlternative", indent.toLocal8Bit().constData());
215 const QBluetoothServiceInfo::Alternative *alternative = static_cast<const QBluetoothServiceInfo::Alternative *>(var.data());
216 for (const QVariant &v : *alternative)
217 dumpAttributeVariant(var: v, indent: indent + '\t');
218 } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
219 QBluetoothUuid uuid = var.value<QBluetoothUuid>();
220 switch (uuid.minimumSize()) {
221 case 0:
222 qDebug(msg: "%suuid NULL", indent.toLocal8Bit().constData());
223 break;
224 case 2:
225 qDebug(msg: "%suuid %04x", indent.toLocal8Bit().constData(), uuid.toUInt16());
226 break;
227 case 4:
228 qDebug(msg: "%suuid %08x", indent.toLocal8Bit().constData(), uuid.toUInt32());
229 break;
230 case 16: {
231 qDebug(msg: "%suuid %s", indent.toLocal8Bit().constData(), QByteArray(reinterpret_cast<const char *>(uuid.toUInt128().data), 16).toHex().constData());
232 break;
233 }
234 default:
235 qDebug(msg: "%suuid ???", indent.toLocal8Bit().constData());
236 }
237 } else {
238 switch (var.userType()) {
239 case QVariant::UInt:
240 qDebug(msg: "%suint %u", indent.toLocal8Bit().constData(), var.toUInt());
241 break;
242 case QVariant::Int:
243 qDebug(msg: "%sint %d", indent.toLocal8Bit().constData(), var.toInt());
244 break;
245 case QVariant::String:
246 qDebug(msg: "%sstring %s", indent.toLocal8Bit().constData(), var.toString().toLocal8Bit().constData());
247 break;
248 case QVariant::Bool:
249 qDebug(msg: "%sbool %d", indent.toLocal8Bit().constData(), var.toBool());
250 break;
251 case QVariant::Url:
252 qDebug(msg: "%surl %s", indent.toLocal8Bit().constData(), var.toUrl().toString().toLocal8Bit().constData());
253 break;
254 default:
255 qDebug(msg: "%sunknown", indent.toLocal8Bit().constData());
256 }
257 }
258}
259
260static inline void dumpServiceInfoAttributes(const QBluetoothServiceInfo &info)
261{
262 const QList<quint16> attributes = info.attributes();
263 for (quint16 id : attributes) {
264 dumpAttributeVariant(var: info.attribute(attributeId: id), indent: QString("\t"));
265 }
266}
267
268
269void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery_data()
270{
271 if (devices.isEmpty())
272 QSKIP("This test requires an in-range bluetooth device");
273
274 QTest::addColumn<QBluetoothDeviceInfo>(name: "deviceInfo");
275 QTest::addColumn<QList<QBluetoothUuid> >(name: "uuidFilter");
276 QTest::addColumn<QBluetoothServiceDiscoveryAgent::Error>(name: "serviceDiscoveryError");
277
278 // Only need to test the first 5 live devices
279 int max = 5;
280 for (const QBluetoothDeviceInfo &info : qAsConst(t&: devices)) {
281 if (info.isCached())
282 continue;
283 QTest::newRow(dataTag: "default filter") << info << QList<QBluetoothUuid>()
284 << QBluetoothServiceDiscoveryAgent::NoError;
285 if (!--max)
286 break;
287 //QTest::newRow("public browse group") << info << (QList<QBluetoothUuid>() << QBluetoothUuid::PublicBrowseGroup);
288 //QTest::newRow("l2cap") << info << (QList<QBluetoothUuid>() << QBluetoothUuid::L2cap);
289 }
290 QTest::newRow(dataTag: "all devices") << QBluetoothDeviceInfo() << QList<QBluetoothUuid>()
291 << QBluetoothServiceDiscoveryAgent::NoError;
292}
293
294void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscoveryAdapters()
295{
296 QBluetoothLocalDevice localDevice;
297 int numberOfAdapters = (localDevice.allDevices()).size();
298 if (numberOfAdapters>1) {
299 if (devices.isEmpty())
300 QSKIP("This test requires an in-range bluetooth device");
301
302 QList<QBluetoothAddress> addresses;
303
304 for (int i=0; i<numberOfAdapters; i++) {
305 addresses.append(t: ((QBluetoothHostInfo)localDevice.allDevices().at(i)).address());
306 }
307 QBluetoothServer server(QBluetoothServiceInfo::RfcommProtocol);
308 QBluetoothUuid uuid(QBluetoothUuid::Ftp);
309 server.listen(address: addresses[0]);
310 QBluetoothServiceInfo serviceInfo;
311 serviceInfo.setAttribute(attributeId: QBluetoothServiceInfo::ServiceName, value: "serviceName");
312 QBluetoothServiceInfo::Sequence publicBrowse;
313 publicBrowse << QVariant::fromValue(value: QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
314 serviceInfo.setAttribute(attributeId: QBluetoothServiceInfo::BrowseGroupList, value: publicBrowse);
315
316 QBluetoothServiceInfo::Sequence profileSequence;
317 QBluetoothServiceInfo::Sequence classId;
318 classId << QVariant::fromValue(value: QBluetoothUuid(QBluetoothUuid::SerialPort));
319 classId << QVariant::fromValue(value: quint16(0x100));
320 profileSequence.append(t: QVariant::fromValue(value: classId));
321 serviceInfo.setAttribute(attributeId: QBluetoothServiceInfo::BluetoothProfileDescriptorList,
322 value: profileSequence);
323
324 serviceInfo.setServiceUuid(uuid);
325
326 QBluetoothServiceInfo::Sequence protocolDescriptorList;
327 QBluetoothServiceInfo::Sequence protocol;
328 protocol << QVariant::fromValue(value: QBluetoothUuid(QBluetoothUuid::L2cap));
329 protocolDescriptorList.append(t: QVariant::fromValue(value: protocol));
330 protocol.clear();
331
332 protocol << QVariant::fromValue(value: QBluetoothUuid(QBluetoothUuid::Rfcomm))
333 << QVariant::fromValue(value: quint8(server.serverPort()));
334
335 protocolDescriptorList.append(t: QVariant::fromValue(value: protocol));
336 serviceInfo.setAttribute(attributeId: QBluetoothServiceInfo::ProtocolDescriptorList,
337 value: protocolDescriptorList);
338 QVERIFY(serviceInfo.registerService());
339
340 QVERIFY(server.isListening());
341 qDebug() << "Scanning address " << addresses[0].toString();
342 QBluetoothServiceDiscoveryAgent discoveryAgent(addresses[1]);
343 bool setAddress = discoveryAgent.setRemoteAddress(addresses[0]);
344
345 QVERIFY(setAddress);
346
347 QVERIFY(!discoveryAgent.isActive());
348
349 QVERIFY(discoveryAgent.discoveredServices().isEmpty());
350
351 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
352
353 discoveryAgent.start();
354 int scanTime = MaxScanTime;
355 while (finishedSpy.count() == 0 && scanTime > 0) {
356 QTest::qWait(ms: 1000);
357 scanTime -= 1000;
358 }
359
360 QList<QBluetoothServiceInfo> discServices = discoveryAgent.discoveredServices();
361 QVERIFY(!discServices.empty());
362
363 int counter = 0;
364 for (int i = 0; i<discServices.size(); i++)
365 {
366 QBluetoothServiceInfo service((QBluetoothServiceInfo)discServices.at(i));
367 if (uuid == service.serviceUuid())
368 counter++;
369 }
370 QVERIFY(counter == 1);
371 }
372
373}
374
375
376void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery()
377{
378 // Not all devices respond to SDP, so allow for a failure
379 static int expected_failures = 0;
380
381 if (devices.isEmpty())
382 QSKIP("This test requires an in-range bluetooth device");
383
384 QFETCH(QBluetoothDeviceInfo, deviceInfo);
385 QFETCH(QList<QBluetoothUuid>, uuidFilter);
386 QFETCH(QBluetoothServiceDiscoveryAgent::Error, serviceDiscoveryError);
387
388 QBluetoothLocalDevice localDevice;
389 qDebug() << "Scanning address" << deviceInfo.address().toString();
390 QBluetoothServiceDiscoveryAgent discoveryAgent(localDevice.address());
391 bool setAddress = discoveryAgent.setRemoteAddress(deviceInfo.address());
392
393 QVERIFY(setAddress);
394
395 QVERIFY(!discoveryAgent.isActive());
396
397 QVERIFY(discoveryAgent.discoveredServices().isEmpty());
398
399 QVERIFY(discoveryAgent.uuidFilter().isEmpty());
400
401 discoveryAgent.setUuidFilter(uuidFilter);
402
403 QVERIFY(discoveryAgent.uuidFilter() == uuidFilter);
404
405 QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
406 QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)));
407 QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)));
408// connect(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
409// this, SLOT(serviceDiscoveryDebug(QBluetoothServiceInfo)));
410 connect(sender: &discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)),
411 receiver: this, SLOT(serviceError(QBluetoothServiceDiscoveryAgent::Error)));
412
413 discoveryAgent.start(mode: QBluetoothServiceDiscoveryAgent::FullDiscovery);
414
415 /*
416 * Either we wait for discovery agent to run its course (e.g. Bluez 4) or
417 * we have an immediate result (e.g. Bluez 5)
418 */
419 QVERIFY(discoveryAgent.isActive() || !finishedSpy.isEmpty());
420
421 // Wait for up to MaxScanTime for the scan to finish
422 int scanTime = MaxScanTime;
423 while (finishedSpy.count() == 0 && scanTime > 0) {
424 QTest::qWait(ms: 1000);
425 scanTime -= 1000;
426 }
427
428 if (discoveryAgent.error() && expected_failures++ < 2){
429 qDebug() << "Device failed to respond to SDP, skipping device" << discoveryAgent.error() << discoveryAgent.errorString();
430 return;
431 }
432
433 QVERIFY(discoveryAgent.error() == serviceDiscoveryError);
434 QVERIFY(discoveryAgent.errorString() == QString());
435
436 // Expect finished signal with no error
437 QVERIFY(finishedSpy.count() == 1);
438 QVERIFY(errorSpy.isEmpty());
439
440 //if (discoveryAgent.discoveredServices().count() && expected_failures++ <2){
441 if (discoveredSpy.isEmpty() && expected_failures++ < 2){
442 qDebug() << "Device failed to return any results, skipping device" << discoveryAgent.discoveredServices().count();
443 return;
444 }
445
446 // All returned QBluetoothServiceInfo should be valid.
447 bool servicesFound = !discoveredSpy.isEmpty();
448 while (!discoveredSpy.isEmpty()) {
449 const QVariant v = discoveredSpy.takeFirst().at(i: 0);
450 // Work around limitation in QMetaType and moc.
451 // QBluetoothServiceInfo is registered with metatype as QBluetoothServiceInfo
452 // moc sees it as the unqualified QBluetoothServiceInfo.
453 if (v.userType() == qMetaTypeId<QBluetoothServiceInfo>())
454 {
455 const QBluetoothServiceInfo info =
456 *reinterpret_cast<const QBluetoothServiceInfo*>(v.constData());
457
458 QVERIFY(info.isValid());
459 QVERIFY(!info.isRegistered());
460
461#if 0
462 qDebug() << info.device().name() << info.device().address().toString();
463 qDebug() << "\tService name:" << info.serviceName();
464 if (info.protocolServiceMultiplexer() >= 0)
465 qDebug() << "\tL2CAP protocol service multiplexer:" << info.protocolServiceMultiplexer();
466 if (info.serverChannel() >= 0)
467 qDebug() << "\tRFCOMM server channel:" << info.serverChannel();
468 //dumpServiceInfoAttributes(info);
469#endif
470 } else {
471 QFAIL("Unknown type returned by service discovery");
472 }
473
474 }
475
476 if (servicesFound)
477 QVERIFY(discoveryAgent.discoveredServices().count() != 0);
478 discoveryAgent.clear();
479 QVERIFY(discoveryAgent.discoveredServices().count() == 0);
480
481 discoveryAgent.stop();
482 QVERIFY(!discoveryAgent.isActive());
483}
484
485QTEST_MAIN(tst_QBluetoothServiceDiscoveryAgent)
486
487#include "tst_qbluetoothservicediscoveryagent.moc"
488

source code of qtconnectivity/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp