Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlowenergycontroller_bluez.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// Copyright (C) 2016 Javier S. Pedro <maemo@javispedro.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
11#include "bluez/bluez_data_p.h"
12#include "bluez/hcimanager_p.h"
13#include "bluez/objectmanager_p.h"
14#include "bluez/remotedevicemanager_p.h"
15#include "bluez/bluez5_helper_p.h"
16#include "bluez/bluetoothmanagement_p.h"
17
18#include <QtCore/QFileInfo>
19#include <QtCore/QLoggingCategory>
20#include <QtCore/QSettings>
21#include <QtCore/QTimer>
22#include <QtBluetooth/QBluetoothLocalDevice>
23#include <QtBluetooth/QBluetoothSocket>
24#include <QtBluetooth/QLowEnergyCharacteristicData>
25#include <QtBluetooth/QLowEnergyDescriptorData>
26#include <QtBluetooth/QLowEnergyService>
27#include <QtBluetooth/QLowEnergyServiceData>
28
29#include <algorithm>
30#include <climits>
31#include <cstring>
32#include <errno.h>
33#include <sys/types.h>
34#include <sys/socket.h>
35#include <unistd.h>
36
38constexpr quint16 ATT_MAX_LE_MTU = 0x200;
39
40#define GATT_PRIMARY_SERVICE quint16(0x2800)
41#define GATT_SECONDARY_SERVICE quint16(0x2801)
42#define GATT_INCLUDED_SERVICE quint16(0x2802)
43#define GATT_CHARACTERISTIC quint16(0x2803)
44
45//GATT command sizes in bytes
46#define ERROR_RESPONSE_HEADER_SIZE 5
47#define FIND_INFO_REQUEST_HEADER_SIZE 5
48#define GRP_TYPE_REQ_HEADER_SIZE 7
49#define READ_BY_TYPE_REQ_HEADER_SIZE 7
50#define READ_REQUEST_HEADER_SIZE 3
51#define READ_BLOB_REQUEST_HEADER_SIZE 5
52#define WRITE_REQUEST_HEADER_SIZE 3 // same size for WRITE_COMMAND header
53#define PREPARE_WRITE_HEADER_SIZE 5
54#define EXECUTE_WRITE_HEADER_SIZE 2
55#define MTU_EXCHANGE_HEADER_SIZE 3
56
57#define APPEND_VALUE true
58#define NEW_VALUE false
59
60QT_BEGIN_NAMESPACE
61
62Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
63
64using namespace QBluetooth;
65using namespace QtBluetoothPrivate; // for D-Bus wrappers
66
67const int maxPrepareQueueSize = 1024;
68
69/* returns false if the format is incorrect */
70static bool dumpErrorInformation(const QByteArray &response)
71{
72 const char *data = response.constData();
73 if (response.size() != 5
74 || (static_cast<QBluezConst::AttCommand>(data[0])
76 qCWarning(QT_BT_BLUEZ) << QLatin1String("Not a valid error response");
77 return false;
78 }
79
80 QBluezConst::AttCommand lastCommand = static_cast<QBluezConst::AttCommand>(data[1]);
81 quint16 handle = bt_get_le16(&data[2]);
82 QBluezConst::AttError errorCode = static_cast<QBluezConst::AttError>(data[4]);
83
84 QString errorString;
85 switch (errorCode) {
87 errorString = QStringLiteral("invalid handle"); break;
89 errorString = QStringLiteral("not readable attribute - permissions"); break;
91 errorString = QStringLiteral("not writable attribute - permissions"); break;
93 errorString = QStringLiteral("PDU invalid"); break;
95 errorString = QStringLiteral("needs authentication - permissions"); break;
97 errorString = QStringLiteral("server does not support request"); break;
99 errorString = QStringLiteral("offset past end of attribute"); break;
101 errorString = QStringLiteral("need authorization - permissions"); break;
103 errorString = QStringLiteral("run out of prepare queue space"); break;
105 errorString = QStringLiteral("no attribute in given range found"); break;
107 errorString = QStringLiteral("attribute not read/written using read blob"); break;
109 errorString = QStringLiteral("need encryption key size - permissions"); break;
111 errorString = QStringLiteral("written value is invalid size"); break;
113 errorString = QStringLiteral("unlikely error"); break;
115 errorString = QStringLiteral("needs encryption - permissions"); break;
117 errorString = QStringLiteral("unsupported group type"); break;
119 errorString = QStringLiteral("insufficient resources to complete request"); break;
120 default:
123 errorString =
124 QStringLiteral("application error: %1").arg(static_cast<quint8>(errorCode));
125 else
126 errorString = QStringLiteral("unknown error code");
127 break;
128 }
129
130 qCDebug(QT_BT_BLUEZ) << "Error:" << errorCode << "Error description:" << errorString
131 << "last command:" << lastCommand << "handle:" << handle;
132
133 return true;
134}
135
136static int getUuidSize(const QBluetoothUuid &uuid)
137{
138 return uuid.minimumSize() == 2 ? 2 : 16;
139}
140
141template<typename T> static void putDataAndIncrement(const T &src, char *&dst)
142{
143 putBtData(src, dst);
144 dst += sizeof(T);
145}
146template<> void putDataAndIncrement(const QBluetoothUuid &uuid, char *&dst)
147{
148 bool ok;
149 quint16 uuid16 = uuid.toUInt16(&ok);
150 if (ok) {
151 putBtData(uuid16, dst);
152 dst += sizeof(uuid16);
153 } else {
154 QUuid::Id128Bytes btOrder = uuid.toBytes(QSysInfo::LittleEndian);
155 memcpy(dst, btOrder.data, sizeof(btOrder));
156 dst += sizeof(btOrder);
157 }
158}
159template<> void putDataAndIncrement(const QByteArray &value, char *&dst)
160{
161 using namespace std;
162 memcpy(dst, value.constData(), value.size());
163 dst += value.size();
164}
165
166/*!
167 \class QLowEnergyControllerPrivateBluez
168 \inmodule QtBluetooth
169 \internal
170*/
171QLowEnergyControllerPrivateBluez::QLowEnergyControllerPrivateBluez()
173 requestPending(false),
174 mtuSize(ATT_DEFAULT_LE_MTU),
175 securityLevelValue(-1),
176 encryptionChangePending(false)
177{
179 qRegisterMetaType<QList<QLowEnergyHandle> >();
180}
181
182void QLowEnergyControllerPrivateBluez::init()
183{
184 // The HCI manager is shared between this class and the advertiser
185 hciManager = std::make_shared<HciManager>(localAdapter);
186
187 if (!hciManager->isValid()){
188 setError(QLowEnergyController::InvalidBluetoothAdapterError);
189 return;
190 }
191
192 hciManager->monitorEvent(HciManager::HciEvent::EVT_ENCRYPT_CHANGE);
193 connect(hciManager.get(), SIGNAL(encryptionChangedEvent(QBluetoothAddress,bool)),
194 this, SLOT(encryptionChangedEvent(QBluetoothAddress,bool)));
195 hciManager->monitorEvent(HciManager::HciEvent::EVT_LE_META_EVENT);
196 hciManager->monitorAclPackets();
197 connect(hciManager.get(), &HciManager::connectionComplete, this, [this](quint16 handle) {
198 connectionHandle = handle;
199 qCDebug(QT_BT_BLUEZ) << "received connection complete event, handle:" << handle;
200 });
201 connect(hciManager.get(), &HciManager::connectionUpdate, this,
202 [this](quint16 handle, const QLowEnergyConnectionParameters &params) {
203 if (handle == connectionHandle)
204 emit q_ptr->connectionUpdated(params);
205 }
206 );
207 connect(hciManager.get(), &HciManager::signatureResolvingKeyReceived, this,
208 [this](quint16 handle, bool remoteKey, const QUuid::Id128Bytes &csrk) {
209 if (handle != connectionHandle)
210 return;
211 if ((remoteKey && role == QLowEnergyController::CentralRole)
212 || (!remoteKey && role == QLowEnergyController::PeripheralRole)) {
213 return;
214 }
215 qCDebug(QT_BT_BLUEZ) << "received new signature resolving key";
216 signingData.insert(remoteDevice.toUInt64(), SigningData(csrk));
217 }
218 );
219
220 if (role == QLowEnergyController::CentralRole) {
221 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("BLUETOOTH_GATT_TIMEOUT"))) {
222 bool ok = false;
223 int value = qEnvironmentVariableIntValue("BLUETOOTH_GATT_TIMEOUT", &ok);
224 if (ok)
225 gattRequestTimeout = value;
226 }
227
228 // permit disabling of timeout behavior via environment variable
229 if (gattRequestTimeout > 0) {
230 qCWarning(QT_BT_BLUEZ) << "Enabling GATT request timeout behavior" << gattRequestTimeout;
231 requestTimer = new QTimer(this);
232 requestTimer->setSingleShot(true);
233 requestTimer->setInterval(gattRequestTimeout);
234 connect(requestTimer, &QTimer::timeout,
235 this, &QLowEnergyControllerPrivateBluez::handleGattRequestTimeout);
236 }
237 }
238}
239
240void QLowEnergyControllerPrivateBluez::handleGattRequestTimeout()
241{
242 // antyhing open that might require cancellation or a warning?
243 if (encryptionChangePending) {
244 // We cannot really recover for now but the warning is essential for debugging
245 qCWarning(QT_BT_BLUEZ) << "****** Encryption change event blocking further GATT requests";
246 return;
247 }
248
249 if (!openRequests.isEmpty() && requestPending) {
250 const Request currentRequest = openRequests.dequeue();
251 requestPending = false; // reset pending flag
252
253 qCWarning(QT_BT_BLUEZ).nospace() << "****** Request type 0x" << currentRequest.command
254 << " to server/peripheral timed out";
255 qCWarning(QT_BT_BLUEZ) << "****** Looks like the characteristic or descriptor does NOT act in"
256 << "accordance to Bluetooth 4.x spec.";
257 qCWarning(QT_BT_BLUEZ) << "****** Please check server implementation."
258 << "Continuing under reservation.";
259
260 QBluezConst::AttCommand command = currentRequest.command;
261 const auto createRequestErrorMessage = [](QBluezConst::AttCommand opcodeWithError,
262 QLowEnergyHandle handle) {
263 QByteArray errorPackage(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
264 errorPackage[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_ERROR_RESPONSE);
265 errorPackage[1] = static_cast<quint8>(
266 opcodeWithError); // e.g. QBluezConst::AttCommand::ATT_OP_READ_REQUEST
267 putBtData(handle, errorPackage.data() + 2); //
268 errorPackage[4] = static_cast<quint8>(QBluezConst::AttError::ATT_ERROR_REQUEST_STALLED);
269
270 return errorPackage;
271 };
272
273 switch (command) {
275 // never received reply to MTU request
276 // it is safe to skip and go to next request
277 break;
278 case QBluezConst::AttCommand::ATT_OP_READ_BY_GROUP_REQUEST: // primary or secondary service
279 // discovery
280 case QBluezConst::AttCommand::ATT_OP_READ_BY_TYPE_REQUEST: // characteristic or included
281 // service discovery
282 // jump back into usual response handling with custom error code
283 // 2nd param "0" as required by spec
284 processReply(currentRequest, createRequestErrorMessage(command, 0));
285 break;
286 case QBluezConst::AttCommand::ATT_OP_READ_REQUEST: // read descriptor or characteristic
287 // value
288 case QBluezConst::AttCommand::ATT_OP_READ_BLOB_REQUEST: // read long descriptor or
289 // characteristic
290 case QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST: // write descriptor or characteristic
291 {
292 uint handleData = currentRequest.reference.toUInt();
293 const QLowEnergyHandle charHandle = (handleData & 0xffff);
294 const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
295 processReply(currentRequest, createRequestErrorMessage(command,
296 descriptorHandle ? descriptorHandle : charHandle));
297 } break;
298 case QBluezConst::AttCommand::ATT_OP_FIND_INFORMATION_REQUEST: // get descriptor information
299 processReply(currentRequest, createRequestErrorMessage(
300 command, currentRequest.reference2.toUInt()));
301 break;
302 case QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_REQUEST: // prepare to write long desc or
303 // char
304 case QBluezConst::AttCommand::ATT_OP_EXECUTE_WRITE_REQUEST: // execute long write of desc or
305 // char
306 {
307 uint handleData = currentRequest.reference.toUInt();
308 const QLowEnergyHandle attrHandle = (handleData & 0xffff);
309 processReply(currentRequest,
310 createRequestErrorMessage(command, attrHandle));
311 } break;
312 default:
313 // not a command used by central role implementation
314 qCWarning(QT_BT_BLUEZ) << "Missing response for ATT peripheral command: "
315 << Qt::hex << command;
316 break;
317 }
318
319 // spin openRequest queue further
320 sendNextPendingRequest();
321 }
322}
323
324QLowEnergyControllerPrivateBluez::~QLowEnergyControllerPrivateBluez()
325{
326 closeServerSocket();
327 delete cmacCalculator;
328 cmacCalculator = nullptr;
329}
330
332{
333public:
334 bool listen(const QBluetoothAddress &localAdapter)
335 {
336 m_socket = ::socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
337 if (m_socket == -1) {
338 qCWarning(QT_BT_BLUEZ) << "socket creation failed:" << qt_error_string(errno);
339 return false;
340 }
341 sockaddr_l2 addr;
342
343 // memset should be in std namespace for C++ compilers, but we also need to support
344 // broken ones that put it in the global one.
345 using namespace std;
346 memset(&addr, 0, sizeof addr);
347
348 addr.l2_family = AF_BLUETOOTH;
350 addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
351 convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b);
352 if (::bind(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof addr) == -1) {
353 qCWarning(QT_BT_BLUEZ) << "bind() failed:" << qt_error_string(errno);
354 return false;
355 }
356 if (::listen(m_socket, 1)) {
357 qCWarning(QT_BT_BLUEZ) << "listen() failed:" << qt_error_string(errno);
358 return false;
359 }
360 return true;
361 }
362
364 {
365 if (m_socket != -1)
366 close(m_socket);
367 }
368
370 {
371 const int socket = m_socket;
372 m_socket = -1;
373 return socket;
374 }
375
376private:
377 int m_socket = -1;
378};
379
380
381void QLowEnergyControllerPrivateBluez::startAdvertising(const QLowEnergyAdvertisingParameters &params,
382 const QLowEnergyAdvertisingData &advertisingData,
383 const QLowEnergyAdvertisingData &scanResponseData)
384{
385 qCDebug(QT_BT_BLUEZ) << "Starting to advertise";
386 if (!advertiser) {
387 advertiser = new QLeAdvertiserBluez(params, advertisingData, scanResponseData, hciManager,
388 this);
389 connect(advertiser, &QLeAdvertiser::errorOccurred, this,
390 &QLowEnergyControllerPrivateBluez::handleAdvertisingError);
391 }
392 setState(QLowEnergyController::AdvertisingState);
393 advertiser->startAdvertising();
394 if (params.mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd
395 || params.mode() == QLowEnergyAdvertisingParameters::AdvScanInd) {
396 qCDebug(QT_BT_BLUEZ) << "Non-connectable advertising requested, "
397 "not listening for connections.";
398 return;
399 }
400
401 ServerSocket serverSocket;
402 if (!serverSocket.listen(localAdapter)) {
403 setError(QLowEnergyController::AdvertisingError);
404 setState(QLowEnergyController::UnconnectedState);
405 return;
406 }
407
408 const int socketFd = serverSocket.takeSocket();
409 serverSocketNotifier = new QSocketNotifier(socketFd, QSocketNotifier::Read, this);
410 connect(serverSocketNotifier, &QSocketNotifier::activated, this,
411 &QLowEnergyControllerPrivateBluez::handleConnectionRequest);
412}
413
414void QLowEnergyControllerPrivateBluez::stopAdvertising()
415{
416 setState(QLowEnergyController::UnconnectedState);
417 advertiser->stopAdvertising();
418}
419
420void QLowEnergyControllerPrivateBluez::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
421{
422 // The spec says that the connection update command can be used by both slave and master
423 // devices, but BlueZ allows it only for master devices. So for slave devices, we have to use a
424 // connection parameter update request, which we need to wrap in an ACL command, as BlueZ
425 // does not allow user-space sockets for the signaling channel.
426 if (role == QLowEnergyController::CentralRole)
427 hciManager->sendConnectionUpdateCommand(connectionHandle, params);
428 else
429 hciManager->sendConnectionParameterUpdateRequest(connectionHandle, params);
430}
431
432void QLowEnergyControllerPrivateBluez::connectToDevice()
433{
434 if (remoteDevice.isNull()) {
435 qCWarning(QT_BT_BLUEZ) << "Invalid/null remote device address";
436 setError(QLowEnergyController::UnknownRemoteDeviceError);
437 return;
438 }
439
440 setState(QLowEnergyController::ConnectingState);
441 if (l2cpSocket) {
442 delete l2cpSocket;
443 l2cpSocket = nullptr;
444 }
445
446 createServicesForCentralIfRequired();
447
448 // check for active running connections
449 // BlueZ 5.37+ (maybe even earlier versions) can have pending BTLE connections
450 // Only one active L2CP socket to CID 0x4 possible at a time
451
452 const QList<quint16> activeHandles = hciManager->activeLowEnergyConnections();
453 if (!activeHandles.isEmpty()) {
454 qCWarning(QT_BT_BLUEZ) << "Cannot connect due to pending active LE connections";
455
456 if (!device1Manager) {
457 device1Manager = new RemoteDeviceManager(localAdapter, this);
458 connect(device1Manager, &RemoteDeviceManager::finished,
459 this, &QLowEnergyControllerPrivateBluez::activeConnectionTerminationDone);
460 }
461
462 QList<QBluetoothAddress> connectedAddresses;
463 for (const auto handle: activeHandles) {
464 const QBluetoothAddress addr = hciManager->addressForConnectionHandle(handle);
465 if (!addr.isNull())
466 connectedAddresses.push_back(addr);
467 }
468 device1Manager->scheduleJob(RemoteDeviceManager::JobType::JobDisconnectDevice, connectedAddresses);
469 } else {
470 establishL2cpClientSocket();
471 }
472}
473
474/*!
475 * Handles outcome of attempts to close external connections.
476 */
477void QLowEnergyControllerPrivateBluez::activeConnectionTerminationDone()
478{
479 if (!device1Manager)
480 return;
481
482 qCDebug(QT_BT_BLUEZ) << "RemoteDeviceManager finished attempting"
483 << "to close external connections";
484
485 QList<quint16> activeHandles = hciManager->activeLowEnergyConnections();
486 if (!activeHandles.isEmpty()) {
487 qCWarning(QT_BT_BLUEZ) << "Cannot close pending external BTLE connections. Aborting connect attempt";
488 setError(QLowEnergyController::ConnectionError);
489 setState(QLowEnergyController::UnconnectedState);
490 l2cpDisconnected();
491 return;
492 } else {
493 establishL2cpClientSocket();
494 }
495}
496
497/*!
498 * Establishes the L2CP client socket.
499 */
500void QLowEnergyControllerPrivateBluez::establishL2cpClientSocket()
501{
502 //we are already in Connecting state
503
504 l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this);
505 connect(l2cpSocket, SIGNAL(connected()), this, SLOT(l2cpConnected()));
506 connect(l2cpSocket, SIGNAL(disconnected()), this, SLOT(l2cpDisconnected()));
507 connect(l2cpSocket, SIGNAL(errorOccurred(QBluetoothSocket::SocketError)), this,
508 SLOT(l2cpErrorChanged(QBluetoothSocket::SocketError)));
509 connect(l2cpSocket, SIGNAL(readyRead()), this, SLOT(l2cpReadyRead()));
510
511 quint32 addressTypeToUse = (addressType == QLowEnergyController::PublicAddress)
514 // if monitoring is possible and it's private then we force it to the relevant option
515 if (BluetoothManagement::instance()->isAddressRandom(remoteDevice)) {
516 addressTypeToUse = BDADDR_LE_RANDOM;
517 }
518 }
519
520 qCDebug(QT_BT_BLUEZ) << "addresstypeToUse:"
521 << (addressTypeToUse == BDADDR_LE_RANDOM
522 ? QStringLiteral("Random") : QStringLiteral("Public"));
523
524 l2cpSocket->d_ptr->lowEnergySocketType = addressTypeToUse;
525
526 int sockfd = l2cpSocket->socketDescriptor();
527 if (sockfd < 0) {
528 qCWarning(QT_BT_BLUEZ) << "l2cp socket not initialised";
529 setError(QLowEnergyController::ConnectionError);
530 setState(QLowEnergyController::UnconnectedState);
531 return;
532 }
533
534 struct sockaddr_l2 addr;
535 memset(&addr, 0, sizeof(addr));
536 addr.l2_family = AF_BLUETOOTH;
538 addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
539 convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b);
540
541 // bind the socket to the local device
542 if (::bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
543 qCWarning(QT_BT_BLUEZ) << qt_error_string(errno);
544 setError(QLowEnergyController::ConnectionError);
545 setState(QLowEnergyController::UnconnectedState);
546 return;
547 }
548
549 // connect
550 // Unbuffered mode required to separate each GATT packet
551 l2cpSocket->connectToService(remoteDevice, ATTRIBUTE_CHANNEL_ID,
552 QIODevice::ReadWrite | QIODevice::Unbuffered);
553 loadSigningDataIfNecessary(LocalSigningKey);
554}
555
556void QLowEnergyControllerPrivateBluez::createServicesForCentralIfRequired()
557{
558 bool ok = false;
559 int value = qEnvironmentVariableIntValue("QT_DEFAULT_CENTRAL_SERVICES", &ok);
560 if (Q_UNLIKELY(ok && value == 0))
561 return; //nothing to do
562
563 //do not add the services each time we start a connection
564 if (localServices.contains(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::GenericAccess)))
565 return;
566
567 qCDebug(QT_BT_BLUEZ) << "Creating default GAP/GATT services";
568
569 //populate Generic Access service
570 //for now the values are static
571 QLowEnergyServiceData gapServiceData;
572 gapServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
573 gapServiceData.setUuid(QBluetoothUuid::ServiceClassUuid::GenericAccess);
574
575 QLowEnergyCharacteristicData gapDeviceName;
576 gapDeviceName.setUuid(QBluetoothUuid::CharacteristicType::DeviceName);
577 gapDeviceName.setProperties(QLowEnergyCharacteristic::Read);
578
579 QBluetoothLocalDevice mainAdapter;
580 gapDeviceName.setValue(mainAdapter.name().toLatin1()); //static name
581
582 QLowEnergyCharacteristicData gapAppearance;
583 gapAppearance.setUuid(QBluetoothUuid::CharacteristicType::Appearance);
584 gapAppearance.setProperties(QLowEnergyCharacteristic::Read);
585 gapAppearance.setValue(QByteArray::fromHex("80")); // Generic Computer (0x80)
586
587 QLowEnergyCharacteristicData gapPrivacyFlag;
588 gapPrivacyFlag.setUuid(QBluetoothUuid::CharacteristicType::PeripheralPrivacyFlag);
589 gapPrivacyFlag.setProperties(QLowEnergyCharacteristic::Read);
590 gapPrivacyFlag.setValue(QByteArray::fromHex("00")); // disable privacy
591
592 gapServiceData.addCharacteristic(gapDeviceName);
593 gapServiceData.addCharacteristic(gapAppearance);
594 gapServiceData.addCharacteristic(gapPrivacyFlag);
595
596 Q_Q(QLowEnergyController);
597 QLowEnergyService *service = addServiceHelper(gapServiceData);
598 if (service)
599 service->setParent(q);
600
601 QLowEnergyServiceData gattServiceData;
602 gattServiceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
603 gattServiceData.setUuid(QBluetoothUuid::ServiceClassUuid::GenericAttribute);
604
605 QLowEnergyCharacteristicData serviceChangedChar;
606 serviceChangedChar.setUuid(QBluetoothUuid::CharacteristicType::ServiceChanged);
607 serviceChangedChar.setProperties(QLowEnergyCharacteristic::Indicate);
608 //arbitrary range of 2 bit handle range (1-4
609 serviceChangedChar.setValue(QByteArray::fromHex("0104"));
610
611 const QLowEnergyDescriptorData clientConfig(
612 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
613 QByteArray(2, 0));
614 serviceChangedChar.addDescriptor(clientConfig);
615 gattServiceData.addCharacteristic(serviceChangedChar);
616
617 service = addServiceHelper(gattServiceData);
618 if (service)
619 service->setParent(q);
620}
621
622void QLowEnergyControllerPrivateBluez::l2cpConnected()
623{
624 Q_Q(QLowEnergyController);
625
626 securityLevelValue = securityLevel();
627 exchangeMTU();
628
629 setState(QLowEnergyController::ConnectedState);
630 emit q->connected();
631}
632
633void QLowEnergyControllerPrivateBluez::disconnectFromDevice()
634{
635 setState(QLowEnergyController::ClosingState);
636 if (l2cpSocket)
637 l2cpSocket->close();
638 resetController();
639
640 // this may happen when RemoteDeviceManager::JobType::JobDisconnectDevice
641 // is pending.
642 if (!l2cpSocket) {
643 qWarning(QT_BT_BLUEZ) << "Unexpected closure of device. Cleaning up internal states.";
644 l2cpDisconnected();
645 }
646}
647
648void QLowEnergyControllerPrivateBluez::l2cpDisconnected()
649{
650 Q_Q(QLowEnergyController);
651
652 if (role == QLowEnergyController::PeripheralRole) {
653 storeClientConfigurations();
654 remoteDevice.clear();
655 remoteName.clear();
656 }
658 resetController();
659 setState(QLowEnergyController::UnconnectedState);
660 emit q->disconnected();
661}
662
663void QLowEnergyControllerPrivateBluez::l2cpErrorChanged(QBluetoothSocket::SocketError e)
664{
665 switch (e) {
666 case QBluetoothSocket::SocketError::HostNotFoundError:
667 setError(QLowEnergyController::UnknownRemoteDeviceError);
668 qCDebug(QT_BT_BLUEZ) << "The passed remote device address cannot be found";
669 break;
670 case QBluetoothSocket::SocketError::NetworkError:
671 setError(QLowEnergyController::NetworkError);
672 qCDebug(QT_BT_BLUEZ) << "Network IO error while talking to LE device";
673 break;
674 case QBluetoothSocket::SocketError::RemoteHostClosedError:
675 setError(QLowEnergyController::RemoteHostClosedError);
676 qCDebug(QT_BT_BLUEZ) << "Remote host closed the connection";
677 break;
678 case QBluetoothSocket::SocketError::UnknownSocketError:
679 case QBluetoothSocket::SocketError::UnsupportedProtocolError:
680 case QBluetoothSocket::SocketError::OperationError:
681 case QBluetoothSocket::SocketError::ServiceNotFoundError:
682 default:
683 // these errors shouldn't happen -> as it means
684 // the code in this file has bugs
685 qCDebug(QT_BT_BLUEZ) << "Unknown l2cp socket error: " << e << l2cpSocket->errorString();
686 setError(QLowEnergyController::UnknownError);
687 break;
688 }
689
691 resetController();
692 setState(QLowEnergyController::UnconnectedState);
693}
694
695
696void QLowEnergyControllerPrivateBluez::resetController()
697{
698 openRequests.clear();
699 openPrepareWriteRequests.clear();
700 scheduledIndications.clear();
701 indicationInFlight = false;
702 requestPending = false;
703 encryptionChangePending = false;
704 receivedMtuExchangeRequest = false;
705 mtuSize = ATT_DEFAULT_LE_MTU;
706 securityLevelValue = -1;
707 connectionHandle = 0;
708
709 if (role == QLowEnergyController::PeripheralRole) {
710 // public API behavior requires stop of advertisement
711 if (advertiser) {
712 advertiser->stopAdvertising();
713 delete advertiser;
714 advertiser = nullptr;
715 }
716 localAttributes.clear();
717 }
718}
719
720void QLowEnergyControllerPrivateBluez::restartRequestTimer()
721{
722 if (!requestTimer)
723 return;
724
725 if (gattRequestTimeout > 0)
726 requestTimer->start(gattRequestTimeout);
727}
728
729void QLowEnergyControllerPrivateBluez::l2cpReadyRead()
730{
731 const QByteArray incomingPacket = l2cpSocket->readAll();
732 qCDebug(QT_BT_BLUEZ) << "Received size:" << incomingPacket.size() << "data:"
733 << incomingPacket.toHex();
734 if (incomingPacket.isEmpty())
735 return;
736
737 const QBluezConst::AttCommand command =
738 static_cast<QBluezConst::AttCommand>(incomingPacket.constData()[0]);
739 switch (command) {
741 processUnsolicitedReply(incomingPacket);
742 return;
743 }
745 //send confirmation
746 QByteArray packet;
747 packet.append(static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_HANDLE_VAL_CONFIRMATION));
748 sendPacket(packet);
749
750 processUnsolicitedReply(incomingPacket);
751 return;
752 }
753 //--------------------------------------------------
754 // Peripheral side packet handling
755 case QBluezConst::AttCommand::ATT_OP_EXCHANGE_MTU_REQUEST:
756 handleExchangeMtuRequest(incomingPacket);
757 return;
758 case QBluezConst::AttCommand::ATT_OP_FIND_INFORMATION_REQUEST:
759 handleFindInformationRequest(incomingPacket);
760 return;
761 case QBluezConst::AttCommand::ATT_OP_FIND_BY_TYPE_VALUE_REQUEST:
762 handleFindByTypeValueRequest(incomingPacket);
763 return;
764 case QBluezConst::AttCommand::ATT_OP_READ_BY_TYPE_REQUEST:
765 handleReadByTypeRequest(incomingPacket);
766 return;
767 case QBluezConst::AttCommand::ATT_OP_READ_REQUEST:
768 handleReadRequest(incomingPacket);
769 return;
770 case QBluezConst::AttCommand::ATT_OP_READ_BLOB_REQUEST:
771 handleReadBlobRequest(incomingPacket);
772 return;
773 case QBluezConst::AttCommand::ATT_OP_READ_MULTIPLE_REQUEST:
774 handleReadMultipleRequest(incomingPacket);
775 return;
776 case QBluezConst::AttCommand::ATT_OP_READ_BY_GROUP_REQUEST:
777 handleReadByGroupTypeRequest(incomingPacket);
778 return;
779 case QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST:
780 case QBluezConst::AttCommand::ATT_OP_WRITE_COMMAND:
781 case QBluezConst::AttCommand::ATT_OP_SIGNED_WRITE_COMMAND:
782 handleWriteRequestOrCommand(incomingPacket);
783 return;
784 case QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_REQUEST:
785 handlePrepareWriteRequest(incomingPacket);
786 return;
787 case QBluezConst::AttCommand::ATT_OP_EXECUTE_WRITE_REQUEST:
788 handleExecuteWriteRequest(incomingPacket);
789 return;
791 if (indicationInFlight) {
792 indicationInFlight = false;
793 sendNextIndication();
794 } else {
795 qCWarning(QT_BT_BLUEZ) << "received unexpected handle value confirmation";
796 }
797 return;
798 //--------------------------------------------------
799 default:
800 //only solicited replies finish pending requests
801 requestPending = false;
802 break;
803 }
804
805 if (openRequests.isEmpty()) {
806 qCWarning(QT_BT_BLUEZ) << "Received unexpected packet from peer, disconnecting.";
808 return;
809 }
810
811 const Request request = openRequests.dequeue();
812 processReply(request, incomingPacket);
813
814 sendNextPendingRequest();
815}
816
817/*!
818 * Called when the request for socket encryption has been
819 * processed by the kernel. Such requests take time as the kernel
820 * has to renegotiate the link parameters with the remote device.
821 *
822 * Therefore any such request delays the pending ATT commands until this
823 * callback is called. The first pending request in the queue is the request
824 * that triggered the encryption request.
825 */
826void QLowEnergyControllerPrivateBluez::encryptionChangedEvent(
827 const QBluetoothAddress &address, bool wasSuccess)
828{
829 if (!encryptionChangePending) // somebody else caused change event
830 return;
831
832 if (remoteDevice != address)
833 return;
834
835 securityLevelValue = securityLevel();
836
837 // On success continue to process ATT command queue
838 if (!wasSuccess) {
839 // We could not increase the security of the link
840 // The next request was requeued due to security error
841 // skip it to avoid endless loop of security negotiations
842 Q_ASSERT(!openRequests.isEmpty());
843 Request failedRequest = openRequests.takeFirst();
844
845 if (failedRequest.command == QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST) {
846 // Failing write requests trigger some sort of response
847 uint ref = failedRequest.reference.toUInt();
848 const QLowEnergyHandle charHandle = (ref & 0xffff);
849 const QLowEnergyHandle descriptorHandle = ((ref >> 16) & 0xffff);
850
851 QSharedPointer<QLowEnergyServicePrivate> service
852 = serviceForHandle(charHandle);
853 if (!service.isNull() && service->characteristicList.contains(charHandle)) {
854 if (!descriptorHandle)
855 service->setError(QLowEnergyService::CharacteristicWriteError);
856 else
857 service->setError(QLowEnergyService::DescriptorWriteError);
858 }
859 } else if (failedRequest.command == QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_REQUEST) {
860 uint handleData = failedRequest.reference.toUInt();
861 const QLowEnergyHandle attrHandle = (handleData & 0xffff);
862 const QByteArray newValue = failedRequest.reference2.toByteArray();
863
864 // Prepare command failed, cancel pending prepare queue on
865 // the device. The appropriate (Descriptor|Characteristic)WriteError
866 // is emitted too once the execute write request comes through
867 sendExecuteWriteRequest(attrHandle, newValue, true);
868 }
869 }
870
871 encryptionChangePending = false;
872 sendNextPendingRequest();
873}
874
875void QLowEnergyControllerPrivateBluez::sendPacket(const QByteArray &packet)
876{
877 qint64 result = l2cpSocket->write(packet.constData(),
878 packet.size());
879 // We ignore result == 0 which is likely to be caused by EAGAIN.
880 // This packet is effectively discarded but the controller can still recover
881
882 if (result == -1) {
883 qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP packet:" << Qt::hex
884 << packet.toHex()
885 << l2cpSocket->errorString();
886 setError(QLowEnergyController::NetworkError);
887 } else if (result < packet.size()) {
888 qCWarning(QT_BT_BLUEZ) << "L2CP write request incomplete:"
889 << result << "of" << packet.size();
890 }
891
892}
893
894void QLowEnergyControllerPrivateBluez::sendNextPendingRequest()
895{
896 if (openRequests.isEmpty() || requestPending || encryptionChangePending)
897 return;
898
899 const Request &request = openRequests.head();
900// qCDebug(QT_BT_BLUEZ) << "Sending request, type:" << Qt::hex << request.command
901// << request.payload.toHex();
902
903 requestPending = true;
904 restartRequestTimer();
905 sendPacket(request.payload);
906}
907
910 const char *data, quint16 elementLength)
911{
912 Q_ASSERT(charData);
913 Q_ASSERT(data);
914 Q_ASSERT(elementLength >= 5);
915
916 QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]);
917 charData->properties =
918 (QLowEnergyCharacteristic::PropertyTypes)(data[2] & 0xff);
919 charData->valueHandle = bt_get_le16(&data[3]);
920
921 // Bluetooth LE data comes as little endian
922 if (elementLength == 7) // 16 bit uuid
923 charData->uuid = QBluetoothUuid(bt_get_le16(&data[5]));
924 else if (elementLength == 21) // 128 bit uuid
925 charData->uuid = QUuid::fromBytes(&data[5], QSysInfo::LittleEndian);
926
927 qCDebug(QT_BT_BLUEZ) << "Found handle:" << Qt::hex << attributeHandle
928 << "properties:" << charData->properties
929 << "value handle:" << charData->valueHandle
930 << "uuid:" << charData->uuid.toString();
931
932 return attributeHandle;
933}
934
936 QList<QBluetoothUuid> *foundServices,
937 const char *data, quint16 elementLength)
938{
939 Q_ASSERT(foundServices);
940 Q_ASSERT(data);
941 Q_ASSERT(elementLength >= 6);
942
943 QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]);
944
945 // the next 2 elements are not required as we have discovered
946 // all (primary/secondary) services already. Now we are only
947 // interested in their relationship to each other
948 // data[2] -> included service start handle
949 // data[4] -> included service end handle
950
951 // TODO: Spec v. 5.3, Vol. 3, Part G, 4.5.1 mentions that only
952 // 16-bit UUID can be returned here. If the UUID is 128-bit,
953 // then it is omitted from the response, and should be requested
954 // separately with the ATT_READ_REQ command.
955
956 if (elementLength == 8) //16 bit uuid
957 foundServices->append(QBluetoothUuid(bt_get_le16(&data[6])));
958 else if (elementLength == 22) // 128 bit uuid
959 foundServices->append(QUuid::fromBytes(&data[6], QSysInfo::LittleEndian));
960
961 qCDebug(QT_BT_BLUEZ) << "Found included service: " << Qt::hex
962 << attributeHandle << "uuid:" << *foundServices;
963
964 return attributeHandle;
965}
966
968static void reportMalformedData(QBluezConst::AttCommand cmd, const QByteArray &response)
969{
970 qCDebug(QT_BT_BLUEZ, "%s malformed data: %s", qt_getEnumName(cmd),
971 response.toHex().constData());
972}
973
974void QLowEnergyControllerPrivateBluez::processReply(
975 const Request &request, const QByteArray &response)
976{
977 Q_Q(QLowEnergyController);
978
979 // We already have an isEmpty() check at the only calling site that reads
980 // incoming data, so Q_ASSERT is enough.
981 Q_ASSERT(!response.isEmpty());
982
983 QBluezConst::AttCommand command = static_cast<QBluezConst::AttCommand>(response.constData()[0]);
984
985 bool isErrorResponse = false;
986 // if error occurred 2. byte is previous request type
988 if (!dumpErrorInformation(response))
989 return;
990 command = static_cast<QBluezConst::AttCommand>(response.constData()[1]);
991 isErrorResponse = true;
992 }
993
994 switch (command) {
997 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_EXCHANGE_MTU_REQUEST);
998 quint16 oldMtuSize = mtuSize;
999 if (isErrorResponse) {
1000 mtuSize = ATT_DEFAULT_LE_MTU;
1001 } else {
1002 if (response.size() < 3) {
1003 reportMalformedData(command, response);
1004 break;
1005 }
1006 const char *data = response.constData();
1007 quint16 mtu = bt_get_le16(&data[1]);
1008 mtuSize = mtu;
1009 if (mtuSize < ATT_DEFAULT_LE_MTU)
1010 mtuSize = ATT_DEFAULT_LE_MTU;
1011
1012 qCDebug(QT_BT_BLUEZ) << "Server MTU:" << mtu << "resulting mtu:" << mtuSize;
1013 }
1014 if (oldMtuSize != mtuSize)
1015 emit q->mtuChanged(mtuSize);
1016 } break;
1019 // Discovering services
1020 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_READ_BY_GROUP_REQUEST);
1021
1022 const quint16 type = request.reference.toUInt();
1023
1024 if (isErrorResponse) {
1025 if (type == GATT_SECONDARY_SERVICE) {
1026 setState(QLowEnergyController::DiscoveredState);
1027 q->discoveryFinished();
1028 } else { // search for secondary services
1029 sendReadByGroupRequest(0x0001, 0xFFFF, GATT_SECONDARY_SERVICE);
1030 }
1031 break;
1032 }
1033
1034 // response[1] == elementLength. According to the spec it should be
1035 // at least 4 bytes. See Spec v5.3, Vol 3, Part F, 3.4.4.10
1036 if (response.size() < 2 || response[1] < 4) {
1037 reportMalformedData(command, response);
1038 break;
1039 }
1040
1041 QLowEnergyHandle start = 0, end = 0;
1042 const quint16 elementLength = response.constData()[1]; // value checked above
1043 const quint16 numElements = (response.size() - 2) / elementLength;
1044 quint16 offset = 2;
1045 const char *data = response.constData();
1046 for (int i = 0; i < numElements; i++) {
1047 start = bt_get_le16(&data[offset]);
1048 end = bt_get_le16(&data[offset+2]);
1049
1050 QBluetoothUuid uuid;
1051 if (elementLength == 6) //16 bit uuid
1052 uuid = QBluetoothUuid(bt_get_le16(&data[offset+4]));
1053 else if (elementLength == 20) //128 bit uuid
1054 uuid = QUuid::fromBytes(&data[offset+4], QSysInfo::LittleEndian);
1055 //else -> do nothing
1056
1057 offset += elementLength;
1058
1059
1060 qCDebug(QT_BT_BLUEZ) << "Found uuid:" << uuid << "start handle:" << Qt::hex
1061 << start << "end handle:" << end;
1062
1064 priv->uuid = uuid;
1065 priv->startHandle = start;
1066 priv->endHandle = end;
1067 if (type != GATT_PRIMARY_SERVICE) //unset PrimaryService bit
1068 priv->type &= ~QLowEnergyService::PrimaryService;
1069 priv->setController(this);
1070
1071 QSharedPointer<QLowEnergyServicePrivate> pointer(priv);
1072
1073 serviceList.insert(uuid, pointer);
1074 emit q->serviceDiscovered(uuid);
1075 }
1076
1077 if (end != 0xFFFF) {
1078 sendReadByGroupRequest(end+1, 0xFFFF, type);
1079 } else {
1080 if (type == GATT_SECONDARY_SERVICE) {
1081 setState(QLowEnergyController::DiscoveredState);
1082 emit q->discoveryFinished();
1083 } else { // search for secondary services
1084 sendReadByGroupRequest(0x0001, 0xFFFF, GATT_SECONDARY_SERVICE);
1085 }
1086 }
1087 } break;
1090 // Discovering characteristics
1091 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_READ_BY_TYPE_REQUEST);
1092
1093 QSharedPointer<QLowEnergyServicePrivate> p =
1094 request.reference.value<QSharedPointer<QLowEnergyServicePrivate> >();
1095 const quint16 attributeType = request.reference2.toUInt();
1096
1097 if (isErrorResponse) {
1098 if (attributeType == GATT_CHARACTERISTIC) {
1099 // we reached end of service handle
1100 // just finished up characteristic discovery
1101 // continue with values of characteristics
1102 if (!p->characteristicList.isEmpty()) {
1103 readServiceValues(p->uuid, true);
1104 } else {
1105 // discovery finished since the service doesn't have any
1106 // characteristics
1107 p->setState(QLowEnergyService::RemoteServiceDiscovered);
1108 }
1109 } else if (attributeType == GATT_INCLUDED_SERVICE) {
1110 // finished up include discovery
1111 // continue with characteristic discovery
1112 sendReadByTypeRequest(p, p->startHandle, GATT_CHARACTERISTIC);
1113 }
1114 break;
1115 }
1116
1117 /* packet format:
1118 * if GATT_CHARACTERISTIC discovery (Spec 5.3, Vol. 3, Part G, 4.6)
1119 * <opcode><elementLength>
1120 * [<handle><property><charHandle><uuid>]+
1121 * The minimum elementLength is 7 bytes (uuid is always included)
1122 *
1123 * if GATT_INCLUDE discovery (Spec 5.3, Vol. 3, Part G, 4.5.1)
1124 * <opcode><elementLength>
1125 * [<handle><startHandle_included><endHandle_included><uuid>]+
1126 * The minimum elementLength is 6 bytes (uuid can be omitted).
1127 *
1128 * The uuid can be 16 or 128 bit.
1129 */
1130
1131 const quint8 minimumElementLength = attributeType == GATT_CHARACTERISTIC ? 7 : 6;
1132 if (response.size() < 2 || response[1] < minimumElementLength) {
1133 reportMalformedData(command, response);
1134 break;
1135 }
1136
1137 QLowEnergyHandle lastHandle;
1138 const quint16 elementLength = response.constData()[1];
1139 const quint16 numElements = (response.size() - 2) / elementLength;
1140 quint16 offset = 2;
1141 const char *data = response.constData();
1142 for (int i = 0; i < numElements; i++) {
1143 if (attributeType == GATT_CHARACTERISTIC) {
1144 QLowEnergyServicePrivate::CharData characteristic;
1145 lastHandle = parseReadByTypeCharDiscovery(
1146 &characteristic, &data[offset], elementLength);
1147 p->characteristicList[lastHandle] = characteristic;
1148 offset += elementLength;
1149 } else if (attributeType == GATT_INCLUDED_SERVICE) {
1150 QList<QBluetoothUuid> includedServices;
1151 lastHandle = parseReadByTypeIncludeDiscovery(
1152 &includedServices, &data[offset], elementLength);
1153 p->includedServices = includedServices;
1154 for (const QBluetoothUuid &uuid : std::as_const(includedServices)) {
1155 if (serviceList.contains(uuid))
1156 serviceList[uuid]->type |= QLowEnergyService::IncludedService;
1157 }
1158 }
1159 }
1160
1161 if (lastHandle + 1 < p->endHandle) { // more chars to discover
1162 sendReadByTypeRequest(p, lastHandle + 1, attributeType);
1163 } else {
1164 if (attributeType == GATT_INCLUDED_SERVICE)
1165 sendReadByTypeRequest(p, p->startHandle, GATT_CHARACTERISTIC);
1166 else
1167 readServiceValues(p->uuid, true);
1168 }
1169 } break;
1172 //Reading characteristics and descriptors
1173 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_READ_REQUEST);
1174
1175 uint handleData = request.reference.toUInt();
1176 const QLowEnergyHandle charHandle = (handleData & 0xffff);
1177 const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
1178
1179 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
1180 Q_ASSERT(!service.isNull());
1181 bool isServiceDiscoveryRun
1182 = !(service->state == QLowEnergyService::RemoteServiceDiscovered);
1183
1184 if (isErrorResponse) {
1185 Q_ASSERT(!encryptionChangePending);
1186 QBluezConst::AttError err = static_cast<QBluezConst::AttError>(response.constData()[4]);
1187 encryptionChangePending = increaseEncryptLevelfRequired(err);
1188 if (encryptionChangePending) {
1189 // Just requested a security level change.
1190 // Retry the same command again once the change has happened
1191 openRequests.prepend(request);
1192 break;
1193 } else if (!isServiceDiscoveryRun) {
1194 // not encryption problem -> abort readCharacteristic()/readDescriptor() run
1195 if (!descriptorHandle)
1196 service->setError(QLowEnergyService::CharacteristicReadError);
1197 else
1198 service->setError(QLowEnergyService::DescriptorReadError);
1199 }
1200 } else {
1201 if (!descriptorHandle)
1202 updateValueOfCharacteristic(charHandle, response.mid(1), NEW_VALUE);
1203 else
1204 updateValueOfDescriptor(charHandle, descriptorHandle,
1205 response.mid(1), NEW_VALUE);
1206
1207 if (response.size() == mtuSize) {
1208 qCDebug(QT_BT_BLUEZ) << "Switching to blob reads for"
1209 << charHandle << descriptorHandle
1210 << service->characteristicList[charHandle].uuid.toString();
1211 // Potentially more data -> switch to blob reads
1212 readServiceValuesByOffset(handleData, mtuSize-1,
1213 request.reference2.toBool());
1214 break;
1215 } else if (!isServiceDiscoveryRun) {
1216 // readCharacteristic() or readDescriptor() ongoing
1217 if (!descriptorHandle) {
1218 QLowEnergyCharacteristic ch(service, charHandle);
1219 emit service->characteristicRead(ch, response.mid(1));
1220 } else {
1221 QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle);
1222 emit service->descriptorRead(descriptor, response.mid(1));
1223 }
1224 break;
1225 }
1226 }
1227
1228 if (request.reference2.toBool() && isServiceDiscoveryRun) {
1229 // we only run into this code path during the initial service discovery
1230 // and not when processing readCharacteristics() after service discovery
1231
1232 //last characteristic -> progress to descriptor discovery
1233 //last descriptor -> service discovery is done
1234 if (!descriptorHandle)
1235 discoverServiceDescriptors(service->uuid);
1236 else
1237 service->setState(QLowEnergyService::RemoteServiceDiscovered);
1238 }
1239 } break;
1242 //Reading characteristic or descriptor with value longer value than MTU
1243 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_READ_BLOB_REQUEST);
1244
1245 uint handleData = request.reference.toUInt();
1246 const QLowEnergyHandle charHandle = (handleData & 0xffff);
1247 const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
1248
1249 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
1250 Q_ASSERT(!service.isNull());
1251
1252 /*
1253 * READ_BLOB does not require encryption setup code. BLOB commands
1254 * are only issued after read request if the read request is too long
1255 * for single MTU. The preceding read request would have triggered
1256 * the setup of the encryption already.
1257 */
1258 if (!isErrorResponse) {
1259 quint16 length = 0;
1260 if (!descriptorHandle)
1261 length = updateValueOfCharacteristic(charHandle, response.mid(1), APPEND_VALUE);
1262 else
1263 length = updateValueOfDescriptor(charHandle, descriptorHandle,
1264 response.mid(1), APPEND_VALUE);
1265
1266 if (response.size() == mtuSize) {
1267 readServiceValuesByOffset(handleData, length,
1268 request.reference2.toBool());
1269 break;
1270 } else if (service->state == QLowEnergyService::RemoteServiceDiscovered) {
1271 // readCharacteristic() or readDescriptor() ongoing
1272 if (!descriptorHandle) {
1273 QLowEnergyCharacteristic ch(service, charHandle);
1274 emit service->characteristicRead(ch, ch.value());
1275 } else {
1276 QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle);
1277 emit service->descriptorRead(descriptor, descriptor.value());
1278 }
1279 break;
1280 }
1281 } else {
1282 qWarning() << "READ BLOB for char:" << charHandle
1283 << "descriptor:" << descriptorHandle << "on service"
1284 << service->uuid.toString() << "failed (service discovery run:"
1285 << (service->state == QLowEnergyService::RemoteServiceDiscovered) << ")";
1286 }
1287
1288 if (request.reference2.toBool()) {
1289 //last overlong characteristic -> progress to descriptor discovery
1290 //last overlong descriptor -> service discovery is done
1291
1292 if (!descriptorHandle)
1293 discoverServiceDescriptors(service->uuid);
1294 else
1295 service->setState(QLowEnergyService::RemoteServiceDiscovered);
1296 }
1297
1298 } break;
1301 //Discovering descriptors
1303
1304 /* packet format:
1305 * <opcode><format>[<handle><descriptor_uuid>]+
1306 *
1307 * The uuid can be 16 or 128 bit which is indicated by format.
1308 */
1309
1310 QList<QLowEnergyHandle> keys = request.reference.value<QList<QLowEnergyHandle> >();
1311 if (keys.isEmpty()) {
1312 qCWarning(QT_BT_BLUEZ) << "Descriptor discovery for unknown characteristic received";
1313 break;
1314 }
1315 QLowEnergyHandle charHandle = keys.first();
1316
1317 QSharedPointer<QLowEnergyServicePrivate> p =
1318 serviceForHandle(charHandle);
1319 Q_ASSERT(!p.isNull());
1320
1321 if (isErrorResponse) {
1322 if (keys.size() == 1) {
1323 // no more descriptors to discover
1324 readServiceValues(p->uuid, false); //read descriptor values
1325 } else {
1326 // hop to the next descriptor
1327 keys.removeFirst();
1328 discoverNextDescriptor(p, keys, keys.first());
1329 }
1330 break;
1331 }
1332
1333 // Spec 5.3, Vol. 3, Part F, 3.4.3.2
1334 if (response.size() < 6) {
1335 reportMalformedData(command, response);
1336 break;
1337 }
1338
1339 const quint8 format = response[1];
1340 quint16 elementLength;
1341 switch (format) {
1342 case 0x01:
1343 elementLength = 2 + 2; //sizeof(QLowEnergyHandle) + 16bit uuid
1344 break;
1345 case 0x02:
1346 elementLength = 2 + 16; //sizeof(QLowEnergyHandle) + 128bit uuid
1347 break;
1348 default:
1349 qCWarning(QT_BT_BLUEZ) << "Unknown format in FIND_INFORMATION_RESPONSE";
1350 return;
1351 }
1352
1353 const quint16 numElements = (response.size() - 2) / elementLength;
1354
1355 quint16 offset = 2;
1356 QLowEnergyHandle descriptorHandle {};
1357 QBluetoothUuid uuid;
1358 const char *data = response.constData();
1359 for (int i = 0; i < numElements; i++) {
1360 descriptorHandle = bt_get_le16(&data[offset]);
1361
1362 if (format == 0x01)
1363 uuid = QBluetoothUuid(bt_get_le16(&data[offset+2]));
1364 else if (format == 0x02)
1365 uuid = QUuid::fromBytes(&data[offset+2], QSysInfo::LittleEndian);
1366
1367 offset += elementLength;
1368
1369 // ignore all attributes which are not of type descriptor
1370 // examples are the characteristics value or
1371 bool ok = false;
1372 quint16 shortUuid = uuid.toUInt16(&ok);
1373 if (ok && shortUuid >= QLowEnergyServicePrivate::PrimaryService
1375 qCDebug(QT_BT_BLUEZ) << "Suppressing primary/characteristic" << Qt::hex << shortUuid;
1376 continue;
1377 }
1378
1379 // ignore value handle
1380 if (descriptorHandle == p->characteristicList[charHandle].valueHandle) {
1381 qCDebug(QT_BT_BLUEZ) << "Suppressing char handle" << Qt::hex << descriptorHandle;
1382 continue;
1383 }
1384
1386 data.uuid = uuid;
1387 p->characteristicList[charHandle].descriptorList.insert(
1388 descriptorHandle, data);
1389
1390 qCDebug(QT_BT_BLUEZ) << "Descriptor found, uuid:"
1391 << uuid.toString()
1392 << "descriptor handle:" << Qt::hex << descriptorHandle;
1393 }
1394
1395 const QLowEnergyHandle nextPotentialHandle = descriptorHandle + 1;
1396 if (keys.size() == 1) {
1397 // Reached last characteristic of service
1398
1399 // The endhandle of a service is always the last handle of
1400 // the current service. We must either continue until we have reached
1401 // the starting handle of the next service (endHandle+1) or
1402 // the last physical handle address (0xffff). Note that
1403 // the endHandle of the last service on the device is 0xffff.
1404
1405 if ((p->endHandle != 0xffff && nextPotentialHandle >= p->endHandle + 1)
1406 || (descriptorHandle == 0xffff)) {
1407 keys.removeFirst();
1408 // last descriptor of last characteristic found
1409 // continue with reading descriptor values
1410 readServiceValues(p->uuid, false);
1411 } else {
1412 discoverNextDescriptor(p, keys, nextPotentialHandle);
1413 }
1414 } else {
1415 if (nextPotentialHandle >= keys[1]) //reached next char
1416 keys.removeFirst();
1417 discoverNextDescriptor(p, keys, nextPotentialHandle);
1418 }
1419 } break;
1422 //Write command response
1423 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST);
1424
1425 uint ref = request.reference.toUInt();
1426 const QLowEnergyHandle charHandle = (ref & 0xffff);
1427 const QLowEnergyHandle descriptorHandle = ((ref >> 16) & 0xffff);
1428
1429 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle);
1430 if (service.isNull() || !service->characteristicList.contains(charHandle))
1431 break;
1432
1433 if (isErrorResponse) {
1434 Q_ASSERT(!encryptionChangePending);
1435 QBluezConst::AttError err = static_cast<QBluezConst::AttError>(response.constData()[4]);
1436 encryptionChangePending = increaseEncryptLevelfRequired(err);
1437 if (encryptionChangePending) {
1438 openRequests.prepend(request);
1439 break;
1440 }
1441
1442 if (!descriptorHandle)
1443 service->setError(QLowEnergyService::CharacteristicWriteError);
1444 else
1445 service->setError(QLowEnergyService::DescriptorWriteError);
1446 break;
1447 }
1448
1449 const QByteArray newValue = request.reference2.toByteArray();
1450 if (!descriptorHandle) {
1451 QLowEnergyCharacteristic ch(service, charHandle);
1452 if (ch.properties() & QLowEnergyCharacteristic::Read)
1453 updateValueOfCharacteristic(charHandle, newValue, NEW_VALUE);
1454 emit service->characteristicWritten(ch, newValue);
1455 } else {
1456 updateValueOfDescriptor(charHandle, descriptorHandle, newValue, NEW_VALUE);
1457 QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle);
1458 emit service->descriptorWritten(descriptor, newValue);
1459 }
1460 } break;
1463 //Prepare write command response
1464 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_REQUEST);
1465
1466 uint handleData = request.reference.toUInt();
1467 const QLowEnergyHandle attrHandle = (handleData & 0xffff);
1468 const QByteArray newValue = request.reference2.toByteArray();
1469 const int writtenPayload = ((handleData >> 16) & 0xffff);
1470
1471 if (isErrorResponse) {
1472 Q_ASSERT(!encryptionChangePending);
1473 QBluezConst::AttError err = static_cast<QBluezConst::AttError>(response.constData()[4]);
1474 encryptionChangePending = increaseEncryptLevelfRequired(err);
1475 if (encryptionChangePending) {
1476 openRequests.prepend(request);
1477 break;
1478 }
1479 //emits error on cancellation and aborts existing prepare reuqests
1480 sendExecuteWriteRequest(attrHandle, newValue, true);
1481 } else {
1482 if (writtenPayload < newValue.size()) {
1483 sendNextPrepareWriteRequest(attrHandle, newValue, writtenPayload);
1484 } else {
1485 sendExecuteWriteRequest(attrHandle, newValue, false);
1486 }
1487 }
1488 } break;
1491 // right now used in connection with long characteristic/descriptor value writes
1492 // not catering for reliable writes
1493 Q_ASSERT(request.command == QBluezConst::AttCommand::ATT_OP_EXECUTE_WRITE_REQUEST);
1494
1495 uint handleData = request.reference.toUInt();
1496 const QLowEnergyHandle attrHandle = handleData & 0xffff;
1497 bool wasCancellation = !((handleData >> 16) & 0xffff);
1498 const QByteArray newValue = request.reference2.toByteArray();
1499
1500 // is it a descriptor or characteristic?
1501 const QLowEnergyDescriptor descriptor = descriptorForHandle(attrHandle);
1502 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(attrHandle);
1503 Q_ASSERT(!service.isNull());
1504
1505 if (isErrorResponse || wasCancellation) {
1506 // charHandle == 0 -> cancellation
1507 if (descriptor.isValid())
1508 service->setError(QLowEnergyService::DescriptorWriteError);
1509 else
1510 service->setError(QLowEnergyService::CharacteristicWriteError);
1511 } else {
1512 if (descriptor.isValid()) {
1513 updateValueOfDescriptor(descriptor.characteristicHandle(),
1514 attrHandle, newValue, NEW_VALUE);
1515 emit service->descriptorWritten(descriptor, newValue);
1516 } else {
1517 QLowEnergyCharacteristic ch(service, attrHandle);
1518 if (ch.properties() & QLowEnergyCharacteristic::Read)
1519 updateValueOfCharacteristic(attrHandle, newValue, NEW_VALUE);
1520 emit service->characteristicWritten(ch, newValue);
1521 }
1522 }
1523 } break;
1524 default:
1525 qCDebug(QT_BT_BLUEZ) << "Unknown packet: " << response.toHex();
1526 break;
1527 }
1528}
1529
1530void QLowEnergyControllerPrivateBluez::discoverServices()
1531{
1532 sendReadByGroupRequest(0x0001, 0xFFFF, GATT_PRIMARY_SERVICE);
1533}
1534
1535void QLowEnergyControllerPrivateBluez::sendReadByGroupRequest(
1536 QLowEnergyHandle start, QLowEnergyHandle end, quint16 type)
1537{
1538 //call for primary and secondary services
1539 quint8 packet[GRP_TYPE_REQ_HEADER_SIZE];
1540
1541 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BY_GROUP_REQUEST);
1542 putBtData(start, &packet[1]);
1543 putBtData(end, &packet[3]);
1544 putBtData(type, &packet[5]);
1545
1546 QByteArray data(GRP_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
1547 memcpy(data.data(), packet, GRP_TYPE_REQ_HEADER_SIZE);
1548 qCDebug(QT_BT_BLUEZ) << "Sending read_by_group_type request, startHandle:" << Qt::hex
1549 << start << "endHandle:" << end << type;
1550
1551 Request request;
1552 request.payload = data;
1554 request.reference = type;
1555 openRequests.enqueue(request);
1556
1557 sendNextPendingRequest();
1558}
1559
1560void QLowEnergyControllerPrivateBluez::discoverServiceDetails(const QBluetoothUuid &service,
1561 QLowEnergyService::DiscoveryMode mode)
1562{
1563 if (!serviceList.contains(service)) {
1564 qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString()
1565 << "not possible";
1566 return;
1567 }
1568
1569 QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(service);
1570 serviceData->mode = mode;
1571 serviceData->characteristicList.clear();
1572 sendReadByTypeRequest(serviceData, serviceData->startHandle, GATT_INCLUDED_SERVICE);
1573}
1574
1575void QLowEnergyControllerPrivateBluez::sendReadByTypeRequest(
1576 QSharedPointer<QLowEnergyServicePrivate> serviceData,
1577 QLowEnergyHandle nextHandle, quint16 attributeType)
1578{
1579 quint8 packet[READ_BY_TYPE_REQ_HEADER_SIZE];
1580
1581 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BY_TYPE_REQUEST);
1582 putBtData(nextHandle, &packet[1]);
1583 putBtData(serviceData->endHandle, &packet[3]);
1584 putBtData(attributeType, &packet[5]);
1585
1586 QByteArray data(READ_BY_TYPE_REQ_HEADER_SIZE, Qt::Uninitialized);
1587 memcpy(data.data(), packet, READ_BY_TYPE_REQ_HEADER_SIZE);
1588 qCDebug(QT_BT_BLUEZ) << "Sending read_by_type request, startHandle:" << Qt::hex
1589 << nextHandle << "endHandle:" << serviceData->endHandle
1590 << "type:" << attributeType << "packet:" << data.toHex();
1591
1592 Request request;
1593 request.payload = data;
1595 request.reference = QVariant::fromValue(serviceData);
1596 request.reference2 = attributeType;
1597 openRequests.enqueue(request);
1598
1599 sendNextPendingRequest();
1600}
1601
1602/*!
1603 \internal
1604
1605 Reads all values of specific characteristic and descriptor. This function is
1606 used during the initial service discovery process.
1607
1608 \a readCharacteristics determines whether we intend to read a characteristic;
1609 otherwise we read a descriptor.
1610 */
1611void QLowEnergyControllerPrivateBluez::readServiceValues(
1612 const QBluetoothUuid &serviceUuid, bool readCharacteristics)
1613{
1614 quint8 packet[READ_REQUEST_HEADER_SIZE];
1615 if (QT_BT_BLUEZ().isDebugEnabled()) {
1616 if (readCharacteristics)
1617 qCDebug(QT_BT_BLUEZ) << "Reading all characteristic values for"
1618 << serviceUuid.toString();
1619 else
1620 qCDebug(QT_BT_BLUEZ) << "Reading all descriptor values for"
1621 << serviceUuid.toString();
1622 }
1623
1624 QSharedPointer<QLowEnergyServicePrivate> service = serviceList.value(serviceUuid);
1625
1626 if (service->mode == QLowEnergyService::SkipValueDiscovery) {
1627 if (readCharacteristics) {
1628 // -> continue with descriptor discovery
1629 discoverServiceDescriptors(service->uuid);
1630 } else {
1631 service->setState(QLowEnergyService::RemoteServiceDiscovered);
1632 }
1633 return;
1634 }
1635
1636 // pair.first -> target attribute
1637 // pair.second -> context information for read request
1638 QPair<QLowEnergyHandle, quint32> pair;
1639
1640 // Create list of attribute handles which need to be read
1641 QList<QPair<QLowEnergyHandle, quint32> > targetHandles;
1642
1643 CharacteristicDataMap::const_iterator charIt = service->characteristicList.constBegin();
1644 for ( ; charIt != service->characteristicList.constEnd(); ++charIt) {
1645 const QLowEnergyHandle charHandle = charIt.key();
1646 const QLowEnergyServicePrivate::CharData &charDetails = charIt.value();
1647
1648 if (readCharacteristics) {
1649 // Collect handles of all characteristic value attributes
1650
1651 // Don't try to read writeOnly characteristic
1652 if (!(charDetails.properties & QLowEnergyCharacteristic::Read))
1653 continue;
1654
1655 pair.first = charDetails.valueHandle;
1656 pair.second = charHandle;
1657 targetHandles.append(pair);
1658
1659 } else {
1660 // Collect handles of all descriptor attributes
1661 DescriptorDataMap::const_iterator descIt = charDetails.descriptorList.constBegin();
1662 for ( ; descIt != charDetails.descriptorList.constEnd(); ++descIt) {
1663 const QLowEnergyHandle descriptorHandle = descIt.key();
1664
1665 pair.first = descriptorHandle;
1666 pair.second = (charHandle | (descriptorHandle << 16));
1667 targetHandles.append(pair);
1668 }
1669 }
1670 }
1671
1672
1673 if (targetHandles.isEmpty()) {
1674 if (readCharacteristics) {
1675 // none of the characteristics is readable
1676 // -> continue with descriptor discovery
1677 discoverServiceDescriptors(service->uuid);
1678 } else {
1679 // characteristic w/o descriptors
1680 service->setState(QLowEnergyService::RemoteServiceDiscovered);
1681 }
1682 return;
1683 }
1684
1685 for (qsizetype i = 0; i < targetHandles.size(); i++) {
1686 pair = targetHandles.at(i);
1687 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_REQUEST);
1688 putBtData(pair.first, &packet[1]);
1689
1690 QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
1691 memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
1692
1693 Request request;
1694 request.payload = data;
1696 request.reference = pair.second;
1697 // last entry?
1698 request.reference2 = QVariant((bool)(i + 1 == targetHandles.size()));
1699 openRequests.enqueue(request);
1700 }
1701
1702 sendNextPendingRequest();
1703}
1704
1705/*!
1706 \internal
1707
1708 This function is used when reading a handle value that is
1709 longer than the mtuSize.
1710
1711 The BLOB read request is prepended to the list of
1712 open requests to finish the current value read up before
1713 starting the next read request.
1714 */
1715void QLowEnergyControllerPrivateBluez::readServiceValuesByOffset(
1716 uint handleData, quint16 offset, bool isLastValue)
1717{
1718 const QLowEnergyHandle charHandle = (handleData & 0xffff);
1719 const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff);
1720
1721 QByteArray data(READ_BLOB_REQUEST_HEADER_SIZE, Qt::Uninitialized);
1722 data[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BLOB_REQUEST);
1723
1724 QLowEnergyHandle handleToRead = charHandle;
1725 if (descriptorHandle) {
1726 handleToRead = descriptorHandle;
1727 qCDebug(QT_BT_BLUEZ) << "Reading descriptor via blob request"
1728 << Qt::hex << descriptorHandle;
1729 } else {
1730 //charHandle is not the char's value handle
1731 QSharedPointer<QLowEnergyServicePrivate> service =
1732 serviceForHandle(charHandle);
1733 if (!service.isNull()
1734 && service->characteristicList.contains(charHandle)) {
1735 handleToRead = service->characteristicList[charHandle].valueHandle;
1736 qCDebug(QT_BT_BLUEZ) << "Reading characteristic via blob request"
1737 << Qt::hex << handleToRead;
1738 } else {
1739 Q_ASSERT(false);
1740 }
1741 }
1742
1743 putBtData(handleToRead, data.data() + 1);
1744 putBtData(offset, data.data() + 3);
1745
1746 Request request;
1747 request.payload = data;
1749 request.reference = handleData;
1750 request.reference2 = isLastValue;
1751 openRequests.prepend(request);
1752}
1753
1754void QLowEnergyControllerPrivateBluez::discoverServiceDescriptors(
1755 const QBluetoothUuid &serviceUuid)
1756{
1757 qCDebug(QT_BT_BLUEZ) << "Discovering descriptor values for"
1758 << serviceUuid.toString();
1759 QSharedPointer<QLowEnergyServicePrivate> service = serviceList.value(serviceUuid);
1760
1761 if (service->characteristicList.isEmpty()) { // service has no characteristics
1762 // implies that characteristic & descriptor discovery can be skipped
1763 service->setState(QLowEnergyService::RemoteServiceDiscovered);
1764 return;
1765 }
1766
1767 // start handle of all known characteristics
1768 QList<QLowEnergyHandle> keys = service->characteristicList.keys();
1769 std::sort(keys.begin(), keys.end());
1770
1771 discoverNextDescriptor(service, keys, keys[0]);
1772}
1773
1774void QLowEnergyControllerPrivateBluez::processUnsolicitedReply(const QByteArray &payload)
1775{
1776 Q_ASSERT(!payload.isEmpty());
1777
1778 const char *data = payload.constData();
1779 const auto command = static_cast<QBluezConst::AttCommand>(data[0]);
1780 bool isNotification = (command
1782
1783 if (payload.size() < 3) {
1784 reportMalformedData(command, payload);
1785 return;
1786 }
1787
1788 const QLowEnergyHandle changedHandle = bt_get_le16(&data[1]);
1789
1790 if (QT_BT_BLUEZ().isDebugEnabled()) {
1791 if (isNotification)
1792 qCDebug(QT_BT_BLUEZ) << "Change notification for handle" << Qt::hex << changedHandle;
1793 else
1794 qCDebug(QT_BT_BLUEZ) << "Change indication for handle" << Qt::hex << changedHandle;
1795 }
1796
1797 const QLowEnergyCharacteristic ch = characteristicForHandle(changedHandle);
1798 if (ch.isValid() && ch.handle() == changedHandle) {
1799 if (ch.properties() & QLowEnergyCharacteristic::Read)
1800 updateValueOfCharacteristic(ch.attributeHandle(), payload.mid(3), NEW_VALUE);
1801 emit ch.d_ptr->characteristicChanged(ch, payload.mid(3));
1802 } else {
1803 qCWarning(QT_BT_BLUEZ) << "Cannot find matching characteristic for "
1804 "notification/indication";
1805 }
1806}
1807
1808void QLowEnergyControllerPrivateBluez::exchangeMTU()
1809{
1810 qCDebug(QT_BT_BLUEZ) << "Exchanging MTU";
1811
1812 quint8 packet[MTU_EXCHANGE_HEADER_SIZE];
1813 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_EXCHANGE_MTU_REQUEST);
1814 putBtData(ATT_MAX_LE_MTU, &packet[1]);
1815
1816 QByteArray data(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized);
1817 memcpy(data.data(), packet, MTU_EXCHANGE_HEADER_SIZE);
1818
1819 Request request;
1820 request.payload = data;
1822 openRequests.enqueue(request);
1823
1824 sendNextPendingRequest();
1825}
1826
1827int QLowEnergyControllerPrivateBluez::securityLevel() const
1828{
1829 int socket = l2cpSocket->socketDescriptor();
1830 if (socket < 0) {
1831 qCWarning(QT_BT_BLUEZ) << "Invalid l2cp socket, aborting getting of sec level";
1832 return -1;
1833 }
1834
1835 struct bt_security secData;
1836 socklen_t length = sizeof(secData);
1837 memset(&secData, 0, length);
1838
1839 if (getsockopt(socket, SOL_BLUETOOTH, BT_SECURITY, &secData, &length) == 0) {
1840 qCDebug(QT_BT_BLUEZ) << "Current l2cp sec level:" << secData.level;
1841 return secData.level;
1842 }
1843
1844 if (errno != ENOPROTOOPT) //older kernel, fall back to L2CAP_LM option
1845 return -1;
1846
1847 // cater for older kernels
1848 int optval;
1849 length = sizeof(optval);
1850 if (getsockopt(socket, SOL_L2CAP, L2CAP_LM, &optval, &length) == 0) {
1851 int level = BT_SECURITY_SDP;
1852 if (optval & L2CAP_LM_AUTH)
1853 level = BT_SECURITY_LOW;
1854 if (optval & L2CAP_LM_ENCRYPT)
1855 level = BT_SECURITY_MEDIUM;
1856 if (optval & L2CAP_LM_SECURE)
1857 level = BT_SECURITY_HIGH;
1858
1859 qCDebug(QT_BT_BLUEZ) << "Current l2cp sec level (old):" << level;
1860 return level;
1861 }
1862
1863 return -1;
1864}
1865
1866bool QLowEnergyControllerPrivateBluez::setSecurityLevel(int level)
1867{
1868 if (level > BT_SECURITY_HIGH || level < BT_SECURITY_LOW)
1869 return false;
1870
1871 int socket = l2cpSocket->socketDescriptor();
1872 if (socket < 0) {
1873 qCWarning(QT_BT_BLUEZ) << "Invalid l2cp socket, aborting setting of sec level";
1874 return false;
1875 }
1876
1877 struct bt_security secData;
1878 socklen_t length = sizeof(secData);
1879 memset(&secData, 0, length);
1880 secData.level = level;
1881
1882 if (setsockopt(socket, SOL_BLUETOOTH, BT_SECURITY, &secData, length) == 0) {
1883 qCDebug(QT_BT_BLUEZ) << "Setting new l2cp sec level:" << secData.level;
1884 return true;
1885 }
1886
1887 if (errno != ENOPROTOOPT) //older kernel
1888 return false;
1889
1890 int optval = 0;
1891 switch (level) { // fall through intendeds
1892 case BT_SECURITY_HIGH:
1893 optval |= L2CAP_LM_SECURE;
1894 Q_FALLTHROUGH();
1895 case BT_SECURITY_MEDIUM:
1896 optval |= L2CAP_LM_ENCRYPT;
1897 Q_FALLTHROUGH();
1898 case BT_SECURITY_LOW:
1899 optval |= L2CAP_LM_AUTH;
1900 break;
1901 default:
1902 return false;
1903 }
1904
1905 if (setsockopt(socket, SOL_L2CAP, L2CAP_LM, &optval, sizeof(optval)) == 0) {
1906 qCDebug(QT_BT_BLUEZ) << "Old l2cp sec level:" << optval;
1907 return true;
1908 }
1909
1910 return false;
1911}
1912
1913void QLowEnergyControllerPrivateBluez::discoverNextDescriptor(
1914 QSharedPointer<QLowEnergyServicePrivate> serviceData,
1915 const QList<QLowEnergyHandle> pendingCharHandles,
1916 const QLowEnergyHandle startingHandle)
1917{
1918 Q_ASSERT(!pendingCharHandles.isEmpty());
1919 Q_ASSERT(!serviceData.isNull());
1920
1921 qCDebug(QT_BT_BLUEZ) << "Sending find_info request" << Qt::hex
1922 << pendingCharHandles << startingHandle;
1923
1924 quint8 packet[FIND_INFO_REQUEST_HEADER_SIZE];
1925 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_FIND_INFORMATION_REQUEST);
1926
1927 const QLowEnergyHandle charStartHandle = startingHandle;
1928 QLowEnergyHandle charEndHandle = 0;
1929 if (pendingCharHandles.size() == 1) //single characteristic
1930 charEndHandle = serviceData->endHandle;
1931 else
1932 charEndHandle = pendingCharHandles[1] - 1;
1933
1934 putBtData(charStartHandle, &packet[1]);
1935 putBtData(charEndHandle, &packet[3]);
1936
1937 QByteArray data(FIND_INFO_REQUEST_HEADER_SIZE, Qt::Uninitialized);
1938 memcpy(data.data(), packet, FIND_INFO_REQUEST_HEADER_SIZE);
1939
1940 Request request;
1941 request.payload = data;
1943 request.reference = QVariant::fromValue<QList<QLowEnergyHandle> >(pendingCharHandles);
1944 request.reference2 = startingHandle;
1945 openRequests.enqueue(request);
1946
1947 sendNextPendingRequest();
1948}
1949
1950void QLowEnergyControllerPrivateBluez::sendNextPrepareWriteRequest(
1951 const QLowEnergyHandle handle, const QByteArray &newValue,
1952 quint16 offset)
1953{
1954 // is it a descriptor or characteristic?
1955 QLowEnergyHandle targetHandle = 0;
1956 const QLowEnergyDescriptor descriptor = descriptorForHandle(handle);
1957 if (descriptor.isValid())
1958 targetHandle = descriptor.handle();
1959 else
1960 targetHandle = characteristicForHandle(handle).handle();
1961
1962 if (!targetHandle) {
1963 qCWarning(QT_BT_BLUEZ) << "sendNextPrepareWriteRequest cancelled due to invalid handle"
1964 << handle;
1965 return;
1966 }
1967
1968 quint8 packet[PREPARE_WRITE_HEADER_SIZE];
1969 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_REQUEST);
1970 putBtData(targetHandle, &packet[1]); // attribute handle
1971 putBtData(offset, &packet[3]); // offset into newValue
1972
1973 qCDebug(QT_BT_BLUEZ) << "Writing long characteristic (prepare):"
1974 << Qt::hex << handle;
1975
1976
1977 const qsizetype maxAvailablePayload = qsizetype(mtuSize) - PREPARE_WRITE_HEADER_SIZE;
1978 const qsizetype requiredPayload = (std::min)(newValue.size() - offset, maxAvailablePayload);
1979 const qsizetype dataSize = PREPARE_WRITE_HEADER_SIZE + requiredPayload;
1980
1981 Q_ASSERT((offset + requiredPayload) <= newValue.size());
1982 Q_ASSERT(dataSize <= mtuSize);
1983
1984 QByteArray data(dataSize, Qt::Uninitialized);
1985 memcpy(data.data(), packet, PREPARE_WRITE_HEADER_SIZE);
1986 memcpy(&(data.data()[PREPARE_WRITE_HEADER_SIZE]), &(newValue.constData()[offset]),
1987 requiredPayload);
1988
1989 Request request;
1990 request.payload = data;
1992 request.reference = (handle | ((offset + requiredPayload) << 16));
1993 request.reference2 = newValue;
1994 openRequests.enqueue(request);
1995}
1996
1997/*!
1998 Sends an "Execute Write Request" for a long characteristic or descriptor write.
1999 This cannot be used for executes in relation to reliable write requests.
2000
2001 A cancellation removes all pending prepare write request on the GATT server.
2002 Otherwise this function sends an execute request for all pending prepare
2003 write requests.
2004 */
2005void QLowEnergyControllerPrivateBluez::sendExecuteWriteRequest(
2006 const QLowEnergyHandle attrHandle, const QByteArray &newValue,
2007 bool isCancelation)
2008{
2009 quint8 packet[EXECUTE_WRITE_HEADER_SIZE];
2010 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_EXECUTE_WRITE_REQUEST);
2011 if (isCancelation)
2012 packet[1] = 0x00; // cancel pending write prepare requests
2013 else
2014 packet[1] = 0x01; // execute pending write prepare requests
2015
2016 QByteArray data(EXECUTE_WRITE_HEADER_SIZE, Qt::Uninitialized);
2017 memcpy(data.data(), packet, EXECUTE_WRITE_HEADER_SIZE);
2018
2019 qCDebug(QT_BT_BLUEZ) << "Sending Execute Write Request for long characteristic value"
2020 << Qt::hex << attrHandle;
2021
2022 Request request;
2023 request.payload = data;
2025 request.reference = (attrHandle | ((isCancelation ? 0x00 : 0x01) << 16));
2026 request.reference2 = newValue;
2027 openRequests.prepend(request);
2028}
2029
2030
2031/*!
2032 Writes long (prepare write request), short (write request)
2033 and writeWithoutResponse characteristic values.
2034
2035 TODO Reliable/prepare write across multiple characteristics is not supported
2036 */
2037void QLowEnergyControllerPrivateBluez::writeCharacteristic(
2038 const QSharedPointer<QLowEnergyServicePrivate> service,
2039 const QLowEnergyHandle charHandle,
2040 const QByteArray &newValue,
2041 QLowEnergyService::WriteMode mode)
2042{
2043 Q_ASSERT(!service.isNull());
2044
2045 if (!service->characteristicList.contains(charHandle))
2046 return;
2047
2048 QLowEnergyServicePrivate::CharData &charData = service->characteristicList[charHandle];
2049 if (role == QLowEnergyController::PeripheralRole)
2050 writeCharacteristicForPeripheral(charData, newValue);
2051 else
2052 writeCharacteristicForCentral(service, charHandle, charData.valueHandle, newValue, mode);
2053}
2054
2055void QLowEnergyControllerPrivateBluez::writeDescriptor(
2056 const QSharedPointer<QLowEnergyServicePrivate> service,
2057 const QLowEnergyHandle charHandle,
2058 const QLowEnergyHandle descriptorHandle,
2059 const QByteArray &newValue)
2060{
2061 Q_ASSERT(!service.isNull());
2062
2063 if (role == QLowEnergyController::PeripheralRole)
2064 writeDescriptorForPeripheral(service, charHandle, descriptorHandle, newValue);
2065 else
2066 writeDescriptorForCentral(charHandle, descriptorHandle, newValue);
2067}
2068
2069/*!
2070 \internal
2071
2072 Reads the value of one specific characteristic.
2073 */
2074void QLowEnergyControllerPrivateBluez::readCharacteristic(
2075 const QSharedPointer<QLowEnergyServicePrivate> service,
2076 const QLowEnergyHandle charHandle)
2077{
2078 Q_ASSERT(!service.isNull());
2079 if (!service->characteristicList.contains(charHandle))
2080 return;
2081
2082 const QLowEnergyServicePrivate::CharData &charDetails
2083 = service->characteristicList[charHandle];
2084 if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) {
2085 // if this succeeds the device has a bug, char is advertised as
2086 // non-readable. We try to be permissive and let the remote
2087 // device answer to the read attempt
2088 qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle;
2089 }
2090
2091 quint8 packet[READ_REQUEST_HEADER_SIZE];
2092 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_REQUEST);
2093 putBtData(charDetails.valueHandle, &packet[1]);
2094
2095 QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
2096 memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
2097
2098 qCDebug(QT_BT_BLUEZ) << "Targeted reading characteristic" << Qt::hex << charHandle;
2099
2100 Request request;
2101 request.payload = data;
2103 request.reference = charHandle;
2104 // reference2 not really required but false prevents service discovery
2105 // code from running in QBluezConst::AttCommand::ATT_OP_READ_RESPONSE handler
2106 request.reference2 = false;
2107 openRequests.enqueue(request);
2108
2109 sendNextPendingRequest();
2110}
2111
2112void QLowEnergyControllerPrivateBluez::readDescriptor(
2113 const QSharedPointer<QLowEnergyServicePrivate> service,
2114 const QLowEnergyHandle charHandle,
2115 const QLowEnergyHandle descriptorHandle)
2116{
2117 Q_ASSERT(!service.isNull());
2118 if (!service->characteristicList.contains(charHandle))
2119 return;
2120
2121 const QLowEnergyServicePrivate::CharData &charDetails
2122 = service->characteristicList[charHandle];
2123 if (!charDetails.descriptorList.contains(descriptorHandle))
2124 return;
2125
2126 quint8 packet[READ_REQUEST_HEADER_SIZE];
2127 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_REQUEST);
2128 putBtData(descriptorHandle, &packet[1]);
2129
2130 QByteArray data(READ_REQUEST_HEADER_SIZE, Qt::Uninitialized);
2131 memcpy(data.data(), packet, READ_REQUEST_HEADER_SIZE);
2132
2133 qCDebug(QT_BT_BLUEZ) << "Targeted reading descriptor" << Qt::hex << descriptorHandle;
2134
2135 Request request;
2136 request.payload = data;
2138 request.reference = (charHandle | (descriptorHandle << 16));
2139 // reference2 not really required but false prevents service discovery
2140 // code from running in QBluezConst::AttCommand::ATT_OP_READ_RESPONSE handler
2141 request.reference2 = false;
2142 openRequests.enqueue(request);
2143
2144 sendNextPendingRequest();
2145}
2146
2147/*!
2148 * Returns true if the encryption change was successfully requested.
2149 * The request is triggered if we got a related ATT error.
2150 */
2151bool QLowEnergyControllerPrivateBluez::increaseEncryptLevelfRequired(
2152 QBluezConst::AttError errorCode)
2153{
2154 if (securityLevelValue == BT_SECURITY_HIGH)
2155 return false;
2156
2157 switch (errorCode) {
2161 if (!hciManager->isValid())
2162 return false;
2163 if (!hciManager->monitorEvent(HciManager::HciEvent::EVT_ENCRYPT_CHANGE))
2164 return false;
2165 if (securityLevelValue != BT_SECURITY_HIGH) {
2166 qCDebug(QT_BT_BLUEZ) << "Requesting encrypted link";
2167 if (setSecurityLevel(BT_SECURITY_HIGH)) {
2168 restartRequestTimer();
2169 return true;
2170 }
2171 }
2172 break;
2173 default:
2174 break;
2175 }
2176
2177 return false;
2178}
2179
2180void QLowEnergyControllerPrivateBluez::handleAdvertisingError()
2181{
2182 qCWarning(QT_BT_BLUEZ) << "received advertising error";
2183 setError(QLowEnergyController::AdvertisingError);
2184 setState(QLowEnergyController::UnconnectedState);
2185}
2186
2187bool QLowEnergyControllerPrivateBluez::checkPacketSize(const QByteArray &packet, int minSize,
2188 int maxSize)
2189{
2190 if (maxSize == -1)
2191 maxSize = minSize;
2192 if (Q_LIKELY(packet.size() >= minSize && packet.size() <= maxSize))
2193 return true;
2194 qCWarning(QT_BT_BLUEZ) << "client request of type" << packet.at(0)
2195 << "has unexpected packet size" << packet.size();
2196 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), 0,
2197 QBluezConst::AttError::ATT_ERROR_INVALID_PDU);
2198 return false;
2199}
2200
2201bool QLowEnergyControllerPrivateBluez::checkHandle(const QByteArray &packet, QLowEnergyHandle handle)
2202{
2203 if (handle != 0 && handle <= lastLocalHandle)
2204 return true;
2205 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2206 QBluezConst::AttError::ATT_ERROR_INVALID_HANDLE);
2207 return false;
2208}
2209
2210bool QLowEnergyControllerPrivateBluez::checkHandlePair(QBluezConst::AttCommand request,
2211 QLowEnergyHandle startingHandle,
2212 QLowEnergyHandle endingHandle)
2213{
2214 if (startingHandle == 0 || startingHandle > endingHandle) {
2215 qCDebug(QT_BT_BLUEZ) << "handle range invalid";
2216 sendErrorResponse(request, startingHandle, QBluezConst::AttError::ATT_ERROR_INVALID_HANDLE);
2217 return false;
2218 }
2219 return true;
2220}
2221
2222void QLowEnergyControllerPrivateBluez::handleExchangeMtuRequest(const QByteArray &packet)
2223{
2224 // Spec v4.2, Vol 3, Part F, 3.4.2
2225
2226 if (!checkPacketSize(packet, 3))
2227 return;
2228 if (receivedMtuExchangeRequest) { // Client must only send this once per connection.
2229 qCDebug(QT_BT_BLUEZ) << "Client sent extraneous MTU exchange packet";
2230 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), 0,
2231 QBluezConst::AttError::ATT_ERROR_REQUEST_NOT_SUPPORTED);
2232 return;
2233 }
2234 receivedMtuExchangeRequest = true;
2235
2236 // Send reply.
2237 QByteArray reply(MTU_EXCHANGE_HEADER_SIZE, Qt::Uninitialized);
2238 reply[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_EXCHANGE_MTU_RESPONSE);
2239 putBtData(ATT_MAX_LE_MTU, reply.data() + 1);
2240 sendPacket(reply);
2241
2242 // Apply requested MTU.
2243 const quint16 clientRxMtu = bt_get_le16(packet.constData() + 1);
2244 mtuSize = std::clamp(clientRxMtu, ATT_DEFAULT_LE_MTU, ATT_MAX_LE_MTU);
2245 qCDebug(QT_BT_BLUEZ) << "MTU request from client:" << clientRxMtu
2246 << "effective client RX MTU:" << mtuSize;
2247 qCDebug(QT_BT_BLUEZ) << "Sending server RX MTU" << ATT_MAX_LE_MTU;
2248}
2249
2250void QLowEnergyControllerPrivateBluez::handleFindInformationRequest(const QByteArray &packet)
2251{
2252 // Spec v4.2, Vol 3, Part F, 3.4.3.1-2
2253
2254 if (!checkPacketSize(packet, 5))
2255 return;
2256 const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
2257 const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
2258 qCDebug(QT_BT_BLUEZ) << "client sends find information request; start:" << startingHandle
2259 << "end:" << endingHandle;
2260 if (!checkHandlePair(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2261 endingHandle))
2262 return;
2263
2264 QList<Attribute> results = getAttributes(startingHandle, endingHandle);
2265 if (results.isEmpty()) {
2266 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2267 QBluezConst::AttError::ATT_ERROR_ATTRIBUTE_NOT_FOUND);
2268 return;
2269 }
2270 ensureUniformUuidSizes(results);
2271
2272 QByteArray responsePrefix(2, Qt::Uninitialized);
2273 const int uuidSize = getUuidSize(results.first().type);
2274 responsePrefix[0] =
2276 responsePrefix[1] = uuidSize == 2 ? 0x1 : 0x2;
2277 const int elementSize = sizeof(QLowEnergyHandle) + uuidSize;
2278 const auto elemWriter = [](const Attribute &attr, char *&data) {
2279 putDataAndIncrement(attr.handle, data);
2280 putDataAndIncrement(attr.type, data);
2281 };
2282 sendListResponse(responsePrefix, elementSize, results, elemWriter);
2283
2284}
2285
2286void QLowEnergyControllerPrivateBluez::handleFindByTypeValueRequest(const QByteArray &packet)
2287{
2288 // Spec v4.2, Vol 3, Part F, 3.4.3.3-4
2289
2290 if (!checkPacketSize(packet, 7, mtuSize))
2291 return;
2292 const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
2293 const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
2294 const quint16 type = bt_get_le16(packet.constData() + 5);
2295 const QByteArray value = QByteArray::fromRawData(packet.constData() + 7, packet.size() - 7);
2296 qCDebug(QT_BT_BLUEZ) << "client sends find by type value request; start:" << startingHandle
2297 << "end:" << endingHandle << "type:" << type
2298 << "value:" << value.toHex();
2299 if (!checkHandlePair(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2300 endingHandle))
2301 return;
2302
2303 const auto predicate = [value, this, type](const Attribute &attr) {
2304 return attr.type == QBluetoothUuid(type) && attr.value == value
2305 && checkReadPermissions(attr) == QBluezConst::AttError::ATT_ERROR_NO_ERROR;
2306 };
2307 const QList<Attribute> results = getAttributes(startingHandle, endingHandle, predicate);
2308 if (results.isEmpty()) {
2309 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2310 QBluezConst::AttError::ATT_ERROR_ATTRIBUTE_NOT_FOUND);
2311 return;
2312 }
2313
2314 QByteArray responsePrefix(
2316 const int elemSize = 2 * sizeof(QLowEnergyHandle);
2317 const auto elemWriter = [](const Attribute &attr, char *&data) {
2318 putDataAndIncrement(attr.handle, data);
2319 putDataAndIncrement(attr.groupEndHandle, data);
2320 };
2321 sendListResponse(responsePrefix, elemSize, results, elemWriter);
2322}
2323
2324void QLowEnergyControllerPrivateBluez::handleReadByTypeRequest(const QByteArray &packet)
2325{
2326 // Spec v4.2, Vol 3, Part F, 3.4.4.1-2
2327
2328 if (!checkPacketSize(packet, 7, 21))
2329 return;
2330 const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
2331 const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
2332 const void * const typeStart = packet.constData() + 5;
2333 const bool is16BitUuid = packet.size() == 7;
2334 const bool is128BitUuid = packet.size() == 21;
2335 QBluetoothUuid type;
2336 if (is16BitUuid) {
2337 type = QBluetoothUuid(bt_get_le16(typeStart));
2338 } else if (is128BitUuid) {
2339 type = QUuid::fromBytes(typeStart, QSysInfo::LittleEndian);
2340 } else {
2341 qCWarning(QT_BT_BLUEZ) << "read by type request has invalid packet size" << packet.size();
2342 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), 0,
2343 QBluezConst::AttError::ATT_ERROR_INVALID_PDU);
2344 return;
2345 }
2346 qCDebug(QT_BT_BLUEZ) << "client sends read by type request, start:" << startingHandle
2347 << "end:" << endingHandle << "type:" << type;
2348 if (!checkHandlePair(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2349 endingHandle))
2350 return;
2351
2352 // Get all attributes with matching type.
2353 QList<Attribute> results =
2354 getAttributes(startingHandle, endingHandle,
2355 [type](const Attribute &attr) { return attr.type == type; });
2356 ensureUniformValueSizes(results);
2357
2358 if (results.isEmpty()) {
2359 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2360 QBluezConst::AttError::ATT_ERROR_ATTRIBUTE_NOT_FOUND);
2361 return;
2362 }
2363
2364 const QBluezConst::AttError error = checkReadPermissions(results);
2366 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)),
2367 results.first().handle, error);
2368 return;
2369 }
2370
2371 const qsizetype elementSize = sizeof(QLowEnergyHandle) + results.first().value.size();
2372 QByteArray responsePrefix(2, Qt::Uninitialized);
2373 responsePrefix[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BY_TYPE_RESPONSE);
2374 responsePrefix[1] = elementSize;
2375 const auto elemWriter = [](const Attribute &attr, char *&data) {
2376 putDataAndIncrement(attr.handle, data);
2377 putDataAndIncrement(attr.value, data);
2378 };
2379 sendListResponse(responsePrefix, elementSize, results, elemWriter);
2380}
2381
2382void QLowEnergyControllerPrivateBluez::handleReadRequest(const QByteArray &packet)
2383{
2384 // Spec v4.2, Vol 3, Part F, 3.4.4.3-4
2385
2386 if (!checkPacketSize(packet, 3))
2387 return;
2388 const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
2389 qCDebug(QT_BT_BLUEZ) << "client sends read request; handle:" << handle;
2390
2391 if (!checkHandle(packet, handle))
2392 return;
2393 const Attribute &attribute = localAttributes.at(handle);
2394 const QBluezConst::AttError permissionsError = checkReadPermissions(attribute);
2395 if (permissionsError != QBluezConst::AttError::ATT_ERROR_NO_ERROR) {
2396 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2397 permissionsError);
2398 return;
2399 }
2400
2401 const qsizetype sentValueLength = (std::min)(attribute.value.size(), qsizetype(mtuSize) - 1);
2402 QByteArray response(1 + sentValueLength, Qt::Uninitialized);
2403 response[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_RESPONSE);
2404 using namespace std;
2405 memcpy(response.data() + 1, attribute.value.constData(), sentValueLength);
2406 qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
2407 sendPacket(response);
2408}
2409
2410void QLowEnergyControllerPrivateBluez::handleReadBlobRequest(const QByteArray &packet)
2411{
2412 // Spec v4.2, Vol 3, Part F, 3.4.4.5-6
2413
2414 if (!checkPacketSize(packet, 5))
2415 return;
2416 const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
2417 const quint16 valueOffset = bt_get_le16(packet.constData() + 3);
2418 qCDebug(QT_BT_BLUEZ) << "client sends read blob request; handle:" << handle
2419 << "offset:" << valueOffset;
2420
2421 if (!checkHandle(packet, handle))
2422 return;
2423 const Attribute &attribute = localAttributes.at(handle);
2424 const QBluezConst::AttError permissionsError = checkReadPermissions(attribute);
2425 if (permissionsError != QBluezConst::AttError::ATT_ERROR_NO_ERROR) {
2426 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2427 permissionsError);
2428 return;
2429 }
2430 if (valueOffset > attribute.value.size()) {
2431 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2432 QBluezConst::AttError::ATT_ERROR_INVALID_OFFSET);
2433 return;
2434 }
2435 if (attribute.value.size() <= mtuSize - 3) {
2436 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2437 QBluezConst::AttError::ATT_ERROR_ATTRIBUTE_NOT_LONG);
2438 return;
2439 }
2440
2441 // Yes, this value can be zero.
2442 const qsizetype sentValueLength = (std::min)(attribute.value.size() - valueOffset,
2443 qsizetype(mtuSize) - 1);
2444
2445 QByteArray response(1 + sentValueLength, Qt::Uninitialized);
2446 response[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BLOB_RESPONSE);
2447 using namespace std;
2448 memcpy(response.data() + 1, attribute.value.constData() + valueOffset, sentValueLength);
2449 qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
2450 sendPacket(response);
2451}
2452
2453void QLowEnergyControllerPrivateBluez::handleReadMultipleRequest(const QByteArray &packet)
2454{
2455 // Spec v4.2, Vol 3, Part F, 3.4.4.7-8
2456
2457 if (!checkPacketSize(packet, 5, mtuSize))
2458 return;
2459 QList<QLowEnergyHandle> handles((packet.size() - 1) / sizeof(QLowEnergyHandle));
2460 auto *packetPtr = reinterpret_cast<const QLowEnergyHandle *>(packet.constData() + 1);
2461 for (qsizetype i = 0; i < handles.size(); ++i, ++packetPtr)
2462 handles[i] = bt_get_le16(packetPtr);
2463 qCDebug(QT_BT_BLUEZ) << "client sends read multiple request for handles" << handles;
2464
2465 const auto it = std::find_if(handles.constBegin(), handles.constEnd(),
2466 [this](QLowEnergyHandle handle) { return handle >= lastLocalHandle; });
2467 if (it != handles.constEnd()) {
2468 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), *it,
2469 QBluezConst::AttError::ATT_ERROR_INVALID_HANDLE);
2470 return;
2471 }
2472 const QList<Attribute> results = getAttributes(handles.first(), handles.last());
2473 QByteArray response(
2475 for (const Attribute &attr : results) {
2476 const QBluezConst::AttError error = checkReadPermissions(attr);
2477 if (error != QBluezConst::AttError::ATT_ERROR_NO_ERROR) {
2478 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), attr.handle,
2479 error);
2480 return;
2481 }
2482
2483 // Note: We do not abort if no more values fit into the packet, because we still have to
2484 // report possible permission errors for the other handles.
2485 response += attr.value.left(mtuSize - response.size());
2486 }
2487
2488 qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
2489 sendPacket(response);
2490}
2491
2492void QLowEnergyControllerPrivateBluez::handleReadByGroupTypeRequest(const QByteArray &packet)
2493{
2494 // Spec v4.2, Vol 3, Part F, 3.4.4.9-10
2495
2496 if (!checkPacketSize(packet, 7, 21))
2497 return;
2498 const QLowEnergyHandle startingHandle = bt_get_le16(packet.constData() + 1);
2499 const QLowEnergyHandle endingHandle = bt_get_le16(packet.constData() + 3);
2500 const bool is16BitUuid = packet.size() == 7;
2501 const bool is128BitUuid = packet.size() == 21;
2502 const void * const typeStart = packet.constData() + 5;
2503 QBluetoothUuid type;
2504 if (is16BitUuid) {
2505 type = QBluetoothUuid(bt_get_le16(typeStart));
2506 } else if (is128BitUuid) {
2507 type = QUuid::fromBytes(typeStart, QSysInfo::LittleEndian);
2508 } else {
2509 qCWarning(QT_BT_BLUEZ) << "read by group type request has invalid packet size"
2510 << packet.size();
2511 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), 0,
2512 QBluezConst::AttError::ATT_ERROR_INVALID_PDU);
2513 return;
2514 }
2515 qCDebug(QT_BT_BLUEZ) << "client sends read by group type request, start:" << startingHandle
2516 << "end:" << endingHandle << "type:" << type;
2517
2518 if (!checkHandlePair(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2519 endingHandle))
2520 return;
2521 if (type != QBluetoothUuid(static_cast<quint16>(GATT_PRIMARY_SERVICE))
2522 && type != QBluetoothUuid(static_cast<quint16>(GATT_SECONDARY_SERVICE))) {
2523 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2524 QBluezConst::AttError::ATT_ERROR_UNSUPPRTED_GROUP_TYPE);
2525 return;
2526 }
2527
2528 QList<Attribute> results =
2529 getAttributes(startingHandle, endingHandle,
2530 [type](const Attribute &attr) { return attr.type == type; });
2531 if (results.isEmpty()) {
2532 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), startingHandle,
2533 QBluezConst::AttError::ATT_ERROR_ATTRIBUTE_NOT_FOUND);
2534 return;
2535 }
2536 const QBluezConst::AttError error = checkReadPermissions(results);
2538 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)),
2539 results.first().handle, error);
2540 return;
2541 }
2542
2543 ensureUniformValueSizes(results);
2544
2545 const qsizetype elementSize = 2 * sizeof(QLowEnergyHandle) + results.first().value.size();
2546 QByteArray responsePrefix(2, Qt::Uninitialized);
2547 responsePrefix[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_READ_BY_GROUP_RESPONSE);
2548 responsePrefix[1] = elementSize;
2549 const auto elemWriter = [](const Attribute &attr, char *&data) {
2550 putDataAndIncrement(attr.handle, data);
2551 putDataAndIncrement(attr.groupEndHandle, data);
2552 putDataAndIncrement(attr.value, data);
2553 };
2554 sendListResponse(responsePrefix, elementSize, results, elemWriter);
2555}
2556
2557void QLowEnergyControllerPrivateBluez::updateLocalAttributeValue(
2558 QLowEnergyHandle handle,
2559 const QByteArray &value,
2560 QLowEnergyCharacteristic &characteristic,
2561 QLowEnergyDescriptor &descriptor)
2562{
2563 localAttributes[handle].value = value;
2564 for (const auto &service : std::as_const(localServices)) {
2565 if (handle < service->startHandle || handle > service->endHandle)
2566 continue;
2567 for (auto charIt = service->characteristicList.begin();
2568 charIt != service->characteristicList.end(); ++charIt) {
2569 QLowEnergyServicePrivate::CharData &charData = charIt.value();
2570 if (handle == charIt.key() + 1) { // Char value decl comes right after char decl.
2571 charData.value = value;
2572 characteristic = QLowEnergyCharacteristic(service, charIt.key());
2573 return;
2574 }
2575 for (auto descIt = charData.descriptorList.begin();
2576 descIt != charData.descriptorList.end(); ++descIt) {
2577 if (handle == descIt.key()) {
2578 descIt.value().value = value;
2579 descriptor = QLowEnergyDescriptor(service, charIt.key(), handle);
2580 return;
2581 }
2582 }
2583 }
2584 }
2585 qFatal("local services map inconsistent with local attribute map");
2586}
2587
2588static bool isNotificationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x1; }
2589static bool isIndicationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x2; }
2590
2591void QLowEnergyControllerPrivateBluez::writeCharacteristicForPeripheral(
2593 const QByteArray &newValue)
2594{
2595 const QLowEnergyHandle valueHandle = charData.valueHandle;
2596 Q_ASSERT(valueHandle <= lastLocalHandle);
2597 Attribute &attribute = localAttributes[valueHandle];
2598 if (newValue.size() < attribute.minLength || newValue.size() > attribute.maxLength) {
2599 qCWarning(QT_BT_BLUEZ) << "ignoring value of invalid length" << newValue.size()
2600 << "for attribute" << valueHandle;
2601 return;
2602 }
2603 attribute.value = newValue;
2604 charData.value = newValue;
2605 const bool hasNotifyProperty = attribute.properties & QLowEnergyCharacteristic::Notify;
2606 const bool hasIndicateProperty
2607 = attribute.properties & QLowEnergyCharacteristic::Indicate;
2608 if (!hasNotifyProperty && !hasIndicateProperty)
2609 return;
2610 for (const QLowEnergyServicePrivate::DescData &desc : std::as_const(charData.descriptorList)) {
2611 if (desc.uuid != QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration)
2612 continue;
2613
2614 // Notify/indicate currently connected client.
2615 const bool isConnected = state == QLowEnergyController::ConnectedState;
2616 if (isConnected) {
2617 Q_ASSERT(desc.value.size() == 2);
2618 quint16 configValue = bt_get_le16(desc.value.constData());
2619 if (isNotificationEnabled(configValue) && hasNotifyProperty) {
2620 sendNotification(valueHandle);
2621 } else if (isIndicationEnabled(configValue) && hasIndicateProperty) {
2622 if (indicationInFlight)
2623 scheduledIndications << valueHandle;
2624 else
2625 sendIndication(valueHandle);
2626 }
2627 }
2628
2629 // Prepare notification/indication of unconnected, bonded clients.
2630 for (auto it = clientConfigData.begin(); it != clientConfigData.end(); ++it) {
2631 if (isConnected && it.key() == remoteDevice.toUInt64())
2632 continue;
2633 QList<ClientConfigurationData> &configDataList = it.value();
2634 for (ClientConfigurationData &configData : configDataList) {
2635 if (configData.charValueHandle != valueHandle)
2636 continue;
2637 if ((isNotificationEnabled(configData.configValue) && hasNotifyProperty)
2638 || (isIndicationEnabled(configData.configValue) && hasIndicateProperty)) {
2639 configData.charValueWasUpdated = true;
2640 break;
2641 }
2642 }
2643 }
2644 break;
2645 }
2646}
2647
2648void QLowEnergyControllerPrivateBluez::writeCharacteristicForCentral(const QSharedPointer<QLowEnergyServicePrivate> &service,
2649 QLowEnergyHandle charHandle,
2650 QLowEnergyHandle valueHandle,
2651 const QByteArray &newValue,
2652 QLowEnergyService::WriteMode mode)
2653{
2654 QByteArray packet(WRITE_REQUEST_HEADER_SIZE + newValue.size(), Qt::Uninitialized);
2655 putBtData(valueHandle, packet.data() + 1);
2656 memcpy(packet.data() + 3, newValue.constData(), newValue.size());
2657 bool writeWithResponse = false;
2658 switch (mode) {
2659 case QLowEnergyService::WriteWithResponse:
2660 if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
2661 sendNextPrepareWriteRequest(charHandle, newValue, 0);
2662 sendNextPendingRequest();
2663 return;
2664 }
2665 // write value fits into single package
2666 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST);
2667 writeWithResponse = true;
2668 break;
2669 case QLowEnergyService::WriteWithoutResponse:
2670 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_WRITE_COMMAND);
2671 break;
2672 case QLowEnergyService::WriteSigned:
2673 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_SIGNED_WRITE_COMMAND);
2674 if (!isBonded()) {
2675 qCWarning(QT_BT_BLUEZ) << "signed write not possible: requires bond between devices";
2676 service->setError(QLowEnergyService::CharacteristicWriteError);
2677 return;
2678 }
2679 if (securityLevel() >= BT_SECURITY_MEDIUM) {
2680 qCWarning(QT_BT_BLUEZ) << "signed write not possible: not allowed on encrypted link";
2681 service->setError(QLowEnergyService::CharacteristicWriteError);
2682 return;
2683 }
2684 const auto signingDataIt = signingData.find(remoteDevice.toUInt64());
2685 if (signingDataIt == signingData.end()) {
2686 qCWarning(QT_BT_BLUEZ) << "signed write not possible: no signature key found";
2687 service->setError(QLowEnergyService::CharacteristicWriteError);
2688 return;
2689 }
2690 ++signingDataIt.value().counter;
2691 packet = LeCmacCalculator::createFullMessage(packet, signingDataIt.value().counter);
2692 const quint64 mac = LeCmacCalculator().calculateMac(packet, signingDataIt.value().key);
2693 packet.resize(packet.size() + sizeof mac);
2694 putBtData(mac, packet.data() + packet.size() - sizeof mac);
2695 storeSignCounter(LocalSigningKey);
2696 break;
2697 }
2698
2699 qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << Qt::hex << charHandle
2700 << "(size:" << packet.size() << "with response:"
2701 << (mode == QLowEnergyService::WriteWithResponse)
2702 << "signed:" << (mode == QLowEnergyService::WriteSigned) << ")";
2703
2704 // Advantage of write without response is the quick turnaround.
2705 // It can be sent at any time and does not produce responses.
2706 // Therefore we will not put them into the openRequest queue at all.
2707 if (!writeWithResponse) {
2708 sendPacket(packet);
2709 return;
2710 }
2711
2712 Request request;
2713 request.payload = packet;
2715 request.reference = charHandle;
2716 request.reference2 = newValue;
2717 openRequests.enqueue(request);
2718
2719 sendNextPendingRequest();
2720}
2721
2722void QLowEnergyControllerPrivateBluez::writeDescriptorForPeripheral(
2723 const QSharedPointer<QLowEnergyServicePrivate> &service,
2724 const QLowEnergyHandle charHandle,
2725 const QLowEnergyHandle descriptorHandle,
2726 const QByteArray &newValue)
2727{
2728 Q_ASSERT(descriptorHandle <= lastLocalHandle);
2729 Attribute &attribute = localAttributes[descriptorHandle];
2730 if (newValue.size() < attribute.minLength || newValue.size() > attribute.maxLength) {
2731 qCWarning(QT_BT_BLUEZ) << "invalid value of size" << newValue.size()
2732 << "for attribute" << descriptorHandle;
2733 return;
2734 }
2735 attribute.value = newValue;
2736 service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue;
2737}
2738
2739void QLowEnergyControllerPrivateBluez::writeDescriptorForCentral(
2740 const QLowEnergyHandle charHandle,
2741 const QLowEnergyHandle descriptorHandle,
2742 const QByteArray &newValue)
2743{
2744 if (newValue.size() > (mtuSize - WRITE_REQUEST_HEADER_SIZE)) {
2745 sendNextPrepareWriteRequest(descriptorHandle, newValue, 0);
2746 sendNextPendingRequest();
2747 return;
2748 }
2749
2750 quint8 packet[WRITE_REQUEST_HEADER_SIZE];
2751 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_WRITE_REQUEST);
2752 putBtData(descriptorHandle, &packet[1]);
2753
2754 const qsizetype size = WRITE_REQUEST_HEADER_SIZE + newValue.size();
2755 QByteArray data(size, Qt::Uninitialized);
2756 memcpy(data.data(), packet, WRITE_REQUEST_HEADER_SIZE);
2757 memcpy(&(data.data()[WRITE_REQUEST_HEADER_SIZE]), newValue.constData(), newValue.size());
2758
2759 qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << Qt::hex << descriptorHandle
2760 << "(size:" << size << ")";
2761
2762 Request request;
2763 request.payload = data;
2765 request.reference = (charHandle | (descriptorHandle << 16));
2766 request.reference2 = newValue;
2767 openRequests.enqueue(request);
2768
2769 sendNextPendingRequest();
2770}
2771
2772void QLowEnergyControllerPrivateBluez::handleWriteRequestOrCommand(const QByteArray &packet)
2773{
2774 // Spec v4.2, Vol 3, Part F, 3.4.5.1-3
2775
2776 const bool isRequest = static_cast<QBluezConst::AttCommand>(packet.at(0))
2778 const bool isSigned = static_cast<QBluezConst::AttCommand>(packet.at(0))
2780 if (!checkPacketSize(packet, isSigned ? 15 : 3, mtuSize))
2781 return;
2782 const QLowEnergyHandle handle = bt_get_le16(packet.constData() + 1);
2783 qCDebug(QT_BT_BLUEZ) << "client sends" << (isSigned ? "signed" : "") << "write"
2784 << (isRequest ? "request" : "command") << "for handle" << handle;
2785
2786 if (!checkHandle(packet, handle))
2787 return;
2788
2789 Attribute &attribute = localAttributes[handle];
2790 const QLowEnergyCharacteristic::PropertyType type = isRequest
2791 ? QLowEnergyCharacteristic::Write : isSigned
2792 ? QLowEnergyCharacteristic::WriteSigned : QLowEnergyCharacteristic::WriteNoResponse;
2793 const QBluezConst::AttError permissionsError = checkPermissions(attribute, type);
2794 if (permissionsError != QBluezConst::AttError::ATT_ERROR_NO_ERROR) {
2795 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2796 permissionsError);
2797 return;
2798 }
2799
2800 int valueLength;
2801 if (isSigned) {
2802 if (!isBonded()) {
2803 qCWarning(QT_BT_BLUEZ) << "Ignoring signed write from non-bonded device.";
2804 return;
2805 }
2806 if (securityLevel() >= BT_SECURITY_MEDIUM) {
2807 qCWarning(QT_BT_BLUEZ) << "Ignoring signed write on encrypted link.";
2808 return;
2809 }
2810 const auto signingDataIt = signingData.find(remoteDevice.toUInt64());
2811 if (signingDataIt == signingData.constEnd()) {
2812 qCWarning(QT_BT_BLUEZ) << "No CSRK found for peer device, ignoring signed write";
2813 return;
2814 }
2815
2816 const quint32 signCounter = getBtData<quint32>(packet.data() + packet.size() - 12);
2817 if (signCounter < signingDataIt.value().counter + 1) {
2818 qCWarning(QT_BT_BLUEZ) << "Client's' sign counter" << signCounter
2819 << "not greater than local sign counter"
2820 << signingDataIt.value().counter
2821 << "; ignoring signed write command.";
2822 return;
2823 }
2824
2825 const quint64 macFromClient = getBtData<quint64>(packet.data() + packet.size() - 8);
2826 const bool signatureCorrect = verifyMac(packet.left(packet.size() - 12),
2827 signingDataIt.value().key, signCounter, macFromClient);
2828 if (!signatureCorrect) {
2829 qCWarning(QT_BT_BLUEZ) << "Signed Write packet has wrong signature, disconnecting";
2830 disconnectFromDevice(); // Recommended by spec v4.2, Vol 3, part C, 10.4.2
2831 return;
2832 }
2833
2834 signingDataIt.value().counter = signCounter;
2835 storeSignCounter(RemoteSigningKey);
2836 valueLength = packet.size() - 15;
2837 } else {
2838 valueLength = packet.size() - 3;
2839 }
2840
2841 if (valueLength > attribute.maxLength) {
2842 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2843 QBluezConst::AttError::ATT_ERROR_INVAL_ATTR_VALUE_LEN);
2844 return;
2845 }
2846
2847 // If the attribute value has a fixed size and the value in the packet is shorter,
2848 // then we overwrite only the start of the attribute value and keep the rest.
2849 QByteArray value = packet.mid(3, valueLength);
2850 if (attribute.minLength == attribute.maxLength && valueLength < attribute.minLength)
2851 value += attribute.value.mid(valueLength, attribute.maxLength - valueLength);
2852
2853 QLowEnergyCharacteristic characteristic;
2854 QLowEnergyDescriptor descriptor;
2855 updateLocalAttributeValue(handle, value, characteristic, descriptor);
2856
2857 if (isRequest) {
2858 const QByteArray response =
2859 QByteArray(1, static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_WRITE_RESPONSE));
2860 sendPacket(response);
2861 }
2862
2863 if (characteristic.isValid()) {
2864 emit characteristic.d_ptr->characteristicChanged(characteristic, value);
2865 } else {
2866 Q_ASSERT(descriptor.isValid());
2867 emit descriptor.d_ptr->descriptorWritten(descriptor, value);
2868 }
2869}
2870
2871void QLowEnergyControllerPrivateBluez::handlePrepareWriteRequest(const QByteArray &packet)
2872{
2873 // Spec v4.2, Vol 3, Part F, 3.4.6.1
2874
2875 if (!checkPacketSize(packet, 5, mtuSize))
2876 return;
2877 const quint16 handle = bt_get_le16(packet.constData() + 1);
2878 qCDebug(QT_BT_BLUEZ) << "client sends prepare write request for handle" << handle;
2879
2880 if (!checkHandle(packet, handle))
2881 return;
2882 const Attribute &attribute = localAttributes.at(handle);
2883 const QBluezConst::AttError permissionsError =
2884 checkPermissions(attribute, QLowEnergyCharacteristic::Write);
2885 if (permissionsError != QBluezConst::AttError::ATT_ERROR_NO_ERROR) {
2886 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2887 permissionsError);
2888 return;
2889 }
2890 if (openPrepareWriteRequests.size() >= maxPrepareQueueSize) {
2891 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)), handle,
2892 QBluezConst::AttError::ATT_ERROR_PREPARE_QUEUE_FULL);
2893 return;
2894 }
2895
2896 // The value is not checked here, but on the Execute request.
2897 openPrepareWriteRequests << WriteRequest(handle, bt_get_le16(packet.constData() + 3),
2898 packet.mid(5));
2899
2900 QByteArray response = packet;
2901 response[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_PREPARE_WRITE_RESPONSE);
2902 sendPacket(response);
2903}
2904
2905void QLowEnergyControllerPrivateBluez::handleExecuteWriteRequest(const QByteArray &packet)
2906{
2907 // Spec v4.2, Vol 3, Part F, 3.4.6.3
2908
2909 if (!checkPacketSize(packet, 2))
2910 return;
2911 const bool cancel = packet.at(1) == 0;
2912 qCDebug(QT_BT_BLUEZ) << "client sends execute write request; flag is"
2913 << (cancel ? "cancel" : "flush");
2914
2915 QList<WriteRequest> requests = openPrepareWriteRequests;
2916 openPrepareWriteRequests.clear();
2917 QList<QLowEnergyCharacteristic> characteristics;
2918 QList<QLowEnergyDescriptor> descriptors;
2919 if (!cancel) {
2920 for (const WriteRequest &request : std::as_const(requests)) {
2921 Attribute &attribute = localAttributes[request.handle];
2922 if (request.valueOffset > attribute.value.size()) {
2923 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)),
2924 request.handle, QBluezConst::AttError::ATT_ERROR_INVALID_OFFSET);
2925 return;
2926 }
2927 const QByteArray newValue = attribute.value.left(request.valueOffset) + request.value;
2928 if (newValue.size() > attribute.maxLength) {
2929 sendErrorResponse(static_cast<QBluezConst::AttCommand>(packet.at(0)),
2930 request.handle,
2931 QBluezConst::AttError::ATT_ERROR_INVAL_ATTR_VALUE_LEN);
2932 return;
2933 }
2934 QLowEnergyCharacteristic characteristic;
2935 QLowEnergyDescriptor descriptor;
2936 // TODO: Redundant attribute lookup for the case of the same handle appearing
2937 // more than once.
2938 updateLocalAttributeValue(request.handle, newValue, characteristic, descriptor);
2939 if (characteristic.isValid()) {
2940 characteristics << characteristic;
2941 } else if (descriptor.isValid()) {
2942 Q_ASSERT(descriptor.isValid());
2943 descriptors << descriptor;
2944 }
2945 }
2946 }
2947
2948 sendPacket(QByteArray(
2949 1, static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_EXECUTE_WRITE_RESPONSE)));
2950
2951 for (const QLowEnergyCharacteristic &characteristic : std::as_const(characteristics))
2952 emit characteristic.d_ptr->characteristicChanged(characteristic, characteristic.value());
2953 for (const QLowEnergyDescriptor &descriptor : std::as_const(descriptors))
2954 emit descriptor.d_ptr->descriptorWritten(descriptor, descriptor.value());
2955}
2956
2957void QLowEnergyControllerPrivateBluez::sendErrorResponse(QBluezConst::AttCommand request,
2958 quint16 handle, QBluezConst::AttError code)
2959{
2960 // An ATT command never receives an error response.
2963 return;
2964
2965 QByteArray packet(ERROR_RESPONSE_HEADER_SIZE, Qt::Uninitialized);
2966 packet[0] = static_cast<quint8>(QBluezConst::AttCommand::ATT_OP_ERROR_RESPONSE);
2967 packet[1] = static_cast<quint8>(request);
2968 putBtData(handle, packet.data() + 2);
2969 packet[4] = static_cast<quint8>(code);
2970 qCWarning(QT_BT_BLUEZ) << "sending error response; request:"
2971 << request << "handle:" << handle
2972 << "code:" << code;
2973 sendPacket(packet);
2974}
2975
2976void QLowEnergyControllerPrivateBluez::sendListResponse(const QByteArray &packetStart,
2977 qsizetype elemSize,
2978 const QList<Attribute> &attributes,
2979 const ElemWriter &elemWriter)
2980{
2981 const qsizetype offset = packetStart.size();
2982 const qsizetype elemCount = (std::min)(attributes.size(), (mtuSize - offset) / elemSize);
2983 const qsizetype totalPacketSize = offset + elemCount * elemSize;
2984 QByteArray response(totalPacketSize, Qt::Uninitialized);
2985 using namespace std;
2986 memcpy(response.data(), packetStart.constData(), offset);
2987 char *data = response.data() + offset;
2988 for_each(attributes.constBegin(), attributes.constBegin() + elemCount,
2989 [&data, elemWriter](const Attribute &attr) { elemWriter(attr, data); });
2990 qCDebug(QT_BT_BLUEZ) << "sending response:" << response.toHex();
2991 sendPacket(response);
2992}
2993
2994void QLowEnergyControllerPrivateBluez::sendNotification(QLowEnergyHandle handle)
2995{
2996 sendNotificationOrIndication(QBluezConst::AttCommand::ATT_OP_HANDLE_VAL_NOTIFICATION, handle);
2997}
2998
2999void QLowEnergyControllerPrivateBluez::sendIndication(QLowEnergyHandle handle)
3000{
3001 Q_ASSERT(!indicationInFlight);
3002 indicationInFlight = true;
3003 sendNotificationOrIndication(QBluezConst::AttCommand::ATT_OP_HANDLE_VAL_INDICATION, handle);
3004}
3005
3006void QLowEnergyControllerPrivateBluez::sendNotificationOrIndication(QBluezConst::AttCommand opCode,
3007 QLowEnergyHandle handle)
3008{
3009 Q_ASSERT(handle <= lastLocalHandle);
3010 const Attribute &attribute = localAttributes.at(handle);
3011 const qsizetype maxValueLength = (std::min)(attribute.value.size(), qsizetype(mtuSize) - 3);
3012 QByteArray packet(3 + maxValueLength, Qt::Uninitialized);
3013 packet[0] = static_cast<quint8>(opCode);
3014 putBtData(handle, packet.data() + 1);
3015 using namespace std;
3016 memcpy(packet.data() + 3, attribute.value.constData(), maxValueLength);
3017 qCDebug(QT_BT_BLUEZ) << "sending notification/indication:" << packet.toHex();
3018 sendPacket(packet);
3019}
3020
3021void QLowEnergyControllerPrivateBluez::sendNextIndication()
3022{
3023 if (!scheduledIndications.isEmpty())
3024 sendIndication(scheduledIndications.takeFirst());
3025}
3026
3027static QString nameOfRemoteCentral(const QBluetoothAddress &peerAddress)
3028{
3029 const QString peerAddressString = peerAddress.toString();
3030 initializeBluez5();
3031 OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez"),
3032 QStringLiteral("/"),
3033 QDBusConnection::systemBus());
3034 QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
3035 reply.waitForFinished();
3036 if (reply.isError())
3037 return QString();
3038
3039 ManagedObjectList managedObjectList = reply.value();
3040 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
3041 const InterfaceList &ifaceList = it.value();
3042
3043 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
3044 const QString &iface = jt.key();
3045 const QVariantMap &ifaceValues = jt.value();
3046
3047 if (iface == QStringLiteral("org.bluez.Device1")) {
3048 if (ifaceValues.value(QStringLiteral("Address")).toString() == peerAddressString)
3049 return ifaceValues.value(QStringLiteral("Alias")).toString();
3050 }
3051 }
3052 }
3053 return QString();
3054}
3055
3056void QLowEnergyControllerPrivateBluez::handleConnectionRequest()
3057{
3058 if (state != QLowEnergyController::AdvertisingState) {
3059 qCWarning(QT_BT_BLUEZ) << "Incoming connection request in unexpected state" << state;
3060 return;
3061 }
3062 Q_ASSERT(serverSocketNotifier);
3063 serverSocketNotifier->setEnabled(false);
3064 sockaddr_l2 clientAddr;
3065 socklen_t clientAddrSize = sizeof clientAddr;
3066 const int clientSocket = accept(serverSocketNotifier->socket(),
3067 reinterpret_cast<sockaddr *>(&clientAddr), &clientAddrSize);
3068 if (clientSocket == -1) {
3069 // Not fatal in itself. The next one might succeed.
3070 qCWarning(QT_BT_BLUEZ) << "accept() failed:" << qt_error_string(errno);
3071 serverSocketNotifier->setEnabled(true);
3072 return;
3073 }
3074
3075 remoteDevice = QBluetoothAddress(convertAddress(clientAddr.l2_bdaddr.b));
3076 remoteName = nameOfRemoteCentral(remoteDevice);
3077 qCDebug(QT_BT_BLUEZ) << "GATT connection from device" << remoteDevice << remoteName;
3078
3079 if (connectionHandle == 0)
3080 qCWarning(QT_BT_BLUEZ) << "Received client connection, but no connection complete event";
3081
3082 if (l2cpSocket) {
3083 disconnect(l2cpSocket);
3084 if (l2cpSocket->isOpen())
3085 l2cpSocket->close();
3086
3087 l2cpSocket->deleteLater();
3088 l2cpSocket = nullptr;
3089 }
3090 closeServerSocket();
3091
3093 l2cpSocket = new QBluetoothSocket(
3094 rawSocketPrivate, QBluetoothServiceInfo::L2capProtocol, this);
3095 connect(l2cpSocket, &QBluetoothSocket::disconnected,
3096 this, &QLowEnergyControllerPrivateBluez::l2cpDisconnected);
3097 connect(l2cpSocket, &QBluetoothSocket::errorOccurred, this,
3098 &QLowEnergyControllerPrivateBluez::l2cpErrorChanged);
3099 connect(l2cpSocket, &QIODevice::readyRead, this, &QLowEnergyControllerPrivateBluez::l2cpReadyRead);
3100 l2cpSocket->d_ptr->lowEnergySocketType = addressType == QLowEnergyController::PublicAddress
3102 l2cpSocket->setSocketDescriptor(clientSocket, QBluetoothServiceInfo::L2capProtocol,
3103 QBluetoothSocket::SocketState::ConnectedState, QIODevice::ReadWrite | QIODevice::Unbuffered);
3104 restoreClientConfigurations();
3105 loadSigningDataIfNecessary(RemoteSigningKey);
3106
3107 Q_Q(QLowEnergyController);
3108 setState(QLowEnergyController::ConnectedState);
3109 emit q->connected();
3110}
3111
3112void QLowEnergyControllerPrivateBluez::closeServerSocket()
3113{
3114 if (!serverSocketNotifier)
3115 return;
3116 serverSocketNotifier->disconnect();
3117 close(serverSocketNotifier->socket());
3118 serverSocketNotifier->deleteLater();
3119 serverSocketNotifier = nullptr;
3120}
3121
3122bool QLowEnergyControllerPrivateBluez::isBonded() const
3123{
3124 // Pairing does not necessarily imply bonding, but we don't know whether the
3125 // bonding flag was set in the original pairing request.
3126 return QBluetoothLocalDevice(localAdapter).pairingStatus(remoteDevice)
3127 != QBluetoothLocalDevice::Unpaired;
3128}
3129
3130QList<QLowEnergyControllerPrivateBluez::TempClientConfigurationData>
3131QLowEnergyControllerPrivateBluez::gatherClientConfigData()
3132{
3133 QList<TempClientConfigurationData> data;
3134 for (const auto &service : std::as_const(localServices)) {
3135 for (auto charIt = service->characteristicList.begin();
3136 charIt != service->characteristicList.end(); ++charIt) {
3137 QLowEnergyServicePrivate::CharData &charData = charIt.value();
3138 for (auto descIt = charData.descriptorList.begin();
3139 descIt != charData.descriptorList.end(); ++descIt) {
3140 QLowEnergyServicePrivate::DescData &descData = descIt.value();
3141 if (descData.uuid == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration) {
3142 data << TempClientConfigurationData(&descData, charData.valueHandle,
3143 descIt.key());
3144 break;
3145 }
3146 }
3147 }
3148 }
3149 return data;
3150}
3151
3152void QLowEnergyControllerPrivateBluez::storeClientConfigurations()
3153{
3154 if (!isBonded()) {
3155 clientConfigData.remove(remoteDevice.toUInt64());
3156 return;
3157 }
3158 QList<ClientConfigurationData> clientConfigs;
3159 const QList<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
3160 for (const auto &tempConfigData : tempConfigList) {
3161 Q_ASSERT(tempConfigData.descData->value.size() == 2);
3162 const quint16 value = bt_get_le16(tempConfigData.descData->value.constData());
3163 if (value != 0) {
3164 clientConfigs << ClientConfigurationData(tempConfigData.charValueHandle,
3165 tempConfigData.configHandle, value);
3166 }
3167 }
3168 clientConfigData.insert(remoteDevice.toUInt64(), clientConfigs);
3169}
3170
3171void QLowEnergyControllerPrivateBluez::restoreClientConfigurations()
3172{
3173 const QList<TempClientConfigurationData> &tempConfigList = gatherClientConfigData();
3174 const QList<ClientConfigurationData> &restoredClientConfigs = isBonded()
3175 ? clientConfigData.value(remoteDevice.toUInt64())
3176 : QList<ClientConfigurationData>();
3177 QList<QLowEnergyHandle> notifications;
3178 for (const auto &tempConfigData : tempConfigList) {
3179 bool wasRestored = false;
3180 for (const auto &restoredData : restoredClientConfigs) {
3181 if (restoredData.charValueHandle == tempConfigData.charValueHandle) {
3182 Q_ASSERT(tempConfigData.descData->value.size() == 2);
3183 putBtData(restoredData.configValue, tempConfigData.descData->value.data());
3184 wasRestored = true;
3185 if (restoredData.charValueWasUpdated) {
3186 if (isNotificationEnabled(restoredData.configValue))
3187 notifications << restoredData.charValueHandle;
3188 else if (isIndicationEnabled(restoredData.configValue))
3189 scheduledIndications << restoredData.charValueHandle;
3190 }
3191 break;
3192 }
3193 }
3194 if (!wasRestored)
3195 tempConfigData.descData->value = QByteArray(2, 0); // Default value.
3196 Q_ASSERT(lastLocalHandle >= tempConfigData.configHandle);
3197 Q_ASSERT(tempConfigData.configHandle > tempConfigData.charValueHandle);
3198 localAttributes[tempConfigData.configHandle].value = tempConfigData.descData->value;
3199 }
3200
3201 for (const QLowEnergyHandle handle : std::as_const(notifications))
3202 sendNotification(handle);
3203 sendNextIndication();
3204}
3205
3206void QLowEnergyControllerPrivateBluez::loadSigningDataIfNecessary(SigningKeyType keyType)
3207{
3208 const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
3209 if (signingDataIt != signingData.constEnd())
3210 return; // We are up to date for this device.
3211 const QString settingsFilePath = keySettingsFilePath();
3212 if (!QFileInfo(settingsFilePath).exists()) {
3213 qCDebug(QT_BT_BLUEZ) << "No settings found for peer device.";
3214 return;
3215 }
3216 QSettings settings(settingsFilePath, QSettings::IniFormat);
3217 const QString group = signingKeySettingsGroup(keyType);
3218 settings.beginGroup(group);
3219 const QByteArray keyString = settings.value(QLatin1String("Key")).toByteArray();
3220 if (keyString.isEmpty()) {
3221 qCDebug(QT_BT_BLUEZ) << "Group" << group << "not found in settings file";
3222 return;
3223 }
3224 const QByteArray keyData = QByteArray::fromHex(keyString);
3225 if (keyData.size() != qsizetype(sizeof(BluezUint128))) {
3226 qCWarning(QT_BT_BLUEZ) << "Signing key in settings file has invalid size"
3227 << keyString.size();
3228 return;
3229 }
3230 const quint32 counter = settings.value(QLatin1String("Counter"), 0).toUInt();
3231 using namespace std;
3232 BluezUint128 csrk;
3233 memcpy(csrk.data, keyData.constData(), keyData.size());
3234 signingData.insert(remoteDevice.toUInt64(), SigningData(csrk, counter - 1));
3235}
3236
3237void QLowEnergyControllerPrivateBluez::storeSignCounter(SigningKeyType keyType) const
3238{
3239 const auto signingDataIt = signingData.constFind(remoteDevice.toUInt64());
3240 if (signingDataIt == signingData.constEnd())
3241 return;
3242 const QString settingsFilePath = keySettingsFilePath();
3243 if (!QFileInfo(settingsFilePath).exists())
3244 return;
3245 QSettings settings(settingsFilePath, QSettings::IniFormat);
3246 if (!settings.isWritable())
3247 return;
3248 settings.beginGroup(signingKeySettingsGroup(keyType));
3249 const QString counterKey = QLatin1String("Counter");
3250 if (!settings.allKeys().contains(counterKey))
3251 return;
3252 const quint32 counterValue = signingDataIt.value().counter + 1;
3253 if (counterValue == settings.value(counterKey).toUInt())
3254 return;
3255 settings.setValue(counterKey, counterValue);
3256}
3257
3258QString QLowEnergyControllerPrivateBluez::signingKeySettingsGroup(SigningKeyType keyType) const
3259{
3260 return QLatin1String(keyType == LocalSigningKey ? "LocalSignatureKey" : "RemoteSignatureKey");
3261}
3262
3263QString QLowEnergyControllerPrivateBluez::keySettingsFilePath() const
3264{
3265 return QString::fromLatin1("/var/lib/bluetooth/%1/%2/info")
3266 .arg(localAdapter.toString(), remoteDevice.toString());
3267}
3268
3269static QByteArray uuidToByteArray(const QBluetoothUuid &uuid)
3270{
3271 QByteArray ba(sizeof(uuid), Qt::Uninitialized);
3272 char *ptr = ba.data();
3273 putDataAndIncrement(uuid, ptr);
3274 ba.resize(ptr - ba.constData());
3275 return ba;
3276}
3277
3278void QLowEnergyControllerPrivateBluez::addToGenericAttributeList(const QLowEnergyServiceData &service,
3279 QLowEnergyHandle startHandle)
3280{
3281 // Construct generic attribute data for the service with handles as keys.
3282 // Otherwise a number of request handling functions will be awkward to write
3283 // as well as computationally inefficient.
3284
3285 localAttributes.resize(lastLocalHandle + 1);
3286 Attribute serviceAttribute;
3287 serviceAttribute.handle = startHandle;
3288 serviceAttribute.type = QBluetoothUuid(static_cast<quint16>(service.type()));
3289 serviceAttribute.properties = QLowEnergyCharacteristic::Read;
3290 serviceAttribute.value = uuidToByteArray(service.uuid());
3291 QLowEnergyHandle currentHandle = startHandle;
3292 const QList<QLowEnergyService *> includedServices = service.includedServices();
3293 for (const QLowEnergyService * const service : includedServices) {
3294 Attribute attribute;
3295 attribute.handle = ++currentHandle;
3296 attribute.type = QBluetoothUuid(GATT_INCLUDED_SERVICE);
3297 attribute.properties = QLowEnergyCharacteristic::Read;
3298 const bool includeUuidInValue = service->serviceUuid().minimumSize() == 2;
3299 attribute.value.resize((2 + includeUuidInValue) * sizeof(QLowEnergyHandle));
3300 char *valueData = attribute.value.data();
3301 putDataAndIncrement(service->d_ptr->startHandle, valueData);
3302 putDataAndIncrement(service->d_ptr->endHandle, valueData);
3303 if (includeUuidInValue)
3304 putDataAndIncrement(service->serviceUuid(), valueData);
3305 localAttributes[attribute.handle] = attribute;
3306 }
3307 const QList<QLowEnergyCharacteristicData> characteristics = service.characteristics();
3308 for (const QLowEnergyCharacteristicData &cd : characteristics) {
3309 Attribute attribute;
3310
3311 // Characteristic declaration;
3312 attribute.handle = ++currentHandle;
3313 attribute.groupEndHandle = attribute.handle + 1 + cd.descriptors().size();
3314 attribute.type = QBluetoothUuid(GATT_CHARACTERISTIC);
3315 attribute.properties = QLowEnergyCharacteristic::Read;
3316 attribute.value.resize(1 + sizeof(QLowEnergyHandle) + cd.uuid().minimumSize());
3317 char *valueData = attribute.value.data();
3318 putDataAndIncrement(static_cast<quint8>(cd.properties()), valueData);
3319 putDataAndIncrement(QLowEnergyHandle(currentHandle + 1), valueData);
3320 putDataAndIncrement(cd.uuid(), valueData);
3321 localAttributes[attribute.handle] = attribute;
3322
3323 // Characteristic value declaration.
3324 attribute.handle = ++currentHandle;
3325 attribute.groupEndHandle = attribute.handle;
3326 attribute.type = cd.uuid();
3327 attribute.properties = cd.properties();
3328 attribute.readConstraints = cd.readConstraints();
3329 attribute.writeConstraints = cd.writeConstraints();
3330 attribute.value = cd.value();
3331 attribute.minLength = cd.minimumValueLength();
3332 attribute.maxLength = cd.maximumValueLength();
3333 localAttributes[attribute.handle] = attribute;
3334
3335 const QList<QLowEnergyDescriptorData> descriptors = cd.descriptors();
3336 for (const QLowEnergyDescriptorData &dd : descriptors) {
3337 attribute.handle = ++currentHandle;
3338 attribute.groupEndHandle = attribute.handle;
3339 attribute.type = dd.uuid();
3340 attribute.properties = QLowEnergyCharacteristic::PropertyTypes();
3341 attribute.readConstraints = AttAccessConstraints();
3342 attribute.writeConstraints = AttAccessConstraints();
3343 attribute.minLength = 0;
3344 attribute.maxLength = INT_MAX;
3345
3346 // Spec v4.2, Vol. 3, Part G, 3.3.3.x
3347 if (attribute.type == QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties) {
3348 attribute.properties = QLowEnergyCharacteristic::Read;
3349 attribute.minLength = attribute.maxLength = 2;
3350 } else if (attribute.type == QBluetoothUuid::DescriptorType::CharacteristicPresentationFormat) {
3351 attribute.properties = QLowEnergyCharacteristic::Read;
3352 attribute.minLength = attribute.maxLength = 7;
3353 } else if (attribute.type == QBluetoothUuid::DescriptorType::CharacteristicAggregateFormat) {
3354 attribute.properties = QLowEnergyCharacteristic::Read;
3355 attribute.minLength = 4;
3356 } else if (attribute.type == QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration
3357 || attribute.type == QBluetoothUuid::DescriptorType::ServerCharacteristicConfiguration) {
3358 attribute.properties = QLowEnergyCharacteristic::Read
3359 | QLowEnergyCharacteristic::Write
3360 | QLowEnergyCharacteristic::WriteNoResponse
3361 | QLowEnergyCharacteristic::WriteSigned;
3362 attribute.writeConstraints = dd.writeConstraints();
3363 attribute.minLength = attribute.maxLength = 2;
3364 } else {
3365 if (dd.isReadable())
3366 attribute.properties |= QLowEnergyCharacteristic::Read;
3367 if (dd.isWritable()) {
3368 attribute.properties |= QLowEnergyCharacteristic::Write;
3369 attribute.properties |= QLowEnergyCharacteristic::WriteNoResponse;
3370 attribute.properties |= QLowEnergyCharacteristic::WriteSigned;
3371 }
3372 attribute.readConstraints = dd.readConstraints();
3373 attribute.writeConstraints = dd.writeConstraints();
3374 }
3375
3376 attribute.value = dd.value();
3377 if (attribute.value.size() < attribute.minLength
3378 || attribute.value.size() > attribute.maxLength) {
3379 qCWarning(QT_BT_BLUEZ) << "attribute of type" << attribute.type
3380 << "has invalid length of" << attribute.value.size()
3381 << "bytes";
3382 attribute.value = QByteArray(attribute.minLength, 0);
3383 }
3384 localAttributes[attribute.handle] = attribute;
3385 }
3386 }
3387 serviceAttribute.groupEndHandle = currentHandle;
3388 localAttributes[serviceAttribute.handle] = serviceAttribute;
3389}
3390
3391int QLowEnergyControllerPrivateBluez::mtu() const
3392{
3393 return mtuSize;
3394}
3395
3396void QLowEnergyControllerPrivateBluez::ensureUniformAttributes(
3397 QList<Attribute> &attributes, const std::function<int(const Attribute &)> &getSize)
3398{
3399 if (attributes.isEmpty())
3400 return;
3401 const int firstSize = getSize(attributes.first());
3402 const auto it = std::find_if(attributes.begin() + 1, attributes.end(),
3403 [firstSize, getSize](const Attribute &attr) { return getSize(attr) != firstSize; });
3404 if (it != attributes.end())
3405 attributes.erase(it, attributes.end());
3406
3407}
3408
3409void QLowEnergyControllerPrivateBluez::ensureUniformUuidSizes(QList<Attribute> &attributes)
3410{
3411 ensureUniformAttributes(attributes,
3412 [](const Attribute &attr) { return getUuidSize(attr.type); });
3413}
3414
3415void QLowEnergyControllerPrivateBluez::ensureUniformValueSizes(QList<Attribute> &attributes)
3416{
3417 ensureUniformAttributes(attributes,
3418 [](const Attribute &attr) { return attr.value.size(); });
3419}
3420
3421QList<QLowEnergyControllerPrivateBluez::Attribute>
3422QLowEnergyControllerPrivateBluez::getAttributes(QLowEnergyHandle startHandle,
3423 QLowEnergyHandle endHandle,
3424 const AttributePredicate &attributePredicate)
3425{
3426 QList<Attribute> results;
3427 if (startHandle > lastLocalHandle)
3428 return results;
3429 if (lastLocalHandle == 0) // We have no services at all.
3430 return results;
3431 Q_ASSERT(startHandle <= endHandle); // Must have been checked before.
3432 const QLowEnergyHandle firstHandle = qMin(startHandle, lastLocalHandle);
3433 const QLowEnergyHandle lastHandle = qMin(endHandle, lastLocalHandle);
3434 for (QLowEnergyHandle i = firstHandle; i <= lastHandle; ++i) {
3435 const Attribute &attr = localAttributes.at(i);
3436 if (attributePredicate(attr))
3437 results << attr;
3438 }
3439 return results;
3440}
3441
3443QLowEnergyControllerPrivateBluez::checkPermissions(const Attribute &attr,
3444 QLowEnergyCharacteristic::PropertyType type)
3445{
3446 const bool isReadAccess = type == QLowEnergyCharacteristic::Read;
3447 const bool isWriteCommand = type == QLowEnergyCharacteristic::WriteNoResponse;
3448 const bool isWriteAccess = type == QLowEnergyCharacteristic::Write
3449 || type == QLowEnergyCharacteristic::WriteSigned
3450 || isWriteCommand;
3451 Q_ASSERT(isReadAccess || isWriteAccess);
3452 if (!(attr.properties & type)) {
3453 if (isReadAccess)
3455
3456 // The spec says: If an attribute requires a signed write, then a non-signed write command
3457 // can also be used if the link is encrypted.
3458 const bool unsignedWriteOk = isWriteCommand
3459 && (attr.properties & QLowEnergyCharacteristic::WriteSigned)
3460 && securityLevel() >= BT_SECURITY_MEDIUM;
3461 if (!unsignedWriteOk)
3463 }
3464 const AttAccessConstraints constraints = isReadAccess
3465 ? attr.readConstraints : attr.writeConstraints;
3466 if (constraints.testFlag(AttAccessConstraint::AttAuthorizationRequired))
3467 return QBluezConst::AttError::ATT_ERROR_INSUF_AUTHORIZATION; // TODO: emit signal (and offer
3468 // authorization function)?
3469 if (constraints.testFlag(AttAccessConstraint::AttEncryptionRequired)
3470 && securityLevel() < BT_SECURITY_MEDIUM)
3472 if (constraints.testFlag(AttAccessConstraint::AttAuthenticationRequired)
3473 && securityLevel() < BT_SECURITY_HIGH)
3475 if (false)
3478}
3479
3480QBluezConst::AttError QLowEnergyControllerPrivateBluez::checkReadPermissions(const Attribute &attr)
3481{
3482 return checkPermissions(attr, QLowEnergyCharacteristic::Read);
3483}
3484
3486QLowEnergyControllerPrivateBluez::checkReadPermissions(QList<Attribute> &attributes)
3487{
3488 if (attributes.isEmpty())
3490
3491 // The logic prescribed in the spec is as follows:
3492 // 1) If the first in a list of matching attributes has a permissions error,
3493 // then that error is returned via an error response.
3494 // 2) If any other element of that list would cause a permissions error, then all
3495 // attributes from this one on are not part of the result set, but no error is returned.
3496 const QBluezConst::AttError error = checkReadPermissions(attributes.first());
3498 return error;
3499 const auto it =
3500 std::find_if(attributes.begin() + 1, attributes.end(), [this](const Attribute &attr) {
3501 return checkReadPermissions(attr) != QBluezConst::AttError::ATT_ERROR_NO_ERROR;
3502 });
3503 if (it != attributes.end())
3504 attributes.erase(it, attributes.end());
3506}
3507
3508bool QLowEnergyControllerPrivateBluez::verifyMac(const QByteArray &message, BluezUint128 csrk,
3509 quint32 signCounter, quint64 expectedMac)
3510{
3511 if (!cmacCalculator)
3512 cmacCalculator = new LeCmacCalculator;
3513 return cmacCalculator->verify(LeCmacCalculator::createFullMessage(message, signCounter), csrk,
3514 expectedMac);
3515}
3516
3517QT_END_NAMESPACE
3518
3519#include "moc_qlowenergycontroller_bluez_p.cpp"
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
#define htobs(d)
#define L2CAP_LM_ENCRYPT
#define BT_SECURITY_LOW
#define BT_SECURITY
#define SOL_L2CAP
#define SOL_BLUETOOTH
#define L2CAP_LM
#define BT_SECURITY_SDP
QUuid::Id128Bytes BluezUint128
#define L2CAP_LM_AUTH
#define ATTRIBUTE_CHANNEL_ID
#define BT_SECURITY_MEDIUM
#define BTPROTO_L2CAP
#define L2CAP_LM_SECURE
#define BDADDR_LE_PUBLIC
#define BT_SECURITY_HIGH
#define BDADDR_LE_RANDOM
static BluetoothManagement * instance()
void discoverServiceDetails(const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode) override
void readCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle) override
void writeCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode mode) override
Writes long (prepare write request), short (write request) and writeWithoutResponse characteristic va...
void writeDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) override
void startAdvertising(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) override
void readDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle) override
void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override
void setController(QLowEnergyControllerPrivate *control)
bool listen(const QBluetoothAddress &localAdapter)
void registerQLowEnergyControllerMetaType()
void putDataAndIncrement(const QBluetoothUuid &uuid, char *&dst)
#define GATT_CHARACTERISTIC
#define GATT_SECONDARY_SERVICE
static bool isNotificationEnabled(quint16 clientConfigValue)
QLowEnergyHandle parseReadByTypeIncludeDiscovery(QList< QBluetoothUuid > *foundServices, const char *data, quint16 elementLength)
#define GATT_PRIMARY_SERVICE
static int getUuidSize(const QBluetoothUuid &uuid)
#define APPEND_VALUE
#define WRITE_REQUEST_HEADER_SIZE
static Q_DECL_COLD_FUNCTION void reportMalformedData(QBluezConst::AttCommand cmd, const QByteArray &response)
constexpr quint16 ATT_MAX_LE_MTU
QLowEnergyHandle parseReadByTypeCharDiscovery(QLowEnergyServicePrivate::CharData *charData, const char *data, quint16 elementLength)
#define GRP_TYPE_REQ_HEADER_SIZE
static bool dumpErrorInformation(const QByteArray &response)
#define READ_REQUEST_HEADER_SIZE
#define READ_BY_TYPE_REQ_HEADER_SIZE
#define EXECUTE_WRITE_HEADER_SIZE
const int maxPrepareQueueSize
static QString nameOfRemoteCentral(const QBluetoothAddress &peerAddress)
#define PREPARE_WRITE_HEADER_SIZE
#define GATT_INCLUDED_SERVICE
#define READ_BLOB_REQUEST_HEADER_SIZE
static void putDataAndIncrement(const T &src, char *&dst)
static bool isIndicationEnabled(quint16 clientConfigValue)
#define MTU_EXCHANGE_HEADER_SIZE
#define FIND_INFO_REQUEST_HEADER_SIZE
#define NEW_VALUE
#define ERROR_RESPONSE_HEADER_SIZE
static QByteArray uuidToByteArray(const QBluetoothUuid &uuid)
constexpr quint16 ATT_DEFAULT_LE_MTU
unsigned short l2_cid