blob: c34443aa1dbc3f2bb593b12a19852cc058357814 [file] [log] [blame]
/****************************************************************************
**
** 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)