Revision control
Copy as Markdown
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qbluetoothservicediscoveryagent.h"
#include "qbluetoothservicediscoveryagent_p.h"
#include "qbluetoothutils_winrt_p.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/private/qfunctions_winrt_p.h>
#include <functional>
#include <robuffer.h>
#include <windows.devices.enumeration.h>
#include <windows.devices.bluetooth.h>
#include <windows.foundation.collections.h>
#include <windows.networking.h>
#include <windows.storage.streams.h>
#include <wrl.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Devices;
using namespace ABI::Windows::Devices::Bluetooth;
using namespace ABI::Windows::Devices::Bluetooth::Rfcomm;
using namespace ABI::Windows::Devices::Enumeration;
using namespace ABI::Windows::Storage::Streams;
typedef Collections::IKeyValuePair<UINT32, IBuffer *> ValueItem;
typedef Collections::IIterable<ValueItem *> ValueIterable;
typedef Collections::IIterator<ValueItem *> ValueIterator;
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS)
#define TYPE_UINT8 8
#define TYPE_UINT16 9
#define TYPE_UINT32 10
#define TYPE_SHORT_UUID 25
#define TYPE_LONG_UUID 28
#define TYPE_STRING 37
#define TYPE_SEQUENCE 53
#define BREAK_IF_FAILED(msg) RETURN_IF_FAILED(msg, break)
// Helper to reverse given uchar array
static void reverseArray(uchar data[], size_t length)
{
for (size_t i = length; i > length/2; i--)
std::swap(data[length - i], data[i - 1]);
}
class QWinRTBluetoothServiceDiscoveryWorker : public QObject
{
Q_OBJECT
public:
explicit QWinRTBluetoothServiceDiscoveryWorker(quint64 targetAddress,
QBluetoothServiceDiscoveryAgent::DiscoveryMode mode);
~QWinRTBluetoothServiceDiscoveryWorker();
void start();
Q_SIGNALS:
void serviceFound(quint64 deviceAddress, const QBluetoothServiceInfo &info);
void scanFinished(quint64 deviceAddress);
void errorOccured();
private:
HRESULT onBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status);
void processServiceSearchResult(quint64 address, ComPtr<IVectorView<RfcommDeviceService*>> services);
QBluetoothServiceInfo::Sequence readSequence(ComPtr<IDataReader> dataReader, bool *ok, quint8 *bytesRead, int depth = 0);
private:
quint64 m_targetAddress;
QBluetoothServiceDiscoveryAgent::DiscoveryMode m_mode;
};
QWinRTBluetoothServiceDiscoveryWorker::QWinRTBluetoothServiceDiscoveryWorker(quint64 targetAddress,
QBluetoothServiceDiscoveryAgent::DiscoveryMode mode)
: m_targetAddress(targetAddress)
, m_mode(mode)
{
}
QWinRTBluetoothServiceDiscoveryWorker::~QWinRTBluetoothServiceDiscoveryWorker()
{
}
void QWinRTBluetoothServiceDiscoveryWorker::start()
{
ComPtr<IBluetoothDeviceStatics> deviceStatics;
HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &deviceStatics);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<BluetoothDevice *>> deviceFromAddressOperation;
hr = deviceStatics->FromBluetoothAddressAsync(m_targetAddress, &deviceFromAddressOperation);
Q_ASSERT_SUCCEEDED(hr);
hr = deviceFromAddressOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothDevice *>>
(this, &QWinRTBluetoothServiceDiscoveryWorker::onBluetoothDeviceFoundAsync).Get());
Q_ASSERT_SUCCEEDED(hr);
}
HRESULT QWinRTBluetoothServiceDiscoveryWorker::onBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status)
{
if (status != Completed) {
qCDebug(QT_BT_WINDOWS) << "Could not find device";
emit errorOccured();
return S_OK;
}
ComPtr<IBluetoothDevice> device;
HRESULT hr;
hr = op->GetResults(&device);
Q_ASSERT_SUCCEEDED(hr);
if (!device) {
qCDebug(QT_BT_WINDOWS) << "The returned device is NULL";
emit errorOccured();
return S_OK;
}
quint64 address;
device->get_BluetoothAddress(&address);
ComPtr<IBluetoothDevice3> device3;
hr = device.As(&device3);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<RfcommDeviceServicesResult *>> serviceOp;
const BluetoothCacheMode cacheMode = m_mode == QBluetoothServiceDiscoveryAgent::MinimalDiscovery
? BluetoothCacheMode_Cached : BluetoothCacheMode_Uncached;
hr = device3->GetRfcommServicesWithCacheModeAsync(cacheMode, &serviceOp);
Q_ASSERT_SUCCEEDED(hr);
hr = serviceOp->put_Completed(Callback<IAsyncOperationCompletedHandler<RfcommDeviceServicesResult *>>
([address, this](IAsyncOperation<RfcommDeviceServicesResult *> *op, AsyncStatus status)
{
if (status != Completed) {
qCDebug(QT_BT_WINDOWS) << "Could not obtain service list";
emit errorOccured();
return S_OK;
}
ComPtr<IRfcommDeviceServicesResult> result;
HRESULT hr = op->GetResults(&result);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IVectorView<RfcommDeviceService*>> commServices;
hr = result->get_Services(&commServices);
Q_ASSERT_SUCCEEDED(hr);
processServiceSearchResult(address, commServices);
return S_OK;
}).Get());
Q_ASSERT_SUCCEEDED(hr);
return S_OK;
}
void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 address, ComPtr<IVectorView<RfcommDeviceService*>> services)
{
quint32 size;
HRESULT hr;
hr = services->get_Size(&size);
Q_ASSERT_SUCCEEDED(hr);
for (quint32 i = 0; i < size; ++i) {
ComPtr<IRfcommDeviceService> service;
hr = services->GetAt(i, &service);
Q_ASSERT_SUCCEEDED(hr);
HString name;
hr = service->get_ConnectionServiceName(name.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
const QString serviceName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr));
ComPtr<ABI::Windows::Networking::IHostName> host;
hr = service->get_ConnectionHostName(host.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
HString hostName;
hr = host->get_RawName(hostName.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
const QString qHostName = QString::fromWCharArray(WindowsGetStringRawBuffer(hostName.Get(),
nullptr));
ComPtr<IRfcommServiceId> id;
hr = service->get_ServiceId(&id);
Q_ASSERT_SUCCEEDED(hr);
GUID guid;
hr = id->get_Uuid(&guid);
const QBluetoothUuid uuid(guid);
Q_ASSERT_SUCCEEDED(hr);
QBluetoothServiceInfo info;
info.setAttribute(0xBEEF, QVariant(qHostName));
info.setAttribute(0xBEF0, QVariant(serviceName));
info.setServiceName(serviceName);
info.setServiceUuid(uuid);
ComPtr<IAsyncOperation<IMapView<UINT32, IBuffer *> *>> op;
hr = service->GetSdpRawAttributesAsync(op.GetAddressOf());
if (FAILED(hr)) {
emit errorOccured();
qCDebug(QT_BT_WINDOWS) << "Check manifest capabilities";
continue;
}
ComPtr<IMapView<UINT32, IBuffer *>> mapView;
hr = QWinRTFunctions::await(op, mapView.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
// TODO timeout
ComPtr<ValueIterable> iterable;
ComPtr<ValueIterator> iterator;
hr = mapView.As(&iterable);
if (FAILED(hr))
continue;
boolean current = false;
hr = iterable->First(&iterator);
if (FAILED(hr))
continue;
hr = iterator->get_HasCurrent(¤t);
if (FAILED(hr))
continue;
while (SUCCEEDED(hr) && current) {
ComPtr<ValueItem> item;
hr = iterator->get_Current(&item);
if (FAILED(hr))
continue;
UINT32 key;
hr = item->get_Key(&key);
if (FAILED(hr))
continue;
ComPtr<IBuffer> buffer;
hr = item->get_Value(&buffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IDataReader> dataReader;
ComPtr<IDataReaderStatics> dataReaderStatics;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), &dataReaderStatics);
Q_ASSERT_SUCCEEDED(hr);
hr = dataReaderStatics->FromBuffer(buffer.Get(), dataReader.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
BYTE type;
hr = dataReader->ReadByte(&type);
BREAK_IF_FAILED("type");
if (type == TYPE_UINT8) {
quint8 value;
hr = dataReader->ReadByte(&value);
BREAK_IF_FAILED("uint8 value");
info.setAttribute(key, value);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "UINT8" << Qt::hex << value;
} else if (type == TYPE_UINT16) {
quint16 value;
hr = dataReader->ReadUInt16(&value);
BREAK_IF_FAILED("uint16 value");
info.setAttribute(key, value);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "UINT16" << Qt::hex << value;
} else if (type == TYPE_UINT32) {
quint32 value;
hr = dataReader->ReadUInt32(&value);
BREAK_IF_FAILED("uint32 value");
info.setAttribute(key, value);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "UINT32" << Qt::hex << value;
} else if (type == TYPE_SHORT_UUID) {
quint16 value;
hr = dataReader->ReadUInt16(&value);
BREAK_IF_FAILED("short uuid");
const QBluetoothUuid uuid(value);
info.setAttribute(key, uuid);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "UUID" << Qt::hex << uuid;
} else if (type == TYPE_LONG_UUID) {
GUID value;
hr = dataReader->ReadGuid(&value);
BREAK_IF_FAILED("long uuid");
// The latter 8 bytes are in reverse order
reverseArray(value.Data4, sizeof(value.Data4)/sizeof(value.Data4[0]));
const QBluetoothUuid uuid(value);
info.setAttribute(key, uuid);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "UUID" << Qt::hex << uuid;
} else if (type == TYPE_STRING) {
BYTE length;
hr = dataReader->ReadByte(&length);
BREAK_IF_FAILED("string length");
HString value;
hr = dataReader->ReadString(length, value.GetAddressOf());
BREAK_IF_FAILED("string value");
const QString str = QString::fromWCharArray(WindowsGetStringRawBuffer(value.Get(), nullptr));
info.setAttribute(key, str);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "STRING" << str;
} else if (type == TYPE_SEQUENCE) {
bool ok;
QBluetoothServiceInfo::Sequence sequence = readSequence(dataReader, &ok, nullptr);
if (ok) {
info.setAttribute(key, sequence);
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "SEQUENCE" << sequence;
} else {
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type << "SEQUENCE ERROR";
}
} else {
qCDebug(QT_BT_WINDOWS) << "UUID" << uuid << "KEY" << Qt::hex << key << "TYPE" << Qt::dec << type;
}
hr = iterator->MoveNext(¤t);
}
// Windows is only able to discover Rfcomm services but the according protocolDescriptor is
// not always set in the raw attribute map. If we encounter a service like that we should
// fill the protocol descriptor ourselves.
if (info.protocolDescriptor(QBluetoothUuid::ProtocolUuid::Rfcomm).isEmpty()) {
QBluetoothServiceInfo::Sequence protocolDescriptorList;
QBluetoothServiceInfo::Sequence protocol;
protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Rfcomm))
<< QVariant::fromValue(0);
protocolDescriptorList.append(QVariant::fromValue(protocol));
info.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
}
emit serviceFound(address, info);
}
emit scanFinished(address);
deleteLater();
}
QBluetoothServiceInfo::Sequence QWinRTBluetoothServiceDiscoveryWorker::readSequence(ComPtr<IDataReader> dataReader, bool *ok, quint8 *bytesRead, int depth)
{
if (ok)
*ok = false;
if (bytesRead)
*bytesRead = 0;
QBluetoothServiceInfo::Sequence result;
if (!dataReader)
return result;
if (depth > QBluetoothServiceDiscoveryAgentPrivate::kMaxSdpRecursionDepth) {
qCWarning(QT_BT_WINDOWS) << "SDP sequence recursion depth exceeded";
return result;
}
quint8 remainingLength;
auto verifyLength = [&remainingLength](size_t dataLen) -> bool {
if (size_t(remainingLength) < dataLen) {
qCWarning(QT_BT_WINDOWS)
<< "Remaining length of the sequence is not enough to fit the new value."
<< "Remaining length:" << remainingLength << "Size to read:" << dataLen;
return false;
}
return true;
};
#define RETURN_IF_INVALID_LENGTH(dataLen) \
if (!verifyLength(dataLen)) { \
result.clear(); \
return result; \
}
HRESULT hr = dataReader->ReadByte(&remainingLength);
RETURN_IF_FAILED("remainingLength", return result);
if (bytesRead)
*bytesRead += 1;
BYTE type;
RETURN_IF_INVALID_LENGTH(sizeof(type));
hr = dataReader->ReadByte(&type);
RETURN_IF_FAILED("type", return result);
remainingLength -= 1;
if (bytesRead)
*bytesRead += 1;
while (true) {
switch (type) {
case TYPE_UINT8: {
quint8 value;
RETURN_IF_INVALID_LENGTH(sizeof(value));
hr = dataReader->ReadByte(&value);
RETURN_IF_FAILED("uint8 value", return result);
result.append(QVariant::fromValue(value));
remainingLength -= 1;
if (bytesRead)
*bytesRead += 1;
break;
}
case TYPE_UINT16: {
quint16 value;
RETURN_IF_INVALID_LENGTH(sizeof(value));
hr = dataReader->ReadUInt16(&value);
RETURN_IF_FAILED("uint16 value", return result);
result.append(QVariant::fromValue(value));
remainingLength -= 2;
if (bytesRead)
*bytesRead += 2;
break;
}
case TYPE_UINT32: {
quint32 value;
RETURN_IF_INVALID_LENGTH(sizeof(value));
hr = dataReader->ReadUInt32(&value);
RETURN_IF_FAILED("uint32 value", return result);
result.append(QVariant::fromValue(value));
remainingLength -= 4;
if (bytesRead)
*bytesRead += 4;
break;
}
case TYPE_SHORT_UUID: {
quint16 b;
RETURN_IF_INVALID_LENGTH(sizeof(b));
hr = dataReader->ReadUInt16(&b);
RETURN_IF_FAILED("short uuid", return result);
const QBluetoothUuid uuid(b);
result.append(QVariant::fromValue(uuid));
remainingLength -= 2;
if (bytesRead)
*bytesRead += 2;
break;
}
case TYPE_LONG_UUID: {
GUID b;
RETURN_IF_INVALID_LENGTH(sizeof(b));
hr = dataReader->ReadGuid(&b);
RETURN_IF_FAILED("long uuid", return result);
// The latter 8 bytes are in reverse order
reverseArray(b.Data4, sizeof(b.Data4)/sizeof(b.Data4[0]));
const QBluetoothUuid uuid(b);
result.append(QVariant::fromValue(uuid));
remainingLength -= sizeof(GUID);
if (bytesRead)
*bytesRead += sizeof(GUID);
break;
}
case TYPE_STRING: {
BYTE length;
RETURN_IF_INVALID_LENGTH(sizeof(length));
hr = dataReader->ReadByte(&length);
RETURN_IF_FAILED("string length", return result);
remainingLength -= 1;
if (bytesRead)
*bytesRead += 1;
RETURN_IF_INVALID_LENGTH(size_t(length));
HString value;
hr = dataReader->ReadString(length, value.GetAddressOf());
RETURN_IF_FAILED("string value", return result);
const QString str = QString::fromWCharArray(WindowsGetStringRawBuffer(value.Get(), nullptr));
result.append(QVariant::fromValue(str));
remainingLength -= length;
if (bytesRead)
*bytesRead += length;
break;
}
case TYPE_SEQUENCE: {
quint8 bytesR;
const QBluetoothServiceInfo::Sequence sequence = readSequence(dataReader, ok, &bytesR, depth + 1);
RETURN_IF_INVALID_LENGTH(size_t(bytesR));
if (*ok)
result.append(QVariant::fromValue(sequence));
else
return result;
remainingLength -= bytesR;
if (bytesRead)
*bytesRead += bytesR;
break;
}
default:
qCDebug(QT_BT_WINDOWS) << "SEQUENCE ERROR" << type;
result.clear();
return result;
}
if (remainingLength == 0)
break;
RETURN_IF_INVALID_LENGTH(sizeof(type));
hr = dataReader->ReadByte(&type);
RETURN_IF_FAILED("next type", return result);
remainingLength -= 1;
if (bytesRead)
*bytesRead += 1;
}
#undef RETURN_IF_INVALID_LENGTH
if (ok)
*ok = true;
return result;
}
QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
: error(QBluetoothServiceDiscoveryAgent::NoError),
state(Inactive),
mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
singleDevice(false),
q_ptr(qp)
{
threadCoInit(this);
// TODO: use local adapter for discovery. Possible?
Q_UNUSED(deviceAdapter);
}
QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
{
releaseWorker();
threadCoUninit(this);
}
void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
{
if (worker)
return;
worker = new QWinRTBluetoothServiceDiscoveryWorker(address.toUInt64(), mode);
connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService, Qt::QueuedConnection);
connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished, Qt::QueuedConnection);
connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
this, &QBluetoothServiceDiscoveryAgentPrivate::onError, Qt::QueuedConnection);
worker->start();
}
void QBluetoothServiceDiscoveryAgentPrivate::stop()
{
releaseWorker();
Q_Q(QBluetoothServiceDiscoveryAgent);
emit q->canceled();
}
void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info)
{
Q_Q(QBluetoothServiceDiscoveryAgent);
//apply uuidFilter
if (!uuidFilter.isEmpty()) {
bool serviceNameMatched = uuidFilter.contains(info.serviceUuid());
bool serviceClassMatched = false;
const QList<QBluetoothUuid> serviceClassUuids
= info.serviceClassUuids();
for (const QBluetoothUuid &id : serviceClassUuids) {
if (uuidFilter.contains(id)) {
serviceClassMatched = true;
break;
}
}
if (!serviceNameMatched && !serviceClassMatched)
return;
}
if (!info.isValid())
return;
QBluetoothServiceInfo returnInfo(info);
bool deviceFound;
for (const QBluetoothDeviceInfo &deviceInfo : std::as_const(discoveredDevices)) {
if (deviceInfo.address().toUInt64() == deviceAddress) {
deviceFound = true;
returnInfo.setDevice(deviceInfo);
break;
}
}
Q_ASSERT(deviceFound);
if (!isDuplicatedService(returnInfo)) {
discoveredServices.append(returnInfo);
qCDebug(QT_BT_WINDOWS) << "Discovered services" << discoveredDevices.at(0).address().toString()
<< returnInfo.serviceName() << returnInfo.serviceUuid()
<< ">>>" << returnInfo.serviceClassUuids();
emit q->serviceDiscovered(returnInfo);
}
}
void QBluetoothServiceDiscoveryAgentPrivate::onScanFinished(quint64 deviceAddress)
{
// The scan for a device's services has finished. Disconnect the
// worker and call the baseclass function which starts the scan for
// the next device if there are any unscanned devices left (or finishes
// the scan if none left)
releaseWorker();
_q_serviceDiscoveryFinished();
}
void QBluetoothServiceDiscoveryAgentPrivate::onError()
{
Q_Q(QBluetoothServiceDiscoveryAgent);
discoveredDevices.clear();
error = QBluetoothServiceDiscoveryAgent::InputOutputError;
errorString = QStringLiteral("errorDescription");
emit q->errorOccurred(error);
}
void QBluetoothServiceDiscoveryAgentPrivate::releaseWorker()
{
if (!worker)
return;
disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService);
disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished);
disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
this, &QBluetoothServiceDiscoveryAgentPrivate::onError);
// mWorker will delete itself as soon as it is done with its discovery
worker = nullptr;
}
QT_END_NAMESPACE
#include <qbluetoothservicediscoveryagent_winrt.moc>