| 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 |  | 
| 53 | using namespace QBluetooth; | 
| 54 |  | 
| 55 | class TestQLowEnergyControllerGattServer : public QObject | 
| 56 | { | 
| 57 |     Q_OBJECT | 
| 58 |  | 
| 59 | private 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 |  | 
| 75 | private: | 
| 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 |  | 
| 86 | void 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 |  | 
| 96 | void 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 |  | 
| 130 | void 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 |  | 
| 166 | void 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 |  | 
| 196 | bool 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 |  | 
| 229 | void 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 |  | 
| 248 | void 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 |  | 
| 270 | void 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 |  | 
| 295 | void 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 |  | 
| 556 | void TestQLowEnergyControllerGattServer::controllerType() | 
| 557 | { | 
| 558 |     const QScopedPointer<QLowEnergyController> controller(QLowEnergyController::createPeripheral()); | 
| 559 |     QVERIFY(!controller.isNull()); | 
| 560 |     QCOMPARE(controller->role(), QLowEnergyController::PeripheralRole); | 
| 561 | } | 
| 562 |  | 
| 563 | void 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 |  | 
| 692 | QTEST_MAIN(TestQLowEnergyControllerGattServer) | 
| 693 |  | 
| 694 | #include "tst_qlowenergycontroller-gattserver.moc" | 
| 695 |  |