1 | // Copyright (C) 2016 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 "hcimanager_p.h" |
6 | |
7 | #include "qbluetoothsocketbase_p.h" |
8 | #include "qlowenergyconnectionparameters.h" |
9 | |
10 | #include <QtCore/qloggingcategory.h> |
11 | |
12 | #include <cstring> |
13 | #include <errno.h> |
14 | #include <sys/types.h> |
15 | #include <sys/socket.h> |
16 | #include <sys/ioctl.h> |
17 | #include <sys/uio.h> |
18 | #include <unistd.h> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
23 | |
24 | HciManager::HciManager(const QBluetoothAddress& deviceAdapter) : |
25 | QObject(nullptr), hciSocket(-1), hciDev(-1) |
26 | { |
27 | hciSocket = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); |
28 | if (hciSocket < 0) { |
29 | qCWarning(QT_BT_BLUEZ) << "Cannot open HCI socket" ; |
30 | return; //TODO error report |
31 | } |
32 | |
33 | hciDev = hciForAddress(deviceAdapter); |
34 | if (hciDev < 0) { |
35 | qCWarning(QT_BT_BLUEZ) << "Cannot find hci dev for" << deviceAdapter.toString(); |
36 | close(fd: hciSocket); |
37 | hciSocket = -1; |
38 | return; |
39 | } |
40 | |
41 | struct sockaddr_hci addr; |
42 | |
43 | memset(s: &addr, c: 0, n: sizeof(struct sockaddr_hci)); |
44 | addr.hci_dev = hciDev; |
45 | addr.hci_family = AF_BLUETOOTH; |
46 | |
47 | if (::bind(fd: hciSocket, addr: (struct sockaddr *) (&addr), len: sizeof(addr)) < 0) { |
48 | qCWarning(QT_BT_BLUEZ) << "HCI bind failed:" << strerror(errno); |
49 | close(fd: hciSocket); |
50 | hciSocket = hciDev = -1; |
51 | return; |
52 | } |
53 | |
54 | notifier = new QSocketNotifier(hciSocket, QSocketNotifier::Read, this); |
55 | connect(sender: notifier, SIGNAL(activated(QSocketDescriptor)), receiver: this, SLOT(_q_readNotify())); |
56 | |
57 | } |
58 | |
59 | HciManager::~HciManager() |
60 | { |
61 | if (hciSocket >= 0) |
62 | ::close(fd: hciSocket); |
63 | |
64 | } |
65 | |
66 | bool HciManager::isValid() const |
67 | { |
68 | if (hciSocket && hciDev >= 0) |
69 | return true; |
70 | return false; |
71 | } |
72 | |
73 | int HciManager::hciForAddress(const QBluetoothAddress &deviceAdapter) |
74 | { |
75 | if (hciSocket < 0) |
76 | return -1; |
77 | |
78 | bdaddr_t adapter; |
79 | convertAddress(from: deviceAdapter.toUInt64(), to&: adapter.b); |
80 | |
81 | struct hci_dev_req *devRequest = nullptr; |
82 | struct hci_dev_list_req *devRequestList = nullptr; |
83 | struct hci_dev_info devInfo; |
84 | const int devListSize = sizeof(struct hci_dev_list_req) |
85 | + HCI_MAX_DEV * sizeof(struct hci_dev_req); |
86 | |
87 | devRequestList = (hci_dev_list_req *) malloc(size: devListSize); |
88 | if (!devRequestList) |
89 | return -1; |
90 | |
91 | QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> p(devRequestList); |
92 | |
93 | memset(s: p.data(), c: 0, n: devListSize); |
94 | p->dev_num = HCI_MAX_DEV; |
95 | devRequest = p->dev_req; |
96 | |
97 | if (ioctl(fd: hciSocket, HCIGETDEVLIST, devRequestList) < 0) |
98 | return -1; |
99 | |
100 | for (int i = 0; i < devRequestList->dev_num; i++) { |
101 | devInfo.dev_id = (devRequest+i)->dev_id; |
102 | if (ioctl(fd: hciSocket, HCIGETDEVINFO, &devInfo) < 0) { |
103 | continue; |
104 | } |
105 | |
106 | int result = memcmp(s1: &adapter, s2: &devInfo.bdaddr, n: sizeof(bdaddr_t)); |
107 | if (result == 0 || deviceAdapter.isNull()) // addresses match |
108 | return devInfo.dev_id; |
109 | } |
110 | |
111 | return -1; |
112 | } |
113 | |
114 | /* |
115 | * Returns true if \a event was successfully enabled |
116 | */ |
117 | bool HciManager::monitorEvent(HciManager::HciEvent event) |
118 | { |
119 | if (!isValid()) |
120 | return false; |
121 | |
122 | // this event is already enabled |
123 | // TODO runningEvents does not seem to be used |
124 | if (runningEvents.contains(value: event)) |
125 | return true; |
126 | |
127 | hci_filter filter; |
128 | socklen_t length = sizeof(hci_filter); |
129 | if (getsockopt(fd: hciSocket, SOL_HCI, HCI_FILTER, optval: &filter, optlen: &length) < 0) { |
130 | qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings" ; |
131 | return false; |
132 | } |
133 | |
134 | hci_filter_set_ptype(HCI_EVENT_PKT, f: &filter); |
135 | hci_filter_set_event(e: static_cast<int>(event), f: &filter); |
136 | //hci_filter_all_events(&filter); |
137 | |
138 | if (setsockopt(fd: hciSocket, SOL_HCI, HCI_FILTER, optval: &filter, optlen: sizeof(hci_filter)) < 0) { |
139 | qCWarning(QT_BT_BLUEZ) << "Could not set HCI socket options:" << strerror(errno); |
140 | return false; |
141 | } |
142 | |
143 | return true; |
144 | } |
145 | |
146 | bool HciManager::monitorAclPackets() |
147 | { |
148 | if (!isValid()) |
149 | return false; |
150 | |
151 | hci_filter filter; |
152 | socklen_t length = sizeof(hci_filter); |
153 | if (getsockopt(fd: hciSocket, SOL_HCI, HCI_FILTER, optval: &filter, optlen: &length) < 0) { |
154 | qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings" ; |
155 | return false; |
156 | } |
157 | |
158 | hci_filter_set_ptype(HCI_ACL_PKT, f: &filter); |
159 | hci_filter_all_events(f: &filter); |
160 | |
161 | if (setsockopt(fd: hciSocket, SOL_HCI, HCI_FILTER, optval: &filter, optlen: sizeof(hci_filter)) < 0) { |
162 | qCWarning(QT_BT_BLUEZ) << "Could not set HCI socket options:" << strerror(errno); |
163 | return false; |
164 | } |
165 | |
166 | return true; |
167 | } |
168 | |
169 | bool HciManager::sendCommand(QBluezConst::OpCodeGroupField ogf, QBluezConst::OpCodeCommandField ocf, const QByteArray ¶meters) |
170 | { |
171 | qCDebug(QT_BT_BLUEZ) << "sending command; ogf:" << ogf << "ocf:" << ocf; |
172 | quint8 packetType = HCI_COMMAND_PKT; |
173 | hci_command_hdr command = { |
174 | opCodePack(ogf, ocf), |
175 | .plen: static_cast<uint8_t>(parameters.size()) |
176 | }; |
177 | static_assert(sizeof command == 3, "unexpected struct size" ); |
178 | struct iovec iv[3]; |
179 | iv[0].iov_base = &packetType; |
180 | iv[0].iov_len = 1; |
181 | iv[1].iov_base = &command; |
182 | iv[1].iov_len = sizeof command; |
183 | int ivn = 2; |
184 | if (!parameters.isEmpty()) { |
185 | iv[2].iov_base = const_cast<char *>(parameters.constData()); // const_cast is safe, since iov_base will not get modified. |
186 | iv[2].iov_len = parameters.size(); |
187 | ++ivn; |
188 | } |
189 | while (writev(fd: hciSocket, iovec: iv, count: ivn) < 0) { |
190 | if (errno == EAGAIN || errno == EINTR) |
191 | continue; |
192 | qCDebug(QT_BT_BLUEZ()) << "hci command failure:" << strerror(errno); |
193 | return false; |
194 | } |
195 | qCDebug(QT_BT_BLUEZ) << "command sent successfully" ; |
196 | return true; |
197 | } |
198 | |
199 | /* |
200 | * Unsubscribe from all events |
201 | */ |
202 | void HciManager::stopEvents() |
203 | { |
204 | if (!isValid()) |
205 | return; |
206 | |
207 | hci_filter filter; |
208 | hci_filter_clear(f: &filter); |
209 | |
210 | if (setsockopt(fd: hciSocket, SOL_HCI, HCI_FILTER, optval: &filter, optlen: sizeof(hci_filter)) < 0) { |
211 | qCWarning(QT_BT_BLUEZ) << "Could not clear HCI socket options:" << strerror(errno); |
212 | return; |
213 | } |
214 | |
215 | runningEvents.clear(); |
216 | } |
217 | |
218 | QBluetoothAddress HciManager::addressForConnectionHandle(quint16 handle) const |
219 | { |
220 | if (!isValid()) |
221 | return QBluetoothAddress(); |
222 | |
223 | hci_conn_info *info; |
224 | hci_conn_list_req *infoList; |
225 | |
226 | const int maxNoOfConnections = 20; |
227 | infoList = (hci_conn_list_req *) |
228 | malloc(size: sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info)); |
229 | |
230 | if (!infoList) |
231 | return QBluetoothAddress(); |
232 | |
233 | QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> p(infoList); |
234 | p->conn_num = maxNoOfConnections; |
235 | p->dev_id = hciDev; |
236 | info = p->conn_info; |
237 | |
238 | if (ioctl(fd: hciSocket, HCIGETCONNLIST, (void *) infoList) < 0) { |
239 | qCWarning(QT_BT_BLUEZ) << "Cannot retrieve connection list" ; |
240 | return QBluetoothAddress(); |
241 | } |
242 | |
243 | for (int i = 0; i < infoList->conn_num; i++) { |
244 | if (info[i].handle == handle) |
245 | return QBluetoothAddress(convertAddress(from: info[i].bdaddr.b)); |
246 | } |
247 | |
248 | return QBluetoothAddress(); |
249 | } |
250 | |
251 | QList<quint16> HciManager::activeLowEnergyConnections() const |
252 | { |
253 | if (!isValid()) |
254 | return QList<quint16>(); |
255 | |
256 | hci_conn_info *info; |
257 | hci_conn_list_req *infoList; |
258 | |
259 | const int maxNoOfConnections = 20; |
260 | infoList = (hci_conn_list_req *) |
261 | malloc(size: sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info)); |
262 | |
263 | if (!infoList) |
264 | return QList<quint16>(); |
265 | |
266 | QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> p(infoList); |
267 | p->conn_num = maxNoOfConnections; |
268 | p->dev_id = hciDev; |
269 | info = p->conn_info; |
270 | |
271 | if (ioctl(fd: hciSocket, HCIGETCONNLIST, (void *) infoList) < 0) { |
272 | qCWarning(QT_BT_BLUEZ) << "Cannot retrieve connection list" ; |
273 | return QList<quint16>(); |
274 | } |
275 | |
276 | QList<quint16> activeLowEnergyHandles; |
277 | for (int i = 0; i < infoList->conn_num; i++) { |
278 | switch (info[i].type) { |
279 | case SCO_LINK: |
280 | case ACL_LINK: |
281 | case ESCO_LINK: |
282 | continue; |
283 | case LE_LINK: |
284 | activeLowEnergyHandles.append(t: info[i].handle); |
285 | break; |
286 | default: |
287 | qCWarning(QT_BT_BLUEZ) << "Unknown active connection type:" << Qt::hex << info[i].type; |
288 | break; |
289 | } |
290 | } |
291 | |
292 | return activeLowEnergyHandles; |
293 | } |
294 | |
295 | quint16 forceIntervalIntoRange(double connectionInterval) |
296 | { |
297 | return qMin<double>(a: qMax<double>(a: 7.5, b: connectionInterval), b: 4000) / 1.25; |
298 | } |
299 | |
300 | struct ConnectionUpdateData { |
301 | quint16 minInterval; |
302 | quint16 maxInterval; |
303 | quint16 slaveLatency; |
304 | quint16 timeout; |
305 | }; |
306 | ConnectionUpdateData connectionUpdateData(const QLowEnergyConnectionParameters ¶ms) |
307 | { |
308 | ConnectionUpdateData data; |
309 | const quint16 minInterval = forceIntervalIntoRange(connectionInterval: params.minimumInterval()); |
310 | const quint16 maxInterval = forceIntervalIntoRange(connectionInterval: params.maximumInterval()); |
311 | data.minInterval = qToLittleEndian(source: minInterval); |
312 | data.maxInterval = qToLittleEndian(source: maxInterval); |
313 | const quint16 latency = qMax<quint16>(a: 0, b: qMin<quint16>(a: params.latency(), b: 499)); |
314 | data.slaveLatency = qToLittleEndian(source: latency); |
315 | const quint16 timeout |
316 | = qMax<quint16>(a: 100, b: qMin<quint16>(a: 32000, b: params.supervisionTimeout())) / 10; |
317 | data.timeout = qToLittleEndian(source: timeout); |
318 | return data; |
319 | } |
320 | |
321 | bool HciManager::sendConnectionUpdateCommand(quint16 handle, |
322 | const QLowEnergyConnectionParameters ¶ms) |
323 | { |
324 | struct CommandParams { |
325 | quint16 handle; |
326 | ConnectionUpdateData data; |
327 | quint16 minCeLength; |
328 | quint16 maxCeLength; |
329 | } commandParams; |
330 | commandParams.handle = qToLittleEndian(source: handle); |
331 | commandParams.data = connectionUpdateData(params); |
332 | commandParams.minCeLength = 0; |
333 | commandParams.maxCeLength = qToLittleEndian(source: quint16(0xffff)); |
334 | const QByteArray data = QByteArray::fromRawData(data: reinterpret_cast<char *>(&commandParams), |
335 | size: sizeof commandParams); |
336 | return sendCommand(ogf: QBluezConst::OgfLinkControl, ocf: QBluezConst::OcfLeConnectionUpdate, parameters: data); |
337 | } |
338 | |
339 | bool HciManager::sendConnectionParameterUpdateRequest(quint16 handle, |
340 | const QLowEnergyConnectionParameters ¶ms) |
341 | { |
342 | ConnectionUpdateData connUpdateData = connectionUpdateData(params); |
343 | |
344 | // Vol 3, part A, 4 |
345 | struct SignalingPacket { |
346 | quint8 code; |
347 | quint8 identifier; |
348 | quint16 length; |
349 | } signalingPacket; |
350 | signalingPacket.code = 0x12; |
351 | signalingPacket.identifier = ++sigPacketIdentifier; |
352 | const quint16 sigPacketLen = sizeof connUpdateData; |
353 | signalingPacket.length = qToLittleEndian(source: sigPacketLen); |
354 | |
355 | L2CapHeader ; |
356 | const quint16 = sizeof signalingPacket + sigPacketLen; |
357 | l2CapHeader.length = qToLittleEndian(source: l2CapHeaderLen); |
358 | l2CapHeader.channelId = qToLittleEndian(source: quint16(SIGNALING_CHANNEL_ID)); |
359 | |
360 | // Vol 2, part E, 5.4.2 |
361 | AclData aclData; |
362 | aclData.handle = qToLittleEndian(source: handle); // Works because the next two values are zero. |
363 | aclData.pbFlag = 0; |
364 | aclData.bcFlag = 0; |
365 | aclData.dataLen = qToLittleEndian(source: quint16(sizeof l2CapHeader + l2CapHeaderLen)); |
366 | |
367 | struct iovec iv[5]; |
368 | quint8 packetType = HCI_ACL_PKT; |
369 | iv[0].iov_base = &packetType; |
370 | iv[0].iov_len = 1; |
371 | iv[1].iov_base = &aclData; |
372 | iv[1].iov_len = sizeof aclData; |
373 | iv[2].iov_base = &l2CapHeader; |
374 | iv[2].iov_len = sizeof l2CapHeader; |
375 | iv[3].iov_base = &signalingPacket; |
376 | iv[3].iov_len = sizeof signalingPacket; |
377 | iv[4].iov_base = &connUpdateData; |
378 | iv[4].iov_len = sizeof connUpdateData; |
379 | while (writev(fd: hciSocket, iovec: iv, count: sizeof iv / sizeof *iv) < 0) { |
380 | if (errno == EAGAIN || errno == EINTR) |
381 | continue; |
382 | qCDebug(QT_BT_BLUEZ()) << "failure writing HCI ACL packet:" << strerror(errno); |
383 | return false; |
384 | } |
385 | qCDebug(QT_BT_BLUEZ) << "Connection Update Request packet sent successfully" ; |
386 | return true; |
387 | } |
388 | |
389 | /*! |
390 | * Process all incoming HCI events. Function cannot process anything else but events. |
391 | */ |
392 | void HciManager::_q_readNotify() |
393 | { |
394 | unsigned char buffer[qMax<int>(HCI_MAX_EVENT_SIZE, b: sizeof(AclData))]; |
395 | |
396 | const auto size = ::read(fd: hciSocket, buf: buffer, nbytes: sizeof(buffer)); |
397 | if (size < 0) { |
398 | if (errno != EAGAIN && errno != EINTR) |
399 | qCWarning(QT_BT_BLUEZ) << "Failed reading HCI events:" << qt_error_string(errno); |
400 | |
401 | return; |
402 | } |
403 | |
404 | switch (buffer[0]) { |
405 | case HCI_EVENT_PKT: |
406 | handleHciEventPacket(data: buffer + 1, size: size - 1); |
407 | break; |
408 | case HCI_ACL_PKT: |
409 | handleHciAclPacket(data: buffer + 1, size: size - 1); |
410 | break; |
411 | default: |
412 | qCWarning(QT_BT_BLUEZ) << "Ignoring unexpected HCI packet type" << buffer[0]; |
413 | } |
414 | } |
415 | |
416 | void HciManager::handleHciEventPacket(const quint8 *data, int size) |
417 | { |
418 | if (size < HCI_EVENT_HDR_SIZE) { |
419 | qCWarning(QT_BT_BLUEZ) << "Unexpected HCI event packet size:" << size; |
420 | return; |
421 | } |
422 | |
423 | hci_event_hdr * = (hci_event_hdr *) data; |
424 | |
425 | size -= HCI_EVENT_HDR_SIZE; |
426 | data += HCI_EVENT_HDR_SIZE; |
427 | |
428 | if (header->plen != size) { |
429 | qCWarning(QT_BT_BLUEZ) << "Invalid HCI event packet size" ; |
430 | return; |
431 | } |
432 | |
433 | qCDebug(QT_BT_BLUEZ) << "HCI event triggered, type:" << (HciManager::HciEvent)header->evt |
434 | << "type code:" << Qt::hex << header->evt; |
435 | |
436 | switch ((HciManager::HciEvent)header->evt) { |
437 | case HciEvent::EVT_ENCRYPT_CHANGE: { |
438 | const evt_encrypt_change *event = (evt_encrypt_change *) data; |
439 | qCDebug(QT_BT_BLUEZ) << "HCI Encrypt change, status:" |
440 | << (event->status == 0 ? "Success" : "Failed" ) |
441 | << "handle:" << Qt::hex << event->handle |
442 | << "encrypt:" << event->encrypt; |
443 | |
444 | QBluetoothAddress remoteDevice = addressForConnectionHandle(handle: event->handle); |
445 | if (!remoteDevice.isNull()) |
446 | emit encryptionChangedEvent(address: remoteDevice, wasSuccess: event->status == 0); |
447 | } break; |
448 | case HciEvent::EVT_CMD_COMPLETE: { |
449 | auto * const event = reinterpret_cast<const evt_cmd_complete *>(data); |
450 | static_assert(sizeof *event == 3, "unexpected struct size" ); |
451 | |
452 | // There is always a status byte right after the generic structure. |
453 | Q_ASSERT(size > static_cast<int>(sizeof *event)); |
454 | const quint8 status = data[sizeof *event]; |
455 | const auto additionalData = QByteArray(reinterpret_cast<const char *>(data) |
456 | + sizeof *event + 1, size - sizeof *event - 1); |
457 | emit commandCompleted(opCode: event->opcode, status, data: additionalData); |
458 | } break; |
459 | case HciEvent::EVT_LE_META_EVENT: |
460 | handleLeMetaEvent(data); |
461 | break; |
462 | default: |
463 | break; |
464 | } |
465 | |
466 | } |
467 | |
468 | void HciManager::handleHciAclPacket(const quint8 *data, int size) |
469 | { |
470 | if (size < int(sizeof(AclData))) { |
471 | qCWarning(QT_BT_BLUEZ) << "Unexpected HCI ACL packet size" ; |
472 | return; |
473 | } |
474 | |
475 | quint16 rawAclData[sizeof(AclData) / sizeof(quint16)]; |
476 | rawAclData[0] = bt_get_le16(ptr: data); |
477 | rawAclData[1] = bt_get_le16(ptr: data + sizeof(quint16)); |
478 | const AclData *aclData = reinterpret_cast<AclData *>(rawAclData); |
479 | data += sizeof *aclData; |
480 | size -= sizeof *aclData; |
481 | |
482 | // Consider only directed, complete messages. |
483 | if ((aclData->pbFlag != 0 && aclData->pbFlag != 2) || aclData->bcFlag != 0) |
484 | return; |
485 | |
486 | if (size < aclData->dataLen) { |
487 | qCWarning(QT_BT_BLUEZ) << "HCI ACL packet data size" << size |
488 | << "is smaller than specified size" << aclData->dataLen; |
489 | return; |
490 | } |
491 | |
492 | // qCDebug(QT_BT_BLUEZ) << "handle:" << aclData->handle << "PB:" << aclData->pbFlag |
493 | // << "BC:" << aclData->bcFlag << "data len:" << aclData->dataLen; |
494 | |
495 | if (size < int(sizeof(L2CapHeader))) { |
496 | qCWarning(QT_BT_BLUEZ) << "Unexpected HCI ACL packet size" ; |
497 | return; |
498 | } |
499 | L2CapHeader = *reinterpret_cast<const L2CapHeader*>(data); |
500 | l2CapHeader.channelId = qFromLittleEndian(source: l2CapHeader.channelId); |
501 | l2CapHeader.length = qFromLittleEndian(source: l2CapHeader.length); |
502 | data += sizeof l2CapHeader; |
503 | size -= sizeof l2CapHeader; |
504 | if (size < l2CapHeader.length) { |
505 | qCWarning(QT_BT_BLUEZ) << "L2Cap payload size" << size << "is smaller than specified size" |
506 | << l2CapHeader.length; |
507 | return; |
508 | } |
509 | // qCDebug(QT_BT_BLUEZ) << "l2cap channel id:" << l2CapHeader.channelId |
510 | // << "payload length:" << l2CapHeader.length; |
511 | if (l2CapHeader.channelId != SECURITY_CHANNEL_ID) |
512 | return; |
513 | if (*data != 0xa) // "Signing Information". Spec v4.2, Vol 3, Part H, 3.6.6 |
514 | return; |
515 | if (size != 17) { |
516 | qCWarning(QT_BT_BLUEZ) << "Unexpected key size" << size << "in Signing Information packet" ; |
517 | return; |
518 | } |
519 | BluezUint128 csrk; |
520 | memcpy(dest: &csrk, src: data + 1, n: sizeof csrk); |
521 | const bool isRemoteKey = aclData->pbFlag == 2; |
522 | emit signatureResolvingKeyReceived(connHandle: aclData->handle, remoteKey: isRemoteKey, csrk); |
523 | } |
524 | |
525 | void HciManager::handleLeMetaEvent(const quint8 *data) |
526 | { |
527 | // Spec v5.3, Vol 4, part E, 7.7.65.* |
528 | switch (*data) { |
529 | case 0x1: // HCI_LE_Connection_Complete |
530 | case 0xA: // HCI_LE_Enhanced_Connection_Complete |
531 | { |
532 | const quint16 handle = bt_get_le16(ptr: data + 2); |
533 | emit connectionComplete(handle); |
534 | break; |
535 | } |
536 | case 0x3: { |
537 | // TODO: From little endian! |
538 | struct ConnectionUpdateData { |
539 | quint8 status; |
540 | quint16 handle; |
541 | quint16 interval; |
542 | quint16 latency; |
543 | quint16 timeout; |
544 | } __attribute((packed)); |
545 | const auto * const updateData |
546 | = reinterpret_cast<const ConnectionUpdateData *>(data + 1); |
547 | if (updateData->status == 0) { |
548 | QLowEnergyConnectionParameters params; |
549 | const double interval = qFromLittleEndian(source: updateData->interval) * 1.25; |
550 | params.setIntervalRange(minimum: interval, maximum: interval); |
551 | params.setLatency(qFromLittleEndian(source: updateData->latency)); |
552 | params.setSupervisionTimeout(qFromLittleEndian(source: updateData->timeout) * 10); |
553 | emit connectionUpdate(handle: qFromLittleEndian(source: updateData->handle), parameters: params); |
554 | } |
555 | break; |
556 | } |
557 | default: |
558 | break; |
559 | } |
560 | } |
561 | |
562 | QT_END_NAMESPACE |
563 | |
564 | #include "moc_hcimanager_p.cpp" |
565 | |