diff options
Diffstat (limited to 'src/bluetooth/qbluetoothservicediscoveryagent_win.cpp')
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_win.cpp | 437 |
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) |