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