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

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