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