blob: 94a065bca3ba34929c9ffc84d0710c68539473ac [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QDebug>
#include <QLoggingCategory>
#include <QVariant>
#include <QList>
#include <qbluetoothaddress.h>
#include <qbluetoothdevicediscoveryagent.h>
#include <qbluetoothservicediscoveryagent.h>
#include <qbluetoothlocaldevice.h>
#include <qbluetoothserver.h>
#include <qbluetoothserviceinfo.h>
QT_USE_NAMESPACE
// Maximum time to for bluetooth device scan
const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms
class tst_QBluetoothServiceDiscoveryAgent : public QObject
{
Q_OBJECT
public:
tst_QBluetoothServiceDiscoveryAgent();
~tst_QBluetoothServiceDiscoveryAgent();
public slots:
void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info);
void serviceDiscoveryDebug(const QBluetoothServiceInfo &info);
void serviceError(const QBluetoothServiceDiscoveryAgent::Error err);
private slots:
void initTestCase();
void tst_invalidBtAddress();
void tst_serviceDiscovery_data();
void tst_serviceDiscovery();
void tst_serviceDiscoveryAdapters();
private:
QList<QBluetoothDeviceInfo> devices;
bool localDeviceAvailable;
};
tst_QBluetoothServiceDiscoveryAgent::tst_QBluetoothServiceDiscoveryAgent()
{
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
// start Bluetooth if not started
#ifndef Q_OS_OSX
QBluetoothLocalDevice *device = new QBluetoothLocalDevice();
localDeviceAvailable = device->isValid();
if (localDeviceAvailable) {
device->powerOn();
// wait for the device to switch bluetooth mode.
QTest::qWait(1000);
}
delete device;
#else
QBluetoothLocalDevice device;
localDeviceAvailable = QBluetoothLocalDevice().hostMode() != QBluetoothLocalDevice::HostPoweredOff;
#endif
qRegisterMetaType<QBluetoothDeviceInfo>();
qRegisterMetaType<QList<QBluetoothUuid> >();
qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>();
qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>();
}
tst_QBluetoothServiceDiscoveryAgent::~tst_QBluetoothServiceDiscoveryAgent()
{
}
void tst_QBluetoothServiceDiscoveryAgent::deviceDiscoveryDebug(const QBluetoothDeviceInfo &info)
{
qDebug() << "Discovered device:" << info.address().toString() << info.name();
}
void tst_QBluetoothServiceDiscoveryAgent::serviceError(const QBluetoothServiceDiscoveryAgent::Error err)
{
qDebug() << "Service discovery error" << err;
}
void tst_QBluetoothServiceDiscoveryAgent::initTestCase()
{
if (localDeviceAvailable) {
QBluetoothDeviceDiscoveryAgent discoveryAgent;
QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)));
QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)));
// connect(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
// this, SLOT(deviceDiscoveryDebug(QBluetoothDeviceInfo)));
discoveryAgent.start();
// Wait for up to MaxScanTime for the scan to finish
int scanTime = MaxScanTime;
while (finishedSpy.count() == 0 && scanTime > 0) {
QTest::qWait(1000);
scanTime -= 1000;
}
// qDebug() << "Scan time left:" << scanTime;
// Expect finished signal with no error
QVERIFY(finishedSpy.count() == 1);
QVERIFY(errorSpy.isEmpty());
devices = discoveryAgent.discoveredDevices();
}
}
void tst_QBluetoothServiceDiscoveryAgent::tst_invalidBtAddress()
{
#ifdef Q_OS_OSX
if (!localDeviceAvailable)
QSKIP("On OS X this test requires Bluetooth adapter in powered ON state");
#endif
QBluetoothServiceDiscoveryAgent *discoveryAgent = new QBluetoothServiceDiscoveryAgent(QBluetoothAddress("11:11:11:11:11:11"));
QCOMPARE(discoveryAgent->error(), QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError);
discoveryAgent->start();
QCOMPARE(discoveryAgent->isActive(), false);
delete discoveryAgent;
discoveryAgent = new QBluetoothServiceDiscoveryAgent(QBluetoothAddress());
QCOMPARE(discoveryAgent->error(), QBluetoothServiceDiscoveryAgent::NoError);
if (QBluetoothLocalDevice::allDevices().count() > 0) {
discoveryAgent->start();
QCOMPARE(discoveryAgent->isActive(), true);
}
delete discoveryAgent;
}
void tst_QBluetoothServiceDiscoveryAgent::serviceDiscoveryDebug(const QBluetoothServiceInfo &info)
{
qDebug() << "Discovered service on"
<< info.device().name() << info.device().address().toString();
qDebug() << "\tService name:" << info.serviceName() << "cached" << info.device().isCached();
qDebug() << "\tDescription:"
<< info.attribute(QBluetoothServiceInfo::ServiceDescription).toString();
qDebug() << "\tProvider:" << info.attribute(QBluetoothServiceInfo::ServiceProvider).toString();
qDebug() << "\tL2CAP protocol service multiplexer:" << info.protocolServiceMultiplexer();
qDebug() << "\tRFCOMM server channel:" << info.serverChannel();
}
static void dumpAttributeVariant(const QVariant &var, const QString indent)
{
if (!var.isValid()) {
qDebug("%sEmpty", indent.toLocal8Bit().constData());
return;
}
if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
qDebug("%sSequence", indent.toLocal8Bit().constData());
const QBluetoothServiceInfo::Sequence *sequence = static_cast<const QBluetoothServiceInfo::Sequence *>(var.data());
for (const QVariant &v : *sequence)
dumpAttributeVariant(v, indent + '\t');
} else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
qDebug("%sAlternative", indent.toLocal8Bit().constData());
const QBluetoothServiceInfo::Alternative *alternative = static_cast<const QBluetoothServiceInfo::Alternative *>(var.data());
for (const QVariant &v : *alternative)
dumpAttributeVariant(v, indent + '\t');
} else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
QBluetoothUuid uuid = var.value<QBluetoothUuid>();
switch (uuid.minimumSize()) {
case 0:
qDebug("%suuid NULL", indent.toLocal8Bit().constData());
break;
case 2:
qDebug("%suuid %04x", indent.toLocal8Bit().constData(), uuid.toUInt16());
break;
case 4:
qDebug("%suuid %08x", indent.toLocal8Bit().constData(), uuid.toUInt32());
break;
case 16: {
qDebug("%suuid %s", indent.toLocal8Bit().constData(), QByteArray(reinterpret_cast<const char *>(uuid.toUInt128().data), 16).toHex().constData());
break;
}
default:
qDebug("%suuid ???", indent.toLocal8Bit().constData());
}
} else {
switch (var.userType()) {
case QVariant::UInt:
qDebug("%suint %u", indent.toLocal8Bit().constData(), var.toUInt());
break;
case QVariant::Int:
qDebug("%sint %d", indent.toLocal8Bit().constData(), var.toInt());
break;
case QVariant::String:
qDebug("%sstring %s", indent.toLocal8Bit().constData(), var.toString().toLocal8Bit().constData());
break;
case QVariant::Bool:
qDebug("%sbool %d", indent.toLocal8Bit().constData(), var.toBool());
break;
case QVariant::Url:
qDebug("%surl %s", indent.toLocal8Bit().constData(), var.toUrl().toString().toLocal8Bit().constData());
break;
default:
qDebug("%sunknown", indent.toLocal8Bit().constData());
}
}
}
static inline void dumpServiceInfoAttributes(const QBluetoothServiceInfo &info)
{
const QList<quint16> attributes = info.attributes();
for (quint16 id : attributes) {
dumpAttributeVariant(info.attribute(id), QString("\t"));
}
}
void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery_data()
{
if (devices.isEmpty())
QSKIP("This test requires an in-range bluetooth device");
QTest::addColumn<QBluetoothDeviceInfo>("deviceInfo");
QTest::addColumn<QList<QBluetoothUuid> >("uuidFilter");
QTest::addColumn<QBluetoothServiceDiscoveryAgent::Error>("serviceDiscoveryError");
// Only need to test the first 5 live devices
int max = 5;
for (const QBluetoothDeviceInfo &info : qAsConst(devices)) {
if (info.isCached())
continue;
QTest::newRow("default filter") << info << QList<QBluetoothUuid>()
<< QBluetoothServiceDiscoveryAgent::NoError;
if (!--max)
break;
//QTest::newRow("public browse group") << info << (QList<QBluetoothUuid>() << QBluetoothUuid::PublicBrowseGroup);
//QTest::newRow("l2cap") << info << (QList<QBluetoothUuid>() << QBluetoothUuid::L2cap);
}
QTest::newRow("all devices") << QBluetoothDeviceInfo() << QList<QBluetoothUuid>()
<< QBluetoothServiceDiscoveryAgent::NoError;
}
void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscoveryAdapters()
{
QBluetoothLocalDevice localDevice;
int numberOfAdapters = (localDevice.allDevices()).size();
if (numberOfAdapters>1) {
if (devices.isEmpty())
QSKIP("This test requires an in-range bluetooth device");
QList<QBluetoothAddress> addresses;
for (int i=0; i<numberOfAdapters; i++) {
addresses.append(((QBluetoothHostInfo)localDevice.allDevices().at(i)).address());
}
QBluetoothServer server(QBluetoothServiceInfo::RfcommProtocol);
QBluetoothUuid uuid(QBluetoothUuid::Ftp);
server.listen(addresses[0]);
QBluetoothServiceInfo serviceInfo;
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, "serviceName");
QBluetoothServiceInfo::Sequence publicBrowse;
publicBrowse << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
QBluetoothServiceInfo::Sequence profileSequence;
QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
classId << QVariant::fromValue(quint16(0x100));
profileSequence.append(QVariant::fromValue(classId));
serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
profileSequence);
serviceInfo.setServiceUuid(uuid);
QBluetoothServiceInfo::Sequence protocolDescriptorList;
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
protocolDescriptorList.append(QVariant::fromValue(protocol));
protocol.clear();
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
<< QVariant::fromValue(quint8(server.serverPort()));
protocolDescriptorList.append(QVariant::fromValue(protocol));
serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList,
protocolDescriptorList);
QVERIFY(serviceInfo.registerService());
QVERIFY(server.isListening());
qDebug() << "Scanning address " << addresses[0].toString();
QBluetoothServiceDiscoveryAgent discoveryAgent(addresses[1]);
bool setAddress = discoveryAgent.setRemoteAddress(addresses[0]);
QVERIFY(setAddress);
QVERIFY(!discoveryAgent.isActive());
QVERIFY(discoveryAgent.discoveredServices().isEmpty());
QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
discoveryAgent.start();
int scanTime = MaxScanTime;
while (finishedSpy.count() == 0 && scanTime > 0) {
QTest::qWait(1000);
scanTime -= 1000;
}
QList<QBluetoothServiceInfo> discServices = discoveryAgent.discoveredServices();
QVERIFY(!discServices.empty());
int counter = 0;
for (int i = 0; i<discServices.size(); i++)
{
QBluetoothServiceInfo service((QBluetoothServiceInfo)discServices.at(i));
if (uuid == service.serviceUuid())
counter++;
}
QVERIFY(counter == 1);
}
}
void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery()
{
// Not all devices respond to SDP, so allow for a failure
static int expected_failures = 0;
if (devices.isEmpty())
QSKIP("This test requires an in-range bluetooth device");
QFETCH(QBluetoothDeviceInfo, deviceInfo);
QFETCH(QList<QBluetoothUuid>, uuidFilter);
QFETCH(QBluetoothServiceDiscoveryAgent::Error, serviceDiscoveryError);
QBluetoothLocalDevice localDevice;
qDebug() << "Scanning address" << deviceInfo.address().toString();
QBluetoothServiceDiscoveryAgent discoveryAgent(localDevice.address());
bool setAddress = discoveryAgent.setRemoteAddress(deviceInfo.address());
QVERIFY(setAddress);
QVERIFY(!discoveryAgent.isActive());
QVERIFY(discoveryAgent.discoveredServices().isEmpty());
QVERIFY(discoveryAgent.uuidFilter().isEmpty());
discoveryAgent.setUuidFilter(uuidFilter);
QVERIFY(discoveryAgent.uuidFilter() == uuidFilter);
QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished()));
QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)));
QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)));
// connect(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
// this, SLOT(serviceDiscoveryDebug(QBluetoothServiceInfo)));
connect(&discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)),
this, SLOT(serviceError(QBluetoothServiceDiscoveryAgent::Error)));
discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
/*
* Either we wait for discovery agent to run its course (e.g. Bluez 4) or
* we have an immediate result (e.g. Bluez 5)
*/
QVERIFY(discoveryAgent.isActive() || !finishedSpy.isEmpty());
// Wait for up to MaxScanTime for the scan to finish
int scanTime = MaxScanTime;
while (finishedSpy.count() == 0 && scanTime > 0) {
QTest::qWait(1000);
scanTime -= 1000;
}
if (discoveryAgent.error() && expected_failures++ < 2){
qDebug() << "Device failed to respond to SDP, skipping device" << discoveryAgent.error() << discoveryAgent.errorString();
return;
}
QVERIFY(discoveryAgent.error() == serviceDiscoveryError);
QVERIFY(discoveryAgent.errorString() == QString());
// Expect finished signal with no error
QVERIFY(finishedSpy.count() == 1);
QVERIFY(errorSpy.isEmpty());
//if (discoveryAgent.discoveredServices().count() && expected_failures++ <2){
if (discoveredSpy.isEmpty() && expected_failures++ < 2){
qDebug() << "Device failed to return any results, skipping device" << discoveryAgent.discoveredServices().count();
return;
}
// All returned QBluetoothServiceInfo should be valid.
bool servicesFound = !discoveredSpy.isEmpty();
while (!discoveredSpy.isEmpty()) {
const QVariant v = discoveredSpy.takeFirst().at(0);
// Work around limitation in QMetaType and moc.
// QBluetoothServiceInfo is registered with metatype as QBluetoothServiceInfo
// moc sees it as the unqualified QBluetoothServiceInfo.
if (v.userType() == qMetaTypeId<QBluetoothServiceInfo>())
{
const QBluetoothServiceInfo info =
*reinterpret_cast<const QBluetoothServiceInfo*>(v.constData());
QVERIFY(info.isValid());
QVERIFY(!info.isRegistered());
#if 0
qDebug() << info.device().name() << info.device().address().toString();
qDebug() << "\tService name:" << info.serviceName();
if (info.protocolServiceMultiplexer() >= 0)
qDebug() << "\tL2CAP protocol service multiplexer:" << info.protocolServiceMultiplexer();
if (info.serverChannel() >= 0)
qDebug() << "\tRFCOMM server channel:" << info.serverChannel();
//dumpServiceInfoAttributes(info);
#endif
} else {
QFAIL("Unknown type returned by service discovery");
}
}
if (servicesFound)
QVERIFY(discoveryAgent.discoveredServices().count() != 0);
discoveryAgent.clear();
QVERIFY(discoveryAgent.discoveredServices().count() == 0);
discoveryAgent.stop();
QVERIFY(!discoveryAgent.isActive());
}
QTEST_MAIN(tst_QBluetoothServiceDiscoveryAgent)
#include "tst_qbluetoothservicediscoveryagent.moc"