summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qbluetoothservicediscoveryagent_win.cpp')
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_win.cpp437
1 files changed, 437 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp
new file mode 100644
index 00000000..c34443aa
--- /dev/null
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp
@@ -0,0 +1,437 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothservicediscoveryagent.h"
+#include "qbluetoothservicediscoveryagent_p.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QLibrary>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QThread>
+#include <QtCore/QUrl>
+
+#include <initguid.h>
+#include <winsock2.h>
+#include <qt_windows.h>
+
+#if defined(Q_CC_MINGW)
+// Workaround for MinGW headers declaring BluetoothSdpGetElementData incorrectly.
+# define BluetoothSdpGetElementData _BluetoothSdpGetElementData_notok
+# include <bluetoothapis.h>
+# undef BluetoothSdpGetElementData
+ extern "C" DWORD WINAPI BluetoothSdpGetElementData(LPBYTE, ULONG, PSDP_ELEMENT_DATA);
+#else
+# include <bluetoothapis.h>
+#endif
+
+#include <ws2bth.h>
+#include <iostream>
+
+QT_BEGIN_NAMESPACE
+
+struct FindServiceResult {
+ QBluetoothServiceInfo info;
+ Qt::HANDLE hSearch = INVALID_HANDLE_VALUE;
+ int systemError = NO_ERROR;
+};
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
+
+static QList<QVariant> spdContainerToVariantList(LPBYTE containerStream, ULONG containerLength);
+
+static QVariant spdElementToVariant(const SDP_ELEMENT_DATA &element)
+{
+ QVariant variant;
+
+ switch (element.type) {
+ case SDP_TYPE_UINT: {
+ switch (element.specificType) {
+ case SDP_ST_UINT128:
+ //Not supported!!!
+ break;
+ case SDP_ST_UINT64:
+ variant = QVariant::fromValue<quint64>(element.data.uint64);
+ break;
+ case SDP_ST_UINT32:
+ variant = QVariant::fromValue<quint32>(element.data.uint32);
+ break;
+ case SDP_ST_UINT16:
+ variant = QVariant::fromValue<quint16>(element.data.uint16);
+ break;
+ case SDP_ST_UINT8:
+ variant = QVariant::fromValue<quint8>(element.data.uint8);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case SDP_TYPE_INT: {
+ switch (element.specificType) {
+ case SDP_ST_INT128: {
+ //Not supported!!!
+ break;
+ }
+ case SDP_ST_INT64:
+ variant = QVariant::fromValue<qint64>(element.data.int64);
+ break;
+ case SDP_ST_INT32:
+ variant = QVariant::fromValue<qint32>(element.data.int32);
+ break;
+ case SDP_ST_INT16:
+ variant = QVariant::fromValue<qint16>(element.data.int16);
+ break;
+ case SDP_ST_INT8:
+ variant = QVariant::fromValue<qint8>(element.data.int8);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case SDP_TYPE_UUID: {
+ switch (element.specificType) {
+ case SDP_ST_UUID128:
+ variant = QVariant::fromValue(QBluetoothUuid(element.data.uuid128));
+ break;
+ case SDP_ST_UUID32:
+ variant = QVariant::fromValue(QBluetoothUuid(quint32(element.data.uuid32)));
+ break;
+ case SDP_ST_UUID16:
+ variant = QVariant::fromValue(QBluetoothUuid(quint16(element.data.uuid16)));
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case SDP_TYPE_STRING: {
+ const QByteArray stringBuffer(reinterpret_cast<const char*>(element.data.string.value), element.data.string.length);
+ variant = QVariant::fromValue<QString>(QString::fromLocal8Bit(stringBuffer));
+ break;
+ }
+ case SDP_TYPE_URL: {
+ const QString urlString = QString::fromLocal8Bit(reinterpret_cast<const char*>(element.data.url.value),
+ int(element.data.url.length));
+ const QUrl url(urlString);
+ if (url.isValid())
+ variant = QVariant::fromValue<QUrl>(url);
+ break;
+ }
+ case SDP_TYPE_SEQUENCE: {
+ const QList<QVariant> sequenceList = spdContainerToVariantList(element.data.sequence.value,
+ element.data.sequence.length);
+ const QBluetoothServiceInfo::Sequence sequence(sequenceList);
+ variant = QVariant::fromValue(sequence);
+ break;
+ }
+ case SDP_TYPE_ALTERNATIVE: {
+ const QList<QVariant> alternativeList = spdContainerToVariantList(element.data.alternative.value,
+ element.data.alternative.length);
+ const QBluetoothServiceInfo::Alternative alternative(alternativeList);
+ variant = QVariant::fromValue(alternative);
+ break;
+ }
+ case SDP_TYPE_BOOLEAN:
+ variant = QVariant::fromValue<bool>(bool(element.data.booleanVal));
+ break;
+ case SDP_TYPE_NIL:
+ break;
+ default:
+ break;
+ }
+
+ return variant;
+}
+
+static QList<QVariant> spdContainerToVariantList(LPBYTE containerStream, ULONG containerLength)
+{
+ HBLUETOOTH_CONTAINER_ELEMENT iter = nullptr;
+ SDP_ELEMENT_DATA element = {};
+
+ QList<QVariant> sequence;
+
+ for (;;) {
+ const DWORD result = ::BluetoothSdpGetContainerElementData(containerStream,
+ containerLength,
+ &iter,
+ &element);
+
+ if (result == ERROR_SUCCESS) {
+ const QVariant variant = spdElementToVariant(element);
+ sequence.append(variant);
+ } else if (result == ERROR_NO_MORE_ITEMS) {
+ break;
+ } else if (result == ERROR_INVALID_PARAMETER) {
+ break;
+ }
+ }
+
+ return sequence;
+}
+
+#if defined(Q_CC_MINGW)
+# define SDP_CALLBACK
+#else
+# define SDP_CALLBACK QT_WIN_CALLBACK
+#endif
+static BOOL SDP_CALLBACK bluetoothSdpCallback(ULONG attributeId, LPBYTE valueStream, ULONG streamSize, LPVOID param)
+{
+ QBluetoothServiceInfo *result = static_cast<QBluetoothServiceInfo*>(param);
+
+ SDP_ELEMENT_DATA element = {};
+
+ if (::BluetoothSdpGetElementData(valueStream, streamSize, &element) == ERROR_SUCCESS) {
+ switch (element.type) {
+ case SDP_TYPE_UINT:
+ case SDP_TYPE_INT:
+ case SDP_TYPE_UUID:
+ case SDP_TYPE_STRING:
+ case SDP_TYPE_URL:
+ case SDP_TYPE_BOOLEAN:
+ case SDP_TYPE_SEQUENCE:
+ case SDP_TYPE_ALTERNATIVE: {
+ const QVariant variant = spdElementToVariant(element);
+
+ result->setAttribute(attributeId, variant);
+ break;
+ }
+ case SDP_TYPE_NIL:
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+enum {
+ WSAControlFlags = LUP_FLUSHCACHE
+ | LUP_RETURN_NAME
+ | LUP_RETURN_TYPE
+ | LUP_RETURN_ADDR
+ | LUP_RETURN_BLOB
+ | LUP_RETURN_COMMENT
+};
+
+static FindServiceResult findNextService(HANDLE hSearch)
+{
+ FindServiceResult result;
+ result.hSearch = hSearch;
+
+ QByteArray resultBuffer(2048, 0);
+ WSAQUERYSET *resultQuery = reinterpret_cast<WSAQUERYSET*>(resultBuffer.data());
+ DWORD resultBufferSize = DWORD(resultBuffer.size());
+ const int resultCode = ::WSALookupServiceNext(hSearch,
+ WSAControlFlags,
+ &resultBufferSize,
+ resultQuery);
+
+ if (resultCode == SOCKET_ERROR) {
+ result.systemError = ::WSAGetLastError();
+ if (result.systemError == WSA_E_NO_MORE)
+ ::WSALookupServiceEnd(hSearch);
+ return result;
+ }
+
+ if (resultQuery->lpBlob
+ && ::BluetoothSdpEnumAttributes(resultQuery->lpBlob->pBlobData,
+ resultQuery->lpBlob->cbSize,
+ bluetoothSdpCallback,
+ &result.info)) {
+ return result;
+ } else {
+ result.systemError = GetLastError();
+ }
+ return result;
+}
+
+static FindServiceResult findFirstService(const QBluetoothAddress &address)
+{
+ WSAData wsadata = {};
+ FindServiceResult result;
+
+ // IPv6 requires Winsock v2.0 or better.
+ if (::WSAStartup(MAKEWORD(2, 0), &wsadata) != 0) {
+ result.systemError = ::WSAGetLastError();
+ return result;
+ }
+
+ const QString addressAsString = QStringLiteral("(%1)").arg(address.toString());
+ QVector<WCHAR> addressAsWChar(addressAsString.size() + 1);
+ addressAsString.toWCharArray(addressAsWChar.data());
+
+ GUID protocol = L2CAP_PROTOCOL_UUID; //Search for L2CAP and RFCOMM services
+
+ WSAQUERYSET serviceQuery = {};
+ serviceQuery.dwSize = sizeof(WSAQUERYSET);
+ serviceQuery.lpServiceClassId = &protocol;
+ serviceQuery.dwNameSpace = NS_BTH;
+ serviceQuery.dwNumberOfCsAddrs = 0;
+ serviceQuery.lpszContext = addressAsWChar.data();
+
+ HANDLE hSearch = nullptr;
+ const int resultCode = ::WSALookupServiceBegin(&serviceQuery,
+ WSAControlFlags,
+ &hSearch);
+ if (resultCode == SOCKET_ERROR) {
+ result.systemError = ::WSAGetLastError();
+ ::WSALookupServiceEnd(hSearch);
+ return result;
+ }
+ return findNextService(hSearch);
+}
+
+QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
+ QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
+ : error(QBluetoothServiceDiscoveryAgent::NoError),
+ state(Inactive),
+ deviceDiscoveryAgent(0),
+ mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
+ singleDevice(false),
+ pendingStop(false),
+ pendingFinish(false),
+ q_ptr(qp)
+{
+ Q_UNUSED(deviceAdapter);
+
+ threadFind = new QThread;
+ threadWorkerFind = new ThreadWorkerFind;
+ threadWorkerFind->moveToThread(threadFind);
+ connect(threadWorkerFind, &ThreadWorkerFind::findFinished, this, &QBluetoothServiceDiscoveryAgentPrivate::_q_nextSdpScan);
+ connect(threadFind, &QThread::finished, threadWorkerFind, &ThreadWorkerFind::deleteLater);
+ connect(threadFind, &QThread::finished, threadFind, &QThread::deleteLater);
+ threadFind->start();
+}
+
+QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
+{
+ if (pendingFinish)
+ stop();
+ if (threadFind)
+ threadFind->quit();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
+{
+ if (!pendingFinish) {
+ pendingFinish = true;
+ pendingStop = false;
+
+ const auto threadWorker = threadWorkerFind;
+ QMetaObject::invokeMethod(threadWorkerFind, [threadWorker, address]()
+ {
+ const FindServiceResult result = findFirstService(address);
+ emit threadWorker->findFinished(QVariant::fromValue(result));
+ }, Qt::QueuedConnection);
+ }
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::stop()
+{
+ pendingStop = true;
+}
+
+bool QBluetoothServiceDiscoveryAgentPrivate::serviceMatches(const QBluetoothServiceInfo &info)
+{
+ if (uuidFilter.isEmpty())
+ return true;
+
+ if (uuidFilter.contains(info.serviceUuid()))
+ return true;
+
+ const QList<QBluetoothUuid> serviceClassUuids = info.serviceClassUuids();
+ for (const QBluetoothUuid &uuid : serviceClassUuids)
+ if (uuidFilter.contains(uuid))
+ return true;
+
+ return false;
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::_q_nextSdpScan(const QVariant &input)
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ auto result = input.value<FindServiceResult>();
+
+ if (pendingStop) {
+ ::WSALookupServiceEnd(result.hSearch);
+ pendingStop = false;
+ pendingFinish = false;
+ emit q->canceled();
+ } else {
+ if (result.systemError == WSA_E_NO_MORE) {
+ result.systemError = NO_ERROR;
+ } else if (result.systemError != NO_ERROR) {
+ if (result.hSearch != INVALID_HANDLE_VALUE)
+ ::WSALookupServiceEnd(result.hSearch);
+ error = (result.systemError == ERROR_INVALID_HANDLE) ?
+ QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError
+ : QBluetoothServiceDiscoveryAgent::InputOutputError;
+ errorString = qt_error_string(result.systemError);
+ qCWarning(QT_BT_WINDOWS) << errorString;
+ emit q->error(this->error);
+ } else {
+
+ if (serviceMatches(result.info)) {
+ result.info.setDevice(discoveredDevices.at(0));
+ if (result.info.isValid()) {
+ if (!isDuplicatedService(result.info)) {
+ discoveredServices.append(result.info);
+ emit q->serviceDiscovered(result.info);
+ }
+ }
+ }
+
+ const auto threadWorker = threadWorkerFind;
+ const auto hSearch = result.hSearch;
+ QMetaObject::invokeMethod(threadWorkerFind, [threadWorker, hSearch]()
+ {
+ FindServiceResult result = findNextService(hSearch);
+ emit threadWorker->findFinished(QVariant::fromValue(result));
+ }, Qt::QueuedConnection);
+ return;
+ }
+ pendingFinish = false;
+ _q_serviceDiscoveryFinished();
+ }
+}
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(FindServiceResult)