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 <QtBluetooth/qbluetoothaddress.h>
30#include <QtBluetooth/qbluetoothdevicediscoveryagent.h>
31#include <QtBluetooth/qbluetoothdeviceinfo.h>
32#include <QtBluetooth/qbluetoothlocaldevice.h>
33#include <QtBluetooth/qlowenergyadvertisingdata.h>
34#include <QtBluetooth/qlowenergyadvertisingparameters.h>
35#include <QtBluetooth/qlowenergyconnectionparameters.h>
36#include <QtBluetooth/qlowenergycontroller.h>
37#include <QtBluetooth/qlowenergycharacteristicdata.h>
38#include <QtBluetooth/qlowenergydescriptordata.h>
39#include <QtBluetooth/qlowenergyservicedata.h>
40#include <QtCore/qendian.h>
41#include <QtCore/qscopedpointer.h>
42//#include <QtCore/qloggingcategory.h>
43#include <QtTest/qsignalspy.h>
44#include <QtTest/QtTest>
45
46#ifdef Q_OS_LINUX
47#include <QtBluetooth/private/lecmaccalculator_p.h>
48#endif
49
50#include <algorithm>
51#include <cstring>
52
53using namespace QBluetooth;
54
55class TestQLowEnergyControllerGattServer : public QObject
56{
57 Q_OBJECT
58
59private slots:
60 void initTestCase();
61
62 // Static, local stuff goes here.
63 void advertisingParameters();
64 void advertisingData();
65 void cmacVerifier();
66 void cmacVerifier_data();
67 void connectionParameters();
68 void controllerType();
69 void serviceData();
70
71 // Interaction with actual GATT server goes here. Order is relevant.
72 void advertisedData();
73 void serverCommunication();
74
75private:
76 QBluetoothAddress m_serverAddress;
77 QBluetoothDeviceInfo m_serverInfo;
78 QScopedPointer<QLowEnergyController> m_leController;
79
80#if defined(CHECK_CMAC_SUPPORT)
81 bool checkCmacSupport(const quint128& csrkMsb);
82#endif
83};
84
85
86void TestQLowEnergyControllerGattServer::initTestCase()
87{
88 //QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
89 const QString serverAddress = qgetenv(varName: "QT_BT_GATTSERVER_TEST_ADDRESS");
90 if (serverAddress.isEmpty())
91 return;
92 m_serverAddress = QBluetoothAddress(serverAddress);
93 QVERIFY(!m_serverAddress.isNull());
94}
95
96void TestQLowEnergyControllerGattServer::advertisingParameters()
97{
98 QLowEnergyAdvertisingParameters params;
99 QCOMPARE(params, QLowEnergyAdvertisingParameters());
100 QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::IgnoreWhiteList);
101 QCOMPARE(params.minimumInterval(), 1280);
102 QCOMPARE(params.maximumInterval(), 1280);
103 QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvInd);
104 QVERIFY(params.whiteList().isEmpty());
105
106 params.setInterval(minimum: 100, maximum: 200);
107 QCOMPARE(params.minimumInterval(), 100);
108 QCOMPARE(params.maximumInterval(), 200);
109 params.setInterval(minimum: 200, maximum: 100);
110 QCOMPARE(params.minimumInterval(), 200);
111 QCOMPARE(params.maximumInterval(), 200);
112
113 params.setMode(QLowEnergyAdvertisingParameters::AdvScanInd);
114 QCOMPARE(params.mode(), QLowEnergyAdvertisingParameters::AdvScanInd);
115
116 const auto whiteList = QList<QLowEnergyAdvertisingParameters::AddressInfo>()
117 << QLowEnergyAdvertisingParameters::AddressInfo(QBluetoothAddress(),
118 QLowEnergyController::PublicAddress);
119 params.setWhiteList(whiteList, policy: QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
120 QCOMPARE(params.whiteList(), whiteList);
121 QCOMPARE(params.filterPolicy(), QLowEnergyAdvertisingParameters::UseWhiteListForConnecting);
122 QVERIFY(params != QLowEnergyAdvertisingParameters());
123
124 // verify default ctor
125 QLowEnergyAdvertisingParameters::AddressInfo info;
126 QVERIFY(info.address == QBluetoothAddress());
127 QVERIFY(info.type == QLowEnergyController::PublicAddress);
128}
129
130void TestQLowEnergyControllerGattServer::advertisingData()
131{
132 QLowEnergyAdvertisingData data;
133 QCOMPARE(data, QLowEnergyAdvertisingData());
134 QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityNone);
135 QCOMPARE(data.includePowerLevel(), false);
136 QCOMPARE(data.localName(), QString());
137 QCOMPARE(data.manufacturerData(), QByteArray());
138 QCOMPARE(data.manufacturerId(), QLowEnergyAdvertisingData::invalidManufacturerId());
139 QVERIFY(data.services().isEmpty());
140
141 data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited);
142 QCOMPARE(data.discoverability(), QLowEnergyAdvertisingData::DiscoverabilityLimited);
143
144 data.setIncludePowerLevel(true);
145 QCOMPARE(data.includePowerLevel(), true);
146
147 data.setLocalName("A device name");
148 QCOMPARE(data.localName(), QString("A device name"));
149
150 data.setManufacturerData(id: 0xfffd, data: "some data");
151 QCOMPARE(data.manufacturerId(), quint16(0xfffd));
152 QCOMPARE(data.manufacturerData(), QByteArray("some data"));
153
154 const auto services = QList<QBluetoothUuid>() << QBluetoothUuid::CurrentTimeService
155 << QBluetoothUuid::DeviceInformation;
156 data.setServices(services);
157 QCOMPARE(data.services(), services);
158
159 QByteArray rawData(7, 'x');
160 data.setRawData(rawData);
161 QCOMPARE(data.rawData(), rawData);
162
163 QVERIFY(data != QLowEnergyAdvertisingData());
164}
165
166void TestQLowEnergyControllerGattServer::cmacVerifier()
167{
168#if defined(CONFIG_LINUX_CRYPTO_API) && defined(QT_BUILD_INTERNAL) && defined(CONFIG_BLUEZ_LE)
169 // Test data comes from spec v4.2, Vol 3, Part H, Appendix D.1
170 const quint128 csrk = {
171 { 0x3c, 0x4f, 0xcf, 0x09, 0x88, 0x15, 0xf7, 0xab,
172 0xa6, 0xd2, 0xae, 0x28, 0x16, 0x15, 0x7e, 0x2b }
173 };
174 QFETCH(QByteArray, message);
175 QFETCH(quint64, expectedMac);
176
177#if defined(CHECK_CMAC_SUPPORT)
178 if (!checkCmacSupport(csrk)) {
179 QSKIP("Needed socket options not available. Running qemu?");
180 }
181#endif
182
183 const bool success = LeCmacCalculator().verify(message, csrk, expectedMac);
184 QVERIFY(success);
185#else // CONFIG_LINUX_CRYPTO_API
186 QSKIP("CMAC verification test only applicable for developer builds on Linux "
187 "with BlueZ and crypto API");
188#endif // Q_OS_LINUX
189}
190
191#if defined(CHECK_CMAC_SUPPORT)
192#include <sys/socket.h>
193#include <linux/if_alg.h>
194#include <unistd.h>
195
196bool TestQLowEnergyControllerGattServer::checkCmacSupport(const quint128& csrk)
197{
198 bool retval = false;
199#if defined(CONFIG_LINUX_CRYPTO_API) && defined(QT_BUILD_INTERNAL) && defined(CONFIG_BLUEZ_LE)
200 quint128 csrkMsb;
201 std::reverse_copy(std::begin(csrk.data), std::end(csrk.data), std::begin(csrkMsb.data));
202
203 int testSocket = socket(AF_ALG, SOCK_SEQPACKET, 0);
204 if (testSocket != -1) {
205 sockaddr_alg sa;
206 using namespace std;
207 memset(&sa, 0, sizeof sa);
208 sa.salg_family = AF_ALG;
209 strcpy(reinterpret_cast<char *>(sa.salg_type), "hash");
210 strcpy(reinterpret_cast<char *>(sa.salg_name), "cmac(aes)");
211 if (::bind(testSocket, reinterpret_cast<sockaddr *>(&sa), sizeof sa) != -1) {
212 if (setsockopt(testSocket, 279 /* SOL_ALG */, ALG_SET_KEY, csrkMsb.data, sizeof csrkMsb) != -1) {
213 retval = true;
214 } else {
215 QWARN("Needed socket options (SOL_ALG) not available");
216 }
217 } else {
218 QWARN("bind() failed for crypto socket:");
219 }
220 close(testSocket);
221 } else {
222 QWARN("Unable to create test socket");
223 }
224#endif
225 return retval;
226}
227#endif
228
229void TestQLowEnergyControllerGattServer::cmacVerifier_data()
230{
231 QTest::addColumn<QByteArray>(name: "message");
232 QTest::addColumn<quint64>(name: "expectedMac");
233 QTest::newRow(dataTag: "D1.1") << QByteArray() << Q_UINT64_C(0xbb1d6929e9593728);
234 QTest::newRow(dataTag: "D1.2") << QByteArray::fromHex(hexEncoded: "2a179373117e3de9969f402ee2bec16b")
235 << Q_UINT64_C(0x070a16b46b4d4144);
236 QByteArray messageD13 = QByteArray::fromHex(hexEncoded: "6bc1bee22e409f96e93d7e117393172aae2d8a57"
237 "1e03ac9c9eb76fac45af8e5130c81c46a35ce411");
238 std::reverse(first: messageD13.begin(), last: messageD13.end());
239 QTest::newRow(dataTag: "D1.3") << messageD13 << Q_UINT64_C(0xdfa66747de9ae630);
240 QByteArray messageD14 = QByteArray::fromHex(hexEncoded: "6bc1bee22e409f96e93d7e117393172a"
241 "ae2d8a571e03ac9c9eb76fac45af8e51"
242 "30c81c46a35ce411e5fbc1191a0a52ef"
243 "f69f2445df4f9b17ad2b417be66c3710");
244 std::reverse(first: messageD14.begin(), last: messageD14.end());
245 QTest::newRow(dataTag: "D1.4") << messageD14 << Q_UINT64_C(0x51f0bebf7e3b9d92);
246}
247
248void TestQLowEnergyControllerGattServer::connectionParameters()
249{
250 QLowEnergyConnectionParameters connParams;
251 QCOMPARE(connParams, QLowEnergyConnectionParameters());
252 connParams.setIntervalRange(minimum: 8, maximum: 9);
253 QCOMPARE(connParams.minimumInterval(), double(8));
254 QCOMPARE(connParams.maximumInterval(), double(9));
255 connParams.setIntervalRange(minimum: 9, maximum: 8);
256 QCOMPARE(connParams.minimumInterval(), double(9));
257 QCOMPARE(connParams.maximumInterval(), double(9));
258 connParams.setLatency(50);
259 QCOMPARE(connParams.latency(), 50);
260 connParams.setSupervisionTimeout(1000);
261 QCOMPARE(connParams.supervisionTimeout(), 1000);
262 const QLowEnergyConnectionParameters cp2 = connParams;
263 QCOMPARE(cp2, connParams);
264 QLowEnergyConnectionParameters cp3;
265 QVERIFY(cp3 != connParams);
266 cp3 = connParams;
267 QCOMPARE(cp3, connParams);
268}
269
270void TestQLowEnergyControllerGattServer::advertisedData()
271{
272 if (m_serverAddress.isNull())
273 QSKIP("No server address provided");
274 QBluetoothDeviceDiscoveryAgent discoveryAgent;
275 discoveryAgent.start();
276 QSignalSpy spy(&discoveryAgent, SIGNAL(finished()));
277 QVERIFY(spy.wait(30000));
278 const QList<QBluetoothDeviceInfo> devices = discoveryAgent.discoveredDevices();
279 const auto it = std::find_if(first: devices.constBegin(), last: devices.constEnd(),
280 pred: [this](const QBluetoothDeviceInfo &device) { return device.address() == m_serverAddress; });
281 QVERIFY(it != devices.constEnd());
282 m_serverInfo = *it;
283
284 // BlueZ seems to interfere with the advertising in some way, so that in addition to the name
285 // we set, the host name of the machine is also sent. Therefore we cannot guarantee that "our"
286 // name is seen on the scanning machine.
287 // QCOMPARE(m_serverInfo.name(), QString("Qt GATT server"));
288
289 QVERIFY(m_serverInfo.serviceUuids().count() >= 3);
290 QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::GenericAccess));
291 QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid::RunningSpeedAndCadence));
292 QVERIFY(m_serverInfo.serviceUuids().contains(QBluetoothUuid(quint16(0x2000))));
293}
294
295void TestQLowEnergyControllerGattServer::serverCommunication()
296{
297 if (m_serverAddress.isNull())
298 QSKIP("No server address provided");
299 m_leController.reset(other: QLowEnergyController::createCentral(remoteDevice: m_serverInfo));
300 QVERIFY(!m_leController.isNull());
301 m_leController->connectToDevice();
302 QScopedPointer<QSignalSpy> spy(new QSignalSpy(m_leController.data(),
303 &QLowEnergyController::connected));
304 QVERIFY(spy->wait(30000));
305 m_leController->discoverServices();
306 spy.reset(other: new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
307 QVERIFY(spy->wait(30000));
308 const QList<QBluetoothUuid> serviceUuids = m_leController->services();
309 QCOMPARE(serviceUuids.count(), 4);
310 QVERIFY(serviceUuids.contains(QBluetoothUuid::GenericAccess));
311 QVERIFY(serviceUuids.contains(QBluetoothUuid::RunningSpeedAndCadence));
312 QVERIFY(serviceUuids.contains(QBluetoothUuid(quint16(0x2000))));
313 QVERIFY(serviceUuids.contains(QBluetoothUuid(QString("c47774c7-f237-4523-8968-e4ae75431daf"))));
314
315 const QScopedPointer<QLowEnergyService> genericAccessService(
316 m_leController->createServiceObject(service: QBluetoothUuid::GenericAccess));
317 QVERIFY(!genericAccessService.isNull());
318 genericAccessService->discoverDetails();
319 while (genericAccessService->state() != QLowEnergyService::ServiceDiscovered) {
320 spy.reset(other: new QSignalSpy(genericAccessService.data(), &QLowEnergyService::stateChanged));
321 QVERIFY(spy->wait(3000));
322 }
323 QCOMPARE(genericAccessService->includedServices().count(), 1);
324 QCOMPARE(genericAccessService->includedServices().first(),
325 QBluetoothUuid(QBluetoothUuid::RunningSpeedAndCadence));
326 QCOMPARE(genericAccessService->characteristics().count(), 2);
327 const QLowEnergyCharacteristic deviceNameChar
328 = genericAccessService->characteristic(uuid: QBluetoothUuid::DeviceName);
329 QVERIFY(deviceNameChar.isValid());
330 QCOMPARE(deviceNameChar.descriptors().count(), 0);
331 QCOMPARE(deviceNameChar.properties(),
332 QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write);
333 QCOMPARE(deviceNameChar.value().constData(), "Qt GATT server");
334 const QLowEnergyCharacteristic appearanceChar
335 = genericAccessService->characteristic(uuid: QBluetoothUuid::Appearance);
336 QVERIFY(appearanceChar.isValid());
337 QCOMPARE(appearanceChar.descriptors().count(), 0);
338 QCOMPARE(appearanceChar.properties(), QLowEnergyCharacteristic::Read);
339 auto value = qFromLittleEndian<quint16>(src: reinterpret_cast<const uchar *>(
340 appearanceChar.value().constData()));
341 QCOMPARE(value, quint16(128));
342
343 const QScopedPointer<QLowEnergyService> runningSpeedService(
344 m_leController->createServiceObject(service: QBluetoothUuid::RunningSpeedAndCadence));
345 QVERIFY(!runningSpeedService.isNull());
346 runningSpeedService->discoverDetails();
347 while (runningSpeedService->state() != QLowEnergyService::ServiceDiscovered) {
348 spy.reset(other: new QSignalSpy(runningSpeedService.data(), &QLowEnergyService::stateChanged));
349 QVERIFY(spy->wait(3000));
350 }
351 QCOMPARE(runningSpeedService->includedServices().count(), 0);
352 QCOMPARE(runningSpeedService->characteristics().count(), 2);
353 QLowEnergyCharacteristic measurementChar
354 = runningSpeedService->characteristic(uuid: QBluetoothUuid::RSCMeasurement);
355 QVERIFY(measurementChar.isValid());
356 QCOMPARE(measurementChar.descriptors().count(), 1);
357 const QLowEnergyDescriptor clientConfigDesc
358 = measurementChar.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
359 QVERIFY(clientConfigDesc.isValid());
360 QCOMPARE(clientConfigDesc.value(), QByteArray(2, 0));
361 QCOMPARE(measurementChar.properties(), QLowEnergyCharacteristic::Notify);
362 QCOMPARE(measurementChar.value(), QByteArray()); // Empty because Read property not set
363 QLowEnergyCharacteristic featureChar
364 = runningSpeedService->characteristic(uuid: QBluetoothUuid::RSCFeature);
365 QVERIFY(featureChar.isValid());
366 QCOMPARE(featureChar.descriptors().count(), 0);
367 QCOMPARE(featureChar.properties(), QLowEnergyCharacteristic::Read);
368 value = qFromLittleEndian<quint16>(src: reinterpret_cast<const uchar *>(
369 featureChar.value().constData()));
370 QCOMPARE(value, quint16(1 << 2));
371
372 // 128 bit custom uuid service
373 QBluetoothUuid serviceUuid128(QString("c47774c7-f237-4523-8968-e4ae75431daf"));
374 QBluetoothUuid charUuid128(QString("c0ad61b1-79e7-42f9-ace0-0a9aa0d0a4f8"));
375 QScopedPointer<QLowEnergyService> customService128(
376 m_leController->createServiceObject(service: serviceUuid128));
377 QVERIFY(!customService128.isNull());
378 customService128->discoverDetails();
379 while (customService128->state() != QLowEnergyService::ServiceDiscovered) {
380 spy.reset(other: new QSignalSpy(customService128.data(), &QLowEnergyService::stateChanged));
381 QVERIFY(spy->wait(5000));
382 }
383 QCOMPARE(customService128->serviceUuid(), serviceUuid128);
384 QCOMPARE(customService128->includedServices().count(), 0);
385 QCOMPARE(customService128->characteristics().count(), 1);
386 QLowEnergyCharacteristic customChar128
387 = customService128->characteristic(uuid: charUuid128);
388 QVERIFY(customChar128.isValid());
389 QCOMPARE(customChar128.descriptors().count(), 0);
390 QCOMPARE(customChar128.value(), QByteArray(15, 'a'));
391
392 QScopedPointer<QLowEnergyService> customService(
393 m_leController->createServiceObject(service: QBluetoothUuid(quint16(0x2000))));
394 QVERIFY(!customService.isNull());
395 customService->discoverDetails();
396 while (customService->state() != QLowEnergyService::ServiceDiscovered) {
397 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
398 QVERIFY(spy->wait(5000));
399 }
400 QCOMPARE(customService->includedServices().count(), 0);
401 QCOMPARE(customService->characteristics().count(), 5);
402 QLowEnergyCharacteristic customChar
403 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5000)));
404 QVERIFY(customChar.isValid());
405 QCOMPARE(customChar.descriptors().count(), 0);
406 QCOMPARE(customChar.value(), QByteArray(1024, 'x'));
407
408 QLowEnergyCharacteristic customChar2
409 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5001)));
410 QVERIFY(customChar2.isValid());
411 QCOMPARE(customChar2.descriptors().count(), 0);
412 QCOMPARE(customChar2.value(), QByteArray()); // Was not readable due to authorization requirement.
413
414 QLowEnergyCharacteristic customChar3
415 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5002)));
416 QVERIFY(customChar3.isValid());
417 QCOMPARE(customChar3.properties(),
418 QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate);
419 QCOMPARE(customChar3.descriptors().count(), 1);
420 QLowEnergyDescriptor cc3ClientConfig
421 = customChar3.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
422 QVERIFY(cc3ClientConfig.isValid());
423
424 QLowEnergyCharacteristic customChar4
425 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5003)));
426 QVERIFY(customChar4.isValid());
427 QCOMPARE(customChar4.properties(),
428 QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
429 QCOMPARE(customChar4.descriptors().count(), 1);
430 QLowEnergyDescriptor cc4ClientConfig
431 = customChar4.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
432 QVERIFY(cc4ClientConfig.isValid());
433
434 QLowEnergyCharacteristic customChar5
435 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5004)));
436 QVERIFY(customChar5.isValid());
437 QCOMPARE(customChar5.properties(),
438 QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned);
439 QCOMPARE(customChar5.descriptors().count(), 0);
440 QCOMPARE(customChar5.value(), QByteArray("initial"));
441
442 customService->writeCharacteristic(characteristic: customChar, newValue: "whatever");
443 spy.reset(other: new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
444 (QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
445 QVERIFY(spy->wait(3000));
446 QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
447
448 spy.reset(other: new QSignalSpy(customService.data(), static_cast<void (QLowEnergyService::*)
449 (QLowEnergyService::ServiceError)>(&QLowEnergyService::error)));
450 customService->writeCharacteristic(characteristic: customChar5, newValue: "1", mode: QLowEnergyService::WriteSigned);
451
452 // error might happen immediately or once event loop comes back
453 bool wasError = ((spy->count() > 0) || spy->wait(timeout: 3000)); //
454
455 if (!wasError) {
456 // Signed write is done twice to test the sign counter stuff.
457 // Note: We assume here that the link is not encrypted, as this information is not exported.
458 customService->readCharacteristic(characteristic: customChar5);
459 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
460 QVERIFY(spy->wait(3000));
461 QCOMPARE(customChar5.value(), QByteArray("1"));
462 customService->writeCharacteristic(characteristic: customChar5, newValue: "2", mode: QLowEnergyService::WriteSigned);
463 customService->readCharacteristic(characteristic: customChar5);
464 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
465 QVERIFY(spy->wait(3000));
466 QCOMPARE(customChar5.value(), QByteArray("2"));
467 } else {
468 QCOMPARE(customService->error(), QLowEnergyService::CharacteristicWriteError);
469 }
470
471 QByteArray indicateValue(2, 0);
472 qToLittleEndian<quint16>(src: 2, dest: reinterpret_cast<uchar *>(indicateValue.data()));
473 customService->writeDescriptor(descriptor: cc3ClientConfig, newValue: indicateValue);
474 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
475 QVERIFY(spy->wait(3000));
476
477 QByteArray notifyValue(2, 0);
478 qToLittleEndian<quint16>(src: 1, dest: reinterpret_cast<uchar *>(notifyValue.data()));
479 customService->writeDescriptor(descriptor: cc4ClientConfig, newValue: notifyValue);
480 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::descriptorWritten));
481 QVERIFY(spy->wait(3000));
482
483 // Server now changes the characteristic values.
484
485 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::characteristicChanged));
486 QVERIFY(spy->wait(3000));
487 if (spy->count() == 1)
488 QVERIFY(spy->wait(3000));
489 QCOMPARE(customChar3.value().constData(), "indicated");
490 QCOMPARE(customChar4.value().constData(), "notified");
491
492 // signal requires root privileges on Linux
493 spy.reset(other: new QSignalSpy(m_leController.data(), &QLowEnergyController::connectionUpdated));
494 QVERIFY(spy->wait(5000));
495
496 m_leController->disconnectFromDevice();
497
498 if (m_leController->state() != QLowEnergyController::UnconnectedState) {
499 spy.reset(other: new QSignalSpy(m_leController.data(), &QLowEnergyController::stateChanged));
500 QVERIFY(spy->wait(3000));
501 }
502 QCOMPARE(m_leController->state(), QLowEnergyController::UnconnectedState);
503
504 // Server now changes the characteristic values again while we're offline.
505 // Note: We cannot test indications and notifications for this case, as the client does
506 // not cache the old information and thus does not yet know the characteristics
507 // at the time the notification/indication is received.
508
509 QTest::qWait(ms: 3000);
510 m_leController->connectToDevice();
511 spy.reset(other: new QSignalSpy(m_leController.data(), &QLowEnergyController::connected));
512 QVERIFY(spy->wait(30000));
513 m_leController->discoverServices();
514 spy.reset(other: new QSignalSpy(m_leController.data(), &QLowEnergyController::discoveryFinished));
515 QVERIFY(spy->wait(30000));
516 customService.reset(other: m_leController->createServiceObject(service: QBluetoothUuid(quint16(0x2000))));
517 QVERIFY(!customService.isNull());
518 customService->discoverDetails();
519 while (customService->state() != QLowEnergyService::ServiceDiscovered) {
520 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::stateChanged));
521 QVERIFY(spy->wait(5000));
522 }
523 customChar3 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5002)));
524 QVERIFY(customChar3.isValid());
525 QCOMPARE(customChar3.value().constData(), "indicated2");
526 customChar4 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5003)));
527 QVERIFY(customChar4.isValid());
528 QCOMPARE(customChar4.value().constData(), "notified2");
529 cc3ClientConfig = customChar3.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
530 QVERIFY(cc3ClientConfig.isValid());
531 cc4ClientConfig = customChar4.descriptor(uuid: QBluetoothUuid::ClientCharacteristicConfiguration);
532 QVERIFY(cc4ClientConfig.isValid());
533
534 const bool isBonded = QBluetoothLocalDevice().pairingStatus(address: m_serverAddress)
535 != QBluetoothLocalDevice::Unpaired;
536 if (isBonded) {
537 QCOMPARE(cc3ClientConfig.value(), indicateValue);
538 QCOMPARE(cc4ClientConfig.value(), notifyValue);
539
540 // Do a third signed write to test sign counter persistence.
541 customChar5 = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5004)));
542 QVERIFY(customChar5.isValid());
543 QCOMPARE(customChar5.value(), QByteArray("2"));
544 customService->writeCharacteristic(characteristic: customChar5, newValue: "3", mode: QLowEnergyService::WriteSigned);
545 customService->readCharacteristic(characteristic: customChar5);
546 spy.reset(other: new QSignalSpy(customService.data(), &QLowEnergyService::characteristicRead));
547 QVERIFY(spy->wait(3000));
548 QCOMPARE(customChar5.value(), QByteArray("3"));
549
550 } else {
551 QCOMPARE(cc3ClientConfig.value(), QByteArray(2, 0));
552 QCOMPARE(cc4ClientConfig.value(), QByteArray(2, 0));
553 }
554}
555
556void TestQLowEnergyControllerGattServer::controllerType()
557{
558 const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
559 QVERIFY(!controller.isNull());
560 QCOMPARE(controller->role(), QLowEnergyController::PeripheralRole);
561}
562
563void TestQLowEnergyControllerGattServer::serviceData()
564{
565 QLowEnergyDescriptorData descData;
566 QVERIFY(!descData.isValid());
567
568 descData.setUuid(QBluetoothUuid::ValidRange);
569 QCOMPARE(descData.uuid(), QBluetoothUuid(QBluetoothUuid::ValidRange));
570 QVERIFY(descData.isValid());
571 QVERIFY(descData != QLowEnergyDescriptorData());
572
573 descData.setValue("xyz");
574 QCOMPARE(descData.value().constData(), "xyz");
575
576 descData.setReadPermissions(readable: true, constraints: AttAuthenticationRequired);
577 QCOMPARE(descData.isReadable(), true);
578 QCOMPARE(descData.readConstraints(), AttAuthenticationRequired);
579
580 descData.setWritePermissions(writable: false);
581 QCOMPARE(descData.isWritable(), false);
582
583 QLowEnergyDescriptorData descData2(QBluetoothUuid::ReportReference, "abc");
584 QVERIFY(descData2 != QLowEnergyDescriptorData());
585 QVERIFY(descData2.isValid());
586 QCOMPARE(descData2.uuid(), QBluetoothUuid(QBluetoothUuid::ReportReference));
587 QCOMPARE(descData2.value().constData(), "abc");
588
589 QLowEnergyCharacteristicData charData;
590 QVERIFY(!charData.isValid());
591
592 charData.setUuid(QBluetoothUuid::BatteryLevel);
593 QVERIFY(charData != QLowEnergyCharacteristicData());
594 QCOMPARE(charData.uuid(), QBluetoothUuid(QBluetoothUuid::BatteryLevel));
595 QVERIFY(charData.isValid());
596
597 charData.setValue("value");
598 QCOMPARE(charData.value().constData(), "value");
599
600 charData.setValueLength(minimum: 4, maximum: 7);
601 QCOMPARE(charData.minimumValueLength(), 4);
602 QCOMPARE(charData.maximumValueLength(), 7);
603 charData.setValueLength(minimum: 5, maximum: 2);
604 QCOMPARE(charData.minimumValueLength(), 5);
605 QCOMPARE(charData.maximumValueLength(), 5);
606
607 const QLowEnergyCharacteristic::PropertyTypes props
608 = QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned;
609 charData.setProperties(props);
610 QCOMPARE(charData.properties(), props);
611
612 charData.setReadConstraints(AttEncryptionRequired);
613 QCOMPARE(charData.readConstraints(), AttEncryptionRequired);
614 charData.setWriteConstraints(AttAuthenticationRequired | AttAuthorizationRequired);
615 QCOMPARE(charData.writeConstraints(), AttAuthenticationRequired | AttAuthorizationRequired);
616
617 charData.addDescriptor(descriptor: descData);
618 QCOMPARE(charData.descriptors().count(), 1);
619 charData.setDescriptors(QList<QLowEnergyDescriptorData>());
620 QCOMPARE(charData.descriptors().count(), 0);
621 charData.setDescriptors(QList<QLowEnergyDescriptorData>() << descData << descData2);
622 QLowEnergyDescriptorData descData3(QBluetoothUuid::ExternalReportReference, "someval");
623 charData.addDescriptor(descriptor: descData3);
624 charData.addDescriptor(descriptor: QLowEnergyDescriptorData()); // Invalid.
625 QCOMPARE(charData.descriptors(),
626 QList<QLowEnergyDescriptorData>() << descData << descData2 << descData3);
627
628 QLowEnergyServiceData secondaryData;
629 QVERIFY(!secondaryData.isValid());
630
631 secondaryData.setUuid(QBluetoothUuid::SerialPort);
632 QCOMPARE(secondaryData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort));
633 QVERIFY(secondaryData.isValid());
634 QVERIFY(secondaryData != QLowEnergyServiceData());
635
636 secondaryData.setType(QLowEnergyServiceData::ServiceTypeSecondary);
637 QCOMPARE(secondaryData.type(), QLowEnergyServiceData::ServiceTypeSecondary);
638
639 secondaryData.addCharacteristic(characteristic: charData);
640 QCOMPARE(secondaryData.characteristics().count(), 1);
641 secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>());
642 QCOMPARE(secondaryData.characteristics().count(), 0);
643 secondaryData.setCharacteristics(QList<QLowEnergyCharacteristicData>()
644 << charData << QLowEnergyCharacteristicData());
645 QCOMPARE(secondaryData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData);
646
647 QLowEnergyServiceData backupData;
648 backupData.setUuid(QBluetoothUuid::SerialPort);
649 QCOMPARE(backupData.uuid(), QBluetoothUuid(QBluetoothUuid::SerialPort));
650 QVERIFY(backupData.isValid());
651 QVERIFY(backupData != QLowEnergyServiceData());
652
653 backupData.setType(QLowEnergyServiceData::ServiceTypeSecondary);
654 QCOMPARE(backupData.type(), QLowEnergyServiceData::ServiceTypeSecondary);
655
656 backupData.setCharacteristics(QList<QLowEnergyCharacteristicData>()
657 << charData << QLowEnergyCharacteristicData());
658 QCOMPARE(backupData.characteristics(), QList<QLowEnergyCharacteristicData>() << charData);
659 QVERIFY(backupData == secondaryData);
660
661#ifdef Q_OS_DARWIN
662 QSKIP("GATT server functionality not implemented for Apple platforms");
663#endif
664 const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral());
665 QVERIFY(!controller->addService(QLowEnergyServiceData()));
666 const QScopedPointer<QLowEnergyService> secondaryService(controller->addService(service: secondaryData));
667 QVERIFY(!secondaryService.isNull());
668 QCOMPARE(secondaryService->serviceUuid(), secondaryData.uuid());
669 const QList<QLowEnergyCharacteristic> characteristics = secondaryService->characteristics();
670 QCOMPARE(characteristics.count(), 1);
671 QCOMPARE(characteristics.first().uuid(), charData.uuid());
672 const QList<QLowEnergyDescriptor> descriptors = characteristics.first().descriptors();
673 QCOMPARE(descriptors.count(), 3);
674 const auto inUuids = QSet<QBluetoothUuid>() << descData.uuid() << descData2.uuid()
675 << descData3.uuid();
676 QSet<QBluetoothUuid> outUuids;
677 for (const QLowEnergyDescriptor &desc : descriptors)
678 outUuids << desc.uuid();
679 QCOMPARE(inUuids, outUuids);
680
681 QLowEnergyServiceData primaryData;
682 primaryData.setUuid(QBluetoothUuid::Headset);
683 primaryData.addIncludedService(service: secondaryService.data());
684 const QScopedPointer<QLowEnergyService> primaryService(controller->addService(service: primaryData));
685 QVERIFY(!primaryService.isNull());
686 QCOMPARE(primaryService->characteristics().count(), 0);
687 const QList<QBluetoothUuid> includedServices = primaryService->includedServices();
688 QCOMPARE(includedServices.count(), 1);
689 QCOMPARE(includedServices.first(), secondaryService->serviceUuid());
690}
691
692QTEST_MAIN(TestQLowEnergyControllerGattServer)
693
694#include "tst_qlowenergycontroller-gattserver.moc"
695

source code of qtconnectivity/tests/auto/qlowenergycontroller-gattserver/test/tst_qlowenergycontroller-gattserver.cpp