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