Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
hcimanager.cpp
Go to the documentation of this file.
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
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
21
23
24HciManager::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(hciSocket);
37 hciSocket = -1;
38 return;
39 }
40
41 struct sockaddr_hci addr;
42
43 memset(&addr, 0, sizeof(struct sockaddr_hci));
44 addr.hci_dev = hciDev;
45 addr.hci_family = AF_BLUETOOTH;
46
47 if (::bind(hciSocket, (struct sockaddr *) (&addr), sizeof(addr)) < 0) {
48 qCWarning(QT_BT_BLUEZ) << "HCI bind failed:" << strerror(errno);
49 close(hciSocket);
50 hciSocket = hciDev = -1;
51 return;
52 }
53
54 notifier = new QSocketNotifier(hciSocket, QSocketNotifier::Read, this);
55 connect(notifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_readNotify()));
56
57}
58
60{
61 if (hciSocket >= 0)
62 ::close(hciSocket);
63
64}
65
67{
68 if (hciSocket && hciDev >= 0)
69 return true;
70 return false;
71}
72
73int HciManager::hciForAddress(const QBluetoothAddress &deviceAdapter)
74{
75 if (hciSocket < 0)
76 return -1;
77
78 bdaddr_t adapter;
79 convertAddress(deviceAdapter.toUInt64(), 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(devListSize);
88 if (!devRequestList)
89 return -1;
90
91 QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> p(devRequestList);
92
93 memset(p.data(), 0, devListSize);
94 p->dev_num = HCI_MAX_DEV;
95 devRequest = p->dev_req;
96
97 if (ioctl(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(hciSocket, HCIGETDEVINFO, &devInfo) < 0) {
103 continue;
104 }
105
106 int result = memcmp(&adapter, &devInfo.bdaddr, 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 */
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(event))
125 return true;
126
128 socklen_t length = sizeof(hci_filter);
129 if (getsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, &length) < 0) {
130 qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings";
131 return false;
132 }
133
135 hci_filter_set_event(static_cast<int>(event), &filter);
136 //hci_filter_all_events(&filter);
137
138 if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, 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
147{
148 if (!isValid())
149 return false;
150
152 socklen_t length = sizeof(hci_filter);
153 if (getsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, &length) < 0) {
154 qCWarning(QT_BT_BLUEZ) << "Cannot retrieve HCI filter settings";
155 return false;
156 }
157
160
161 if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, 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
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 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(hciSocket, iv, 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 */
203{
204 if (!isValid())
205 return;
206
209
210 if (setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &filter, 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
219{
220 if (!isValid())
221 return QBluetoothAddress();
222
224 hci_conn_list_req *infoList;
225
226 const int maxNoOfConnections = 20;
227 infoList = (hci_conn_list_req *)
228 malloc(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(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)
246 }
247
248 return QBluetoothAddress();
249}
250
252{
253 if (!isValid())
254 return QList<quint16>();
255
257 hci_conn_list_req *infoList;
258
259 const int maxNoOfConnections = 20;
260 infoList = (hci_conn_list_req *)
261 malloc(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(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(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
295quint16 forceIntervalIntoRange(double connectionInterval)
296{
297 return qMin<double>(qMax<double>(7.5, connectionInterval), 4000) / 1.25;
298}
299
307{
309 const quint16 minInterval = forceIntervalIntoRange(params.minimumInterval());
310 const quint16 maxInterval = forceIntervalIntoRange(params.maximumInterval());
311 data.minInterval = qToLittleEndian(minInterval);
312 data.maxInterval = qToLittleEndian(maxInterval);
313 const quint16 latency = qMax<quint16>(0, qMin<quint16>(params.latency(), 499));
314 data.slaveLatency = qToLittleEndian(latency);
315 const quint16 timeout
316 = qMax<quint16>(100, qMin<quint16>(32000, params.supervisionTimeout())) / 10;
317 data.timeout = qToLittleEndian(timeout);
318 return data;
319}
320
323{
324 struct CommandParams {
327 quint16 minCeLength;
328 quint16 maxCeLength;
329 } commandParams;
330 commandParams.handle = qToLittleEndian(handle);
331 commandParams.data = connectionUpdateData(params);
332 commandParams.minCeLength = 0;
333 commandParams.maxCeLength = qToLittleEndian(quint16(0xffff));
334 const QByteArray data = QByteArray::fromRawData(reinterpret_cast<char *>(&commandParams),
335 sizeof commandParams);
337}
338
341{
343
344 // Vol 3, part A, 4
345 struct SignalingPacket {
346 quint8 code;
347 quint8 identifier;
349 } signalingPacket;
350 signalingPacket.code = 0x12;
351 signalingPacket.identifier = ++sigPacketIdentifier;
352 const quint16 sigPacketLen = sizeof connUpdateData;
353 signalingPacket.length = qToLittleEndian(sigPacketLen);
354
355 L2CapHeader l2CapHeader;
356 const quint16 l2CapHeaderLen = sizeof signalingPacket + sigPacketLen;
357 l2CapHeader.length = qToLittleEndian(l2CapHeaderLen);
358 l2CapHeader.channelId = qToLittleEndian(quint16(SIGNALING_CHANNEL_ID));
359
360 // Vol 2, part E, 5.4.2
361 AclData aclData;
362 aclData.handle = qToLittleEndian(handle); // Works because the next two values are zero.
363 aclData.pbFlag = 0;
364 aclData.bcFlag = 0;
365 aclData.dataLen = qToLittleEndian(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(hciSocket, iv, 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
392void HciManager::_q_readNotify()
393{
394 unsigned char buffer[qMax<int>(HCI_MAX_EVENT_SIZE, sizeof(AclData))];
395
396 const auto size = ::read(hciSocket, buffer, 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(buffer + 1, size - 1);
407 break;
408 case HCI_ACL_PKT:
409 handleHciAclPacket(buffer + 1, size - 1);
410 break;
411 default:
412 qCWarning(QT_BT_BLUEZ) << "Ignoring unexpected HCI packet type" << buffer[0];
413 }
414}
415
416void 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 *header = (hci_event_hdr *) data;
424
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) {
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(event->handle);
445 if (!remoteDevice.isNull())
446 emit encryptionChangedEvent(remoteDevice, event->status == 0);
447 } break;
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(event->opcode, status, additionalData);
458 } break;
460 handleLeMetaEvent(data);
461 break;
462 default:
463 break;
464 }
465
466}
467
468void 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(data);
477 rawAclData[1] = bt_get_le16(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 l2CapHeader = *reinterpret_cast<const L2CapHeader*>(data);
500 l2CapHeader.channelId = qFromLittleEndian(l2CapHeader.channelId);
501 l2CapHeader.length = qFromLittleEndian(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(&csrk, data + 1, sizeof csrk);
521 const bool isRemoteKey = aclData->pbFlag == 2;
522 emit signatureResolvingKeyReceived(aclData->handle, isRemoteKey, csrk);
523}
524
525void 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(data + 2);
534 break;
535 }
536 case 0x3: {
537 // TODO: From little endian!
538 struct ConnectionUpdateData {
539 quint8 status;
541 quint16 interval;
542 quint16 latency;
544 } __attribute((packed));
545 const auto * const updateData
546 = reinterpret_cast<const ConnectionUpdateData *>(data + 1);
547 if (updateData->status == 0) {
549 const double interval = qFromLittleEndian(updateData->interval) * 1.25;
550 params.setIntervalRange(interval, interval);
551 params.setLatency(qFromLittleEndian(updateData->latency));
552 params.setSupervisionTimeout(qFromLittleEndian(updateData->timeout) * 10);
553 emit connectionUpdate(qFromLittleEndian(updateData->handle), params);
554 }
555 break;
556 }
557 default:
558 break;
559 }
560}
561
563
564#include "moc_hcimanager_p.cpp"
bdaddr_t bdaddr
#define HCI_EVENT_PKT
static void hci_filter_all_events(struct hci_filter *f)
#define HCIGETDEVLIST
#define SIGNALING_CHANNEL_ID
#define SOL_HCI
static void hci_filter_set_ptype(int t, struct hci_filter *f)
#define HCI_FILTER
#define LE_LINK
#define ACL_LINK
#define HCIGETCONNLIST
static quint16 bt_get_le16(const void *ptr)
#define HCI_MAX_DEV
#define BTPROTO_HCI
#define HCI_EVENT_HDR_SIZE
#define ESCO_LINK
static void hci_filter_clear(struct hci_filter *f)
#define HCI_COMMAND_PKT
#define HCI_ACL_PKT
#define SCO_LINK
#define HCIGETDEVINFO
#define SECURITY_CHANNEL_ID
#define opCodePack(ogf, ocf)
static void hci_filter_set_event(int e, struct hci_filter *f)
#define HCI_MAX_EVENT_SIZE
DarwinBluetooth::LECBManagerNotifier * notifier
bool sendConnectionUpdateCommand(quint16 handle, const QLowEnergyConnectionParameters &params)
bool sendCommand(QBluezConst::OpCodeGroupField ogf, QBluezConst::OpCodeCommandField ocf, const QByteArray &parameters)
void stopEvents()
QList< quint16 > activeLowEnergyConnections() const
bool isValid() const
bool sendConnectionParameterUpdateRequest(quint16 handle, const QLowEnergyConnectionParameters &params)
bool monitorEvent(HciManager::HciEvent event)
void commandCompleted(quint16 opCode, quint8 status, const QByteArray &data)
QBluetoothAddress addressForConnectionHandle(quint16 handle) const
bool monitorAclPackets()
void connectionUpdate(quint16 handle, const QLowEnergyConnectionParameters &parameters)
void encryptionChangedEvent(const QBluetoothAddress &address, bool wasSuccess)
void connectionComplete(quint16 handle)
void signatureResolvingKeyReceived(quint16 connHandle, bool remoteKey, BluezUint128 csrk)
\inmodule QtBluetooth
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
The QLowEnergyConnectionParameters class is used when requesting or reporting an update of the parame...
\inmodule QtCore
Definition qobject.h:103
void clear()
Definition qset.h:61
bool contains(const T &value) const
Definition qset.h:71
\inmodule QtCore
\inmodule QtCore
quint16 forceIntervalIntoRange(double connectionInterval)
ConnectionUpdateData connectionUpdateData(const QLowEnergyConnectionParameters &params)
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
static void convertAddress(const quint64 from, quint8(&to)[6])
static QString header(const QString &name)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
constexpr T qFromLittleEndian(T source)
Definition qendian.h:178
constexpr T qToLittleEndian(T source)
Definition qendian.h:176
__attribute((__weak__)) extern int __ulock_wait2(uint32_t operation
quint16 maxInterval
quint16 minInterval
Q_DECL_COLD_FUNCTION Q_CORE_EXPORT QString qt_error_string(int errorCode=-1)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLuint64 GLenum void * handle
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLbitfield GLuint64 timeout
[4]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum type
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
void ** params
struct _cl_event * event
GLenum const void * addr
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLuint GLenum GLsizei GLsizei GLint GLint GLboolean packed
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
unsigned short quint16
Definition qtypes.h:48
unsigned char quint8
Definition qtypes.h:46
ReturnedValue read(const char *data)
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QTcpSocket * socket
[1]
QHostInfo info
[0]
socketLayer bind(QHostAddress::Any, 4000)
quint16 handle
quint16 length
quint16 channelId
\inmodule QtCore
Definition quuid.h:58