blob: f3a0e9a435c9d37e85c2df2ae76822809acb55a1 [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 <QtBluetooth/qbluetoothaddress.h>
#include <QtBluetooth/qbluetoothdevicediscoveryagent.h>
#include <QtBluetooth/qbluetoothdeviceinfo.h>
#include <QtBluetooth/qbluetoothlocaldevice.h>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergyconnectionparameters.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qendian.h>
#include <QtCore/qscopedpointer.h>
//#include <QtCore/qloggingcategory.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/QtTest>
#ifdef Q_OS_LINUX
#include <QtBluetooth/private/lecmaccalculator_p.h>
#endif
#include <algorithm>
#include <cstring>
using namespace QBluetooth;
class TestQLowEnergyControllerGattServer : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
// Static, local stuff goes here.
void advertisingParameters();
void advertisingData();
void cmacVerifier();
void cmacVerifier_data();
void connectionParameters();
void controllerType();
void serviceData();
// Interaction with actual GATT server goes here. Order is relevant.
void advertisedData();
void serverCommunication();
private:
QBluetoothAddress m_serverAddress;
QBluetoothDeviceInfo m_serverInfo;
QScopedPointer<QLowEnergyController> m_leController;
#if defined(CHECK_CMAC_SUPPORT)
bool checkCmacSupport(const quint128& csrkMsb);
#endif
};
void TestQLowEnergyControllerGattServer::initTestCase()
{
//QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
const QString serverAddress = qgetenv("QT_BT_GATTSERVER_TEST_ADDRESS");
if (serverAddress.isEmpty())
return;
m_serverAddress = QBluetoothAddress(serverAddress);
QVERIFY(!m_serverAddress.isNull());
}
void TestQLowEnergyControllerGattServer::advertisingParameters()
{
QLowEnergyAdvertisingParameters params;
QCOMPARE(params, QLowEnergyAdvertisingParameters());
QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::IgnoreWhiteList);
QCOMPARE(params.minimumInterval(), 1280);
QCOMPARE(params.maximumInterval(), 1280);
QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvInd);
QVERIFY(params.whiteList().isEmpty());
params.setInterval(100, 200);
QCOMPARE(params.minimumInterval(), 100);
QCOMPARE(params.maximumInterval(), 200);
params.setInterval(200, 100);
QCOMPARE(params.minimumInterval(), 200);
QCOMPARE(params.maximumInterval(), 200);
params.setMode(QLowEnergyAdvertisingParameters::AdvScanInd);
QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvScanInd);
const auto whiteList = QList<QLowEnergyAdvertisingParameters::AddressInfo>()
<< QLowEnergyAdvertisingParameters::AddressInfo(QBluetoothAddress(),
QLowEnergyController::PublicAddress);
params.setWhiteList(whiteList, QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
QCOMPARE(params.whiteList(), whiteList);
QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
QVERIFY(params != QLowEnergyAdvertisingParameters());
// verify default ctor
QLowEnergyAdvertisingParameters::AddressInfo info;
QVERIFY(info.address == QBluetoothAddress());
QVERIFY(info.type == QLowEnergyController::PublicAddress);
}
void TestQLowEnergyControllerGattServer::advertisingData()
{
QLowEnergyAdvertisingData data;
QCOMPARE(data, QLowEnergyAdvertisingData());
QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityNone);
QCOMPARE(data.includePowerLevel(), false);
QCOMPARE(data.localName(), QString());
QCOMPARE(data.manufacturerData(), QByteArray());
QCOMPARE(data.manufacturerId(), QLowEnergyAdvertisingData::invalidManufacturerId());
QVERIFY(data.services().isEmpty());
data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited);
QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityLimited);
data.setIncludePowerLevel(true);
QCOMPARE(data.includePowerLevel(), true);
data.setLocalName("A device name");
QCOMPARE(data.localName(), QString("A device name"));
data.setManufacturerData(0xfffd, "some data");
QCOMPARE(data.manufacturerId(), quint16(0xfffd));
QCOMPARE(data.manufacturerData(), QByteArray("some data"));
const auto services = QList<QBluetoothUuid>() << QBluetoothUuid::CurrentTimeService
<< QBluetoothUuid::DeviceInformation;
data.setServices(services);
QCOMPARE(data.services(), services);
QByteArray rawData(7, 'x');
data.setRawData(rawData);
QCOMPARE(data.rawData(), rawData);
QVERIFY(data != QLowEnergyAdvertisingData());
}
void TestQLowEnergyControllerGattServer::cmacVerifier()
{
#if defined(CONFIG_LINUX_CRYPTO_API) && defined(QT_BUILD_INTERNAL) && defined(CONFIG_BLUEZ_LE)
// Test data comes from spec v4.2, Vol 3, Part H, Appendix D.1
const quint128 csrk = {
{ 0x3c, 0x4f, 0xcf, 0x09, 0x88, 0x15, 0xf7, 0xab,
0xa6, 0xd2, 0xae, 0x28, 0x16, 0x15, 0x7e, 0x2b }
};
QFETCH(QByteArray, message);
QFETCH(quint64, expectedMac);
#if defined(CHECK_CMAC_SUPPORT)
if (!checkCmacSupport(csrk)) {
QSKIP("Needed socket options not available. Running qemu?");
}
#endif
const bool success = LeCmacCalculator().verify(message, csrk, expectedMac);
QVERIFY(success);
#else // CONFIG_LINUX_CRYPTO_API
QSKIP("CMAC verification test only applicable for developer builds on Linux "
"with BlueZ and crypto API");
#endif // Q_OS_LINUX
}
#if defined(CHECK_CMAC_SUPPORT)
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <unistd.h>
bool TestQLowEnergyControllerGattServer::checkCmacSupport(const quint128& csrk)
{
bool retval = false;
#if defined(CONFIG_LINUX_CRYPTO_API) && defined(QT_BUILD_INTERNAL) && defined(CONFIG_BLUEZ_LE)
quint128 csrkMsb;
std::reverse_copy(std::begin(csrk.data), std::end(csrk.data), std::begin(csrkMsb.data));
int testSocket = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (testSocket != -1) {
sockaddr_alg sa;
using namespace std;
memset(&sa, 0, sizeof sa);
sa.salg_family = AF_ALG;
strcpy(reinterpret_cast<char *>(sa.salg_type), "hash");
strcpy(reinterpret_cast<char *>(sa.salg_name), "cmac(aes)");
if (::bind(testSocket, reinterpret_cast<sockaddr *>(&sa), sizeof sa) != -1) {
if (setsockopt(testSocket, 279 /* SOL_ALG */, ALG_SET_KEY, csrkMsb.data, sizeof csrkMsb) != -1) {
retval = true;
} else {
QWARN("Needed socket options (SOL_ALG) not available");
}
} else {
QWARN("bind() failed for crypto socket:");
}
close(testSocket);
} else {
QWARN("Unable to create test socket");
}
#endif
return retval;
}
#endif
void TestQLowEnergyControllerGattServer::cmacVerifier_data()
{
QTest::addColumn<QByteArray>("message");
QTest::addColumn<quint64>("expectedMac");
QTest::newRow("D1.1") << QByteArray() << Q_UINT64_C(0xbb1d6929e9593728);
QTest::newRow("D1.2") << QByteArray::fromHex("2a179373117e3de9969f402ee2bec16b")
<< Q_UINT64_C(0x070a16b46b4d4144);
QByteArray messageD13 = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a57"
"1e03ac9c9eb76fac45af8e5130c81c46a35ce411");
std::reverse(messageD13.begin(), messageD13.end());
QTest::newRow("D1.3") << messageD13 << Q_UINT64_C(0xdfa66747de9ae630);
QByteArray messageD14 = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710");
std::reverse(messageD14.begin(), messageD14.end());
QTest::newRow("D1.4") << messageD14 << Q_UINT64_C(0x51f0bebf7e3b9d92);
}
void TestQLowEnergyControllerGattServer::connectionParameters()
{
QLowEnergyConnectionParameters connParams;
QCOMPARE(connParams, QLowEnergyConnectionParameters());
connParams.setIntervalRange(8, 9);
QCOMPARE(connParams.minimumInterval(), double(8));
QCOMPARE(connParams.maximumInterval(), double(9));
connParams.setIntervalRange(9, 8);
QCOMPARE(connParams.minimumInterval(), double(9));
QCOMPARE(connParams.maximumInterval(), double(9));
connParams.setLatency(50);
QCOMPARE(connParams.latency(), 50);
connParams.setSupervisionTimeout(1000);
QCOMPARE(connParams.supervisionTimeout(), 1000);
const QLowEnergyConnectionParameters cp2 = connParams;
QCOMPARE(cp2, connParams);
QLowEnergyConnectionParameters cp3;
QVERIFY(cp3 != connParams);
cp3 = connParams;
QCOMPARE(cp3, connParams);
}
void TestQLowEnergyControllerGattServer::advertisedData()
{
if (m_serverAddress.isNull())
QSKIP("No server address provided");
QBluetoothDeviceDiscoveryAgent discoveryAgent;
discoveryAgent.start();
QSignalSpy spy(&discoveryAgent, SIGNAL(finished()));
QVERIFY(spy.wait(30000));
const QList<QBluetoothDeviceInfo> devices = discoveryAgent.discoveredDevices();
const auto it = std::find_if(devices.constBegin(), devices.constEnd(),
[this](const QBluetoothDeviceInfo &device) { return device.address() == m_serverAddress; });
QVERIFY(it != devices.constEnd());
m_serverInfo = *it;
// BlueZ seems to interfere with the advertising in some way, so that in addition to the name
// we set, the host name of the machine is also sent. Therefore we cannot guarantee that "our"
// name is seen on the scanning machine.
// QCOMPARE(m_serverInfo.name(), QString("Qt GATT server"));
QVERIFY(m_serverInfo.serviceUuids().count() >= 3);
QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::GenericAccess));
QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::RunningSpeedAndCadence));
QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid(quint16(0x2000))));
}
void TestQLowEnergyControllerGattServer::serverCommunication()
{
if (m_serverAddress.isNull())
QSKIP("No server address provided");
m_leController.reset(QLowEnergyController::createCentral(m_serverInfo));
QVERIFY(!m_leController.isNull());
m_leController->connectToDevice();
QScopedPointer<QSignalSpy> spy(new QSignalSpy(m_leController.data(),
&QLowEnergyController::connected));
QVERIFY(spy->wait(30000));
m_leController->discoverServices();
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
QVERIFY(spy->wait(30000));
const QList<QBluetoothUuid> serviceUuids = m_leController->services();
QCOMPARE(serviceUuids.count(), 4);
QVERIFY(serviceUuids.contains(QBluetoothUuid::GenericAccess));
QVERIFY(serviceUuids.contains(QBluetoothUuid::RunningSpeedAndCadence));
QVERIFY(serviceUuids.contains(QBluetoothUuid(quint16(0x2000))));
QVERIFY(serviceUuids.contains(QBluetoothUuid(QString("c47774c7-f237-4523-8968-e4ae75431daf"))));
const QScopedPointer<QLowEnergyService> genericAccessService(
m_leController->createServiceObject(QBluetoothUuid::GenericAccess));
QVERIFY(!genericAccessService.isNull());
genericAccessService->discoverDetails();
while (genericAccessService->state() != QLowEnergyService::ServiceDiscovered) {
spy.reset(new QSignalSpy(genericAccessService.data(), &QLowEnergyService::stateChanged));
QVERIFY(spy->wait(3000));
}
QCOMPARE(genericAccessService->includedServices().count(), 1);
QCOMPARE(genericAccessService->includedServices().first(),
QBluetoothUuid(QBluetoothUuid::RunningSpeedAndCadence));
QCOMPARE(genericAccessService->characteristics().count(), 2);
const QLowEnergyCharacteristic deviceNameChar
= genericAccessService->characteristic(QBluetoothUuid::DeviceName);
QVERIFY(deviceNameChar.isValid());
QCOMPARE(deviceNameChar.descriptors().count(), 0);
QCOMPARE(deviceNameChar.properties(),
QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write);
QCOMPARE(deviceNameChar.value().constData(), "Qt GATT server");
const QLowEnergyCharacteristic appearanceChar
= genericAccessService->characteristic(QBluetoothUuid::Appearance);
QVERIFY(appearanceChar.isValid());
QCOMPARE(appearanceChar.descriptors().count(), 0);
QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read);
auto value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(
appearanceChar.value().constData()));
QCOMPARE(value, quint16(128));
const QScopedPointer<QLowEnergyService> runningSpeedService(
m_leController->createServiceObject(QBluetoothUuid::RunningSpeedAndCadence));
QVERIFY(!runningSpeedService.isNull());
runningSpeedService->discoverDetails();
while (runningSpeedService->state() != QLowEnergyService::ServiceDiscovered) {
spy.reset(new QSignalSpy(runningSpeedService.data(), &QLowEnergyService::stateChanged));
QVERIFY(spy->wait(3000));
}
QCOMPARE(runningSpeedService->includedServices().count(), 0);
QCOMPARE(runningSpeedService->characteristics().count(), 2);
QLowEnergyCharacteristic measurementChar
= runningSpeedService->characteristic(QBluetoothUuid::RSCMeasurement);
QVERIFY(measurementChar.isValid());
QCOMPARE(measurementChar.descriptors().count(), 1);
const QLowEnergyDescriptor clientConfigDesc
= measurementChar.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(clientConfigDesc.isValid());
QCOMPARE(clientConfigDesc.value(), QByteArray(2, 0));
QCOMPARE(measurementChar.properties(), QLowEnergyCharacteristic::Notify);
QCOMPARE(measurementChar.value(), QByteArray()); // Empty because Read property not set
QLowEnergyCharacteristic featureChar
= runningSpeedService->characteristic(QBluetoothUuid::RSCFeature);
QVERIFY(featureChar.isValid());
QCOMPARE(featureChar.descriptors().count(), 0);
QCOMPARE(featureChar.properties(), QLowEnergyCharacteristic::Read);
value = qFromLittleEndian<quint16>(reinterpret_cast<const uchar *>(
featureChar.value().constData()));
QCOMPARE(value, quint16(1 << 2));
// 128 bit custom uuid service
QBluetoothUuid serviceUuid128(QString("c47774c7-f237-4523-8968-e4ae75431daf"));
QBluetoothUuid charUuid128(QString("c0ad61b1-79e7-42f9-ace0-0a9aa0d0a4f8"));
QScopedPointer<QLowEnergyService> customService128(
m_leController->createServiceObject(serviceUuid128));
QVERIFY(!customService128.isNull());
customService128->discoverDetails();
while (customService128->state() != QLowEnergyService::ServiceDiscovered) {
spy.reset(new QSignalSpy(customService128.data(), &QLowEnergyService::stateChanged));
QVERIFY(spy->wait(5000));
}
QCOMPARE(customService128->serviceUuid(), serviceUuid128);
QCOMPARE(customService128->includedServices().count(), 0);
QCOMPARE(customService128->characteristics().count(), 1);
QLowEnergyCharacteristic customChar128
= customService128->characteristic(charUuid128);
QVERIFY(customChar128.isValid());
QCOMPARE(customChar128.descriptors().count(), 0);
QCOMPARE(customChar128.value(), QByteArray(15, 'a'));
QScopedPointer<QLowEnergyService> customService(
m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000))));
QVERIFY(!customService.isNull());
customService->discoverDetails();
while (customService->state() != QLowEnergyService::ServiceDiscovered) {
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
QVERIFY(spy->wait(5000));
}
QCOMPARE(customService->includedServices().count(), 0);
QCOMPARE(customService->characteristics().count(), 5);
QLowEnergyCharacteristic customChar
= customService->characteristic(QBluetoothUuid(quint16(0x5000)));
QVERIFY(customChar.isValid());
QCOMPARE(customChar.descriptors().count(), 0);
QCOMPARE(customChar.value(), QByteArray(1024, 'x'));
QLowEnergyCharacteristic customChar2
= customService->characteristic(QBluetoothUuid(quint16(0x5001)));
QVERIFY(customChar2.isValid());
QCOMPARE(customChar2.descriptors().count(), 0);
QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement.
QLowEnergyCharacteristic customChar3
= customService->characteristic(QBluetoothUuid(quint16(0x5002)));
QVERIFY(customChar3.isValid());
QCOMPARE(customChar3.properties(),
QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate);
QCOMPARE(customChar3.descriptors().count(), 1);
QLowEnergyDescriptor cc3ClientConfig
= customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(cc3ClientConfig.isValid());
QLowEnergyCharacteristic customChar4
= customService->characteristic(QBluetoothUuid(quint16(0x5003)));
QVERIFY(customChar4.isValid());
QCOMPARE(customChar4.properties(),
QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
QCOMPARE(customChar4.descriptors().count(), 1);
QLowEnergyDescriptor cc4ClientConfig
= customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(cc4ClientConfig.isValid());
QLowEnergyCharacteristic customChar5
= customService->characteristic(QBluetoothUuid(quint16(0x5004)));
QVERIFY(customChar5.isValid());
QCOMPARE(customChar5.properties(),
QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned);
QCOMPARE(customChar5.descriptors().count(), 0);
QCOMPARE(customChar5.value(), QByteArray("initial"));
customService->writeCharacteristic(customChar, "whatever");
spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
(QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
QVERIFY(spy->wait(3000));
QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
spy.reset(new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
(QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
customService->writeCharacteristic(customChar5, "1", QLowEnergyService::WriteSigned);
// error might happen immediately or once event loop comes back
bool wasError = ((spy->count() > 0) || spy->wait(3000)); //
if (!wasError) {
// Signed write is done twice to test the sign counter stuff.
// Note: We assume here that the link is not encrypted, as this information is not exported.
customService->readCharacteristic(customChar5);
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
QVERIFY(spy->wait(3000));
QCOMPARE(customChar5.value(), QByteArray("1"));
customService->writeCharacteristic(customChar5, "2", QLowEnergyService::WriteSigned);
customService->readCharacteristic(customChar5);
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
QVERIFY(spy->wait(3000));
QCOMPARE(customChar5.value(), QByteArray("2"));
} else {
QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
}
QByteArray indicateValue(2, 0);
qToLittleEndian<quint16>(2, reinterpret_cast<uchar *>(indicateValue.data()));
customService->writeDescriptor(cc3ClientConfig, indicateValue);
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
QVERIFY(spy->wait(3000));
QByteArray notifyValue(2, 0);
qToLittleEndian<quint16>(1, reinterpret_cast<uchar *>(notifyValue.data()));
customService->writeDescriptor(cc4ClientConfig, notifyValue);
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
QVERIFY(spy->wait(3000));
// Server now changes the characteristic values.
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicChanged));
QVERIFY(spy->wait(3000));
if (spy->count() == 1)
QVERIFY(spy->wait(3000));
QCOMPARE(customChar3.value().constData(), "indicated");
QCOMPARE(customChar4.value().constData(), "notified");
// signal requires root privileges on Linux
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated));
QVERIFY(spy->wait(5000));
m_leController->disconnectFromDevice();
if (m_leController->state() != QLowEnergyController::UnconnectedState) {
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::stateChanged));
QVERIFY(spy->wait(3000));
}
QCOMPARE(m_leController->state(), QLowEnergyController::UnconnectedState);
// Server now changes the characteristic values again while we're offline.
// Note: We cannot test indications and notifications for this case, as the client does
// not cache the old information and thus does not yet know the characteristics
// at the time the notification/indication is received.
QTest::qWait(3000);
m_leController->connectToDevice();
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::connected));
QVERIFY(spy->wait(30000));
m_leController->discoverServices();
spy.reset(new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
QVERIFY(spy->wait(30000));
customService.reset(m_leController->createServiceObject(QBluetoothUuid(quint16(0x2000))));
QVERIFY(!customService.isNull());
customService->discoverDetails();
while (customService->state() != QLowEnergyService::ServiceDiscovered) {
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
QVERIFY(spy->wait(5000));
}
customChar3 = customService->characteristic(QBluetoothUuid(quint16(0x5002)));
QVERIFY(customChar3.isValid());
QCOMPARE(customChar3.value().constData(), "indicated2");
customChar4 = customService->characteristic(QBluetoothUuid(quint16(0x5003)));
QVERIFY(customChar4.isValid());
QCOMPARE(customChar4.value().constData(), "notified2");
cc3ClientConfig = customChar3.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(cc3ClientConfig.isValid());
cc4ClientConfig = customChar4.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
QVERIFY(cc4ClientConfig.isValid());
const bool isBonded = QBluetoothLocalDevice().pairingStatus(m_serverAddress)
!= QBluetoothLocalDevice::Unpaired;
if (isBonded) {
QCOMPARE(cc3ClientConfig.value(), indicateValue);
QCOMPARE(cc4ClientConfig.value(), notifyValue);
// Do a third signed write to test sign counter persistence.
customChar5 = customService->characteristic(QBluetoothUuid(quint16(0x5004)));
QVERIFY(customChar5.isValid());
QCOMPARE(customChar5.value(), QByteArray("2"));
customService->writeCharacteristic(customChar5, "3", QLowEnergyService::WriteSigned);
customService->readCharacteristic(customChar5);
spy.reset(new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
QVERIFY(spy->wait(3000));
QCOMPARE(customChar5.value(), QByteArray("3"));
} else {
QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0));
QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0));
}
}
void TestQLowEnergyControllerGattServer::controllerType()
{
const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
QVERIFY(!controller.isNull());
QCOMPARE(controller->role(), QLowEnergyController::PeripheralRole);
}
void TestQLowEnergyControllerGattServer::serviceData()
{
QLowEnergyDescriptorData descData;
QVERIFY(!descData.isValid());
descData.setUuid(QBluetoothUuid::ValidRange);
QCOMPARE(descData.uuid(), QBluetoothUuid(QBluetoothUuid::ValidRange));
QVERIFY(descData.isValid());
QVERIFY(descData != QLowEnergyDescriptorData());
descData.setValue("xyz");
QCOMPARE(descData.value().constData(), "xyz");
descData.setReadPermissions(true, AttAuthenticationRequired);
QCOMPARE(descData.isReadable(), true);
QCOMPARE(descData.readConstraints(), AttAuthenticationRequired);
descData.setWritePermissions(false);
QCOMPARE(descData.isWritable(), false);
QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc");
QVERIFY(descData2 != QLowEnergyDescriptorData());
QVERIFY(descData2.isValid());
QCOMPARE(descData2.uuid(), QBluetoothUuid(QBluetoothUuid::ReportReference));
QCOMPARE(descData2.value().constData(), "abc");
QLowEnergyCharacteristicData charData;
QVERIFY(!charData.isValid());
charData.setUuid(QBluetoothUuid::BatteryLevel);
QVERIFY(charData != QLowEnergyCharacteristicData());
QCOMPARE(charData.uuid(), QBluetoothUuid(QBluetoothUuid::BatteryLevel));
QVERIFY(charData.isValid());
charData.setValue("value");
QCOMPARE(charData.value().constData(), "value");
charData.setValueLength(4, 7);
QCOMPARE(charData.minimumValueLength(), 4);
QCOMPARE(charData.maximumValueLength(), 7);
charData.setValueLength(5, 2);
QCOMPARE(charData.minimumValueLength(), 5);
QCOMPARE(charData.maximumValueLength(), 5);
const QLowEnergyCharacteristic::PropertyTypes props
= QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned;
charData.setProperties(props);
QCOMPARE(charData.properties(), props);
charData.setReadConstraints(AttEncryptionRequired);
QCOMPARE(charData.readConstraints(), AttEncryptionRequired);
charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired);
QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired);
charData.addDescriptor(descData);
QCOMPARE(charData.descriptors().count(), 1);
charData.setDescriptors(QList<QLowEnergyDescriptorData>());
QCOMPARE(charData.descriptors().count(), 0);
charData.setDescriptors(QList<QLowEnergyDescriptorData>() << descData << descData2);
QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval");
charData.addDescriptor(descData3);
charData.addDescriptor(QLowEnergyDescriptorData()); // Invalid.
QCOMPARE(charData.descriptors(),
QList<QLowEnergyDescriptorData>() << descData << descData2 << descData3);
QLowEnergyServiceData secondaryData;
QVERIFY(!secondaryData.isValid());
secondaryData.setUuid(QBluetoothUuid::SerialPort);
QCOMPARE(secondaryData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort));
QVERIFY(secondaryData.isValid());
QVERIFY(secondaryData != QLowEnergyServiceData());
secondaryData.setType(QLowEnergyServiceData::ServiceTypeSecondary);
QCOMPARE(secondaryData.type(), QLowEnergyServiceData::ServiceTypeSecondary);
secondaryData.addCharacteristic(charData);
QCOMPARE(secondaryData.characteristics().count(), 1);
secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>());
QCOMPARE(secondaryData.characteristics().count(), 0);
secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>()
<< charData << QLowEnergyCharacteristicData());
QCOMPARE(secondaryData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData);
QLowEnergyServiceData backupData;
backupData.setUuid(QBluetoothUuid::SerialPort);
QCOMPARE(backupData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort));
QVERIFY(backupData.isValid());
QVERIFY(backupData != QLowEnergyServiceData());
backupData.setType(QLowEnergyServiceData::ServiceTypeSecondary);
QCOMPARE(backupData.type(), QLowEnergyServiceData::ServiceTypeSecondary);
backupData.setCharacteristics(QList<QLowEnergyCharacteristicData>()
<< charData << QLowEnergyCharacteristicData());
QCOMPARE(backupData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData);
QVERIFY(backupData == secondaryData);
#ifdef Q_OS_DARWIN
QSKIP("GATT server functionality not implemented for Apple platforms");
#endif
const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
QVERIFY(!controller->addService(QLowEnergyServiceData()));
const QScopedPointer<QLowEnergyService> secondaryService(controller->addService(secondaryData));
QVERIFY(!secondaryService.isNull());
QCOMPARE(secondaryService->serviceUuid(), secondaryData.uuid());
const QList<QLowEnergyCharacteristic> characteristics = secondaryService->characteristics();
QCOMPARE(characteristics.count(), 1);
QCOMPARE(characteristics.first().uuid(), charData.uuid());
const QList<QLowEnergyDescriptor> descriptors = characteristics.first().descriptors();
QCOMPARE(descriptors.count(), 3);
const auto inUuids = QSet<QBluetoothUuid>() << descData.uuid() << descData2.uuid()
<< descData3.uuid();
QSet<QBluetoothUuid> outUuids;
for (const QLowEnergyDescriptor &desc : descriptors)
outUuids << desc.uuid();
QCOMPARE(inUuids, outUuids);
QLowEnergyServiceData primaryData;
primaryData.setUuid(QBluetoothUuid::Headset);
primaryData.addIncludedService(secondaryService.data());
const QScopedPointer<QLowEnergyService> primaryService(controller->addService(primaryData));
QVERIFY(!primaryService.isNull());
QCOMPARE(primaryService->characteristics().count(), 0);
const QList<QBluetoothUuid> includedServices = primaryService->includedServices();
QCOMPARE(includedServices.count(), 1);
QCOMPARE(includedServices.first(), secondaryService->serviceUuid());
}
QTEST_MAIN(TestQLowEnergyControllerGattServer)
#include "tst_qlowenergycontroller-gattserver.moc"