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 | |
36 | constexpr quint16 ATT_DEFAULT_LE_MTU = 23; |
37 | constexpr 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 5 |
46 | #define 5 |
47 | #define 7 |
48 | #define 7 |
49 | #define 3 |
50 | #define 5 |
51 | #define 3 // same size for WRITE_COMMAND header |
52 | #define 5 |
53 | #define 2 |
54 | #define 3 |
55 | |
56 | #define APPEND_VALUE true |
57 | #define NEW_VALUE false |
58 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
62 | |
63 | using namespace QBluetooth; |
64 | |
65 | const int maxPrepareQueueSize = 1024; |
66 | |
67 | /* returns false if the format is incorrect */ |
68 | static 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 | |
134 | static int getUuidSize(const QBluetoothUuid &uuid) |
135 | { |
136 | return uuid.minimumSize() == 2 ? 2 : 16; |
137 | } |
138 | |
139 | template<typename T> static void putDataAndIncrement(const T &src, char *&dst) |
140 | { |
141 | putBtData(src, dst); |
142 | dst += sizeof(T); |
143 | } |
144 | template<> 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 | } |
157 | template<> 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 | |
164 | QLowEnergyControllerPrivateBluez::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 | |
175 | void 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 ¶ms) { |
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 | |
235 | void 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 | |
319 | QLowEnergyControllerPrivateBluez::~QLowEnergyControllerPrivateBluez() |
320 | { |
321 | closeServerSocket(); |
322 | delete cmacCalculator; |
323 | cmacCalculator = nullptr; |
324 | } |
325 | |
326 | class ServerSocket |
327 | { |
328 | public: |
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 | |
371 | private: |
372 | int m_socket = -1; |
373 | }; |
374 | |
375 | |
376 | void QLowEnergyControllerPrivateBluez::startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, |
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 | |
409 | void QLowEnergyControllerPrivateBluez::stopAdvertising() |
410 | { |
411 | setState(QLowEnergyController::UnconnectedState); |
412 | advertiser->stopAdvertising(); |
413 | } |
414 | |
415 | void QLowEnergyControllerPrivateBluez::requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) |
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 | |
427 | void 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 | */ |
472 | void 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 | */ |
495 | void 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 | |
551 | void 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 | |
617 | void QLowEnergyControllerPrivateBluez::l2cpConnected() |
618 | { |
619 | Q_Q(QLowEnergyController); |
620 | |
621 | securityLevelValue = securityLevel(); |
622 | exchangeMTU(); |
623 | |
624 | setState(QLowEnergyController::ConnectedState); |
625 | emit q->connected(); |
626 | } |
627 | |
628 | void 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 | |
643 | void 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 | |
658 | void 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 | |
691 | void 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 | |
715 | void QLowEnergyControllerPrivateBluez::restartRequestTimer() |
716 | { |
717 | if (!requestTimer) |
718 | return; |
719 | |
720 | if (gattRequestTimeout > 0) |
721 | requestTimer->start(msec: gattRequestTimeout); |
722 | } |
723 | |
724 | void 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 | */ |
821 | void 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 | |
870 | void 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 | |
889 | void 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 | |
903 | QLowEnergyHandle 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 | |
930 | QLowEnergyHandle 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 | |
962 | Q_DECL_COLD_FUNCTION |
963 | static 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 | |
969 | void 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 | |
1525 | void QLowEnergyControllerPrivateBluez::discoverServices() |
1526 | { |
1527 | sendReadByGroupRequest(start: 0x0001, end: 0xFFFF, GATT_PRIMARY_SERVICE); |
1528 | } |
1529 | |
1530 | void 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 | |
1555 | void 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 | |
1570 | void 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 | */ |
1606 | void 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 | */ |
1710 | void 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 | |
1749 | void 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 | |
1769 | void 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 | |
1803 | void 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 | |
1822 | int 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 | |
1861 | bool 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 | |
1908 | void 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 | |
1945 | void 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 | */ |
2000 | void 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 | */ |
2032 | void 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 | |
2050 | void 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 | */ |
2069 | void 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 | |
2107 | void 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 | */ |
2146 | bool 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 | |
2175 | void QLowEnergyControllerPrivateBluez::handleAdvertisingError() |
2176 | { |
2177 | qCWarning(QT_BT_BLUEZ) << "received advertising error" ; |
2178 | setError(QLowEnergyController::AdvertisingError); |
2179 | setState(QLowEnergyController::UnconnectedState); |
2180 | } |
2181 | |
2182 | bool 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 | |
2196 | bool 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 | |
2205 | bool 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 | |
2217 | void 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 | |
2245 | void 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 | |
2281 | void 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 | |
2319 | void 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 | |
2377 | void 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 | |
2405 | void 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 | |
2448 | void 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 | |
2487 | void 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 | |
2552 | void 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 | |
2583 | static bool isNotificationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x1; } |
2584 | static bool isIndicationEnabled(quint16 clientConfigValue) { return clientConfigValue & 0x2; } |
2585 | |
2586 | void 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 | |
2643 | void 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 | |
2717 | void 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 | |
2734 | void 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 | |
2767 | void 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 | |
2866 | void 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 | |
2900 | void 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 | |
2952 | void 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 | |
2971 | void 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 | |
2989 | void QLowEnergyControllerPrivateBluez::sendNotification(QLowEnergyHandle handle) |
2990 | { |
2991 | sendNotificationOrIndication(opCode: QBluezConst::AttCommand::ATT_OP_HANDLE_VAL_NOTIFICATION, handle); |
2992 | } |
2993 | |
2994 | void 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 | |
3001 | void 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 | |
3016 | void QLowEnergyControllerPrivateBluez::sendNextIndication() |
3017 | { |
3018 | if (!scheduledIndications.isEmpty()) |
3019 | sendIndication(handle: scheduledIndications.takeFirst()); |
3020 | } |
3021 | |
3022 | static 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 | |
3051 | void 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 | |
3107 | void QLowEnergyControllerPrivateBluez::closeServerSocket() |
3108 | { |
3109 | if (!serverSocketNotifier) |
3110 | return; |
3111 | serverSocketNotifier->disconnect(); |
3112 | close(fd: serverSocketNotifier->socket()); |
3113 | serverSocketNotifier->deleteLater(); |
3114 | serverSocketNotifier = nullptr; |
3115 | } |
3116 | |
3117 | bool 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 | |
3125 | QList<QLowEnergyControllerPrivateBluez::TempClientConfigurationData> |
3126 | QLowEnergyControllerPrivateBluez::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 | |
3147 | void 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 | |
3166 | void 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 | |
3201 | void 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 | |
3233 | void 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 | |
3254 | QString QLowEnergyControllerPrivateBluez::signingKeySettingsGroup(SigningKeyType keyType) const |
3255 | { |
3256 | return QLatin1String(keyType == LocalSigningKey ? "LocalSignatureKey" : "RemoteSignatureKey" ); |
3257 | } |
3258 | |
3259 | QString 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 | |
3265 | static 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 | |
3274 | void 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 | |
3387 | int QLowEnergyControllerPrivateBluez::mtu() const |
3388 | { |
3389 | return mtuSize; |
3390 | } |
3391 | |
3392 | void 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 | |
3405 | void QLowEnergyControllerPrivateBluez::ensureUniformUuidSizes(QList<Attribute> &attributes) |
3406 | { |
3407 | ensureUniformAttributes(attributes, |
3408 | getSize: [](const Attribute &attr) { return getUuidSize(uuid: attr.type); }); |
3409 | } |
3410 | |
3411 | void QLowEnergyControllerPrivateBluez::ensureUniformValueSizes(QList<Attribute> &attributes) |
3412 | { |
3413 | ensureUniformAttributes(attributes, |
3414 | getSize: [](const Attribute &attr) { return attr.value.size(); }); |
3415 | } |
3416 | |
3417 | QList<QLowEnergyControllerPrivateBluez::Attribute> |
3418 | QLowEnergyControllerPrivateBluez::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 | |
3438 | QBluezConst::AttError |
3439 | QLowEnergyControllerPrivateBluez::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 | |
3476 | QBluezConst::AttError QLowEnergyControllerPrivateBluez::checkReadPermissions(const Attribute &attr) |
3477 | { |
3478 | return checkPermissions(attr, type: QLowEnergyCharacteristic::Read); |
3479 | } |
3480 | |
3481 | QBluezConst::AttError |
3482 | QLowEnergyControllerPrivateBluez::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 | |
3504 | bool 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 | |
3513 | QT_END_NAMESPACE |
3514 | |
3515 | #include "moc_qlowenergycontroller_bluez_p.cpp" |
3516 | |