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

source code of qtconnectivity/src/bluetooth/qlowenergycontroller_bluez.cpp