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