blob: e806096f730c4e2a98b8a3112cce853754344bda [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 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 "qbluetoothserviceinfo.h"
#include "qbluetoothserviceinfo_p.h"
#include "qbluetoothserver_p.h"
#include <QtCore/private/qeventdispatcher_winrt_p.h>
#include <QtCore/QLoggingCategory>
#ifdef CLASSIC_APP_BUILD
#define Q_OS_WINRT
#endif
#include <qfunctions_winrt.h>
#include <wrl.h>
#include <windows.devices.bluetooth.h>
#include <windows.devices.bluetooth.rfcomm.h>
#include <windows.foundation.h>
#include <windows.networking.sockets.h>
#include <windows.storage.streams.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Devices::Bluetooth;
using namespace ABI::Windows::Devices::Bluetooth::Rfcomm;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Networking::Sockets;
using namespace ABI::Windows::Storage::Streams;
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
#define TYPE_VOID 0
#define TYPE_UINT8 8
#define TYPE_UINT16 9
#define TYPE_UINT32 10
#define TYPE_UINT64 11
//#define TYPE_UINT128 12
#define TYPE_INT8 16
#define TYPE_INT16 17
#define TYPE_INT32 18
#define TYPE_INT64 19
//#define TYPE_INT128 20
#define TYPE_UUID16 25
#define TYPE_UUID32 26
#define TYPE_UUID128 28
#define TYPE_STRING_BASE 32
#define TYPE_BOOLEAN 40
#define TYPE_SEQUENCE_BASE 48
#define TYPE_ALTERNATIVE_BASE 56
#define TYPE_URL_BASE 64
extern QHash<QBluetoothServerPrivate *, int> __fakeServerPorts;
inline bool typeIsOfBase(unsigned char type, unsigned char baseType)
{
return ((type & baseType) == baseType);
}
qint64 getLengthForBaseType(unsigned char type, ComPtr<IDataReader> &reader)
{
const bool isOfBase = (typeIsOfBase(type, TYPE_STRING_BASE)
|| typeIsOfBase(type, TYPE_SEQUENCE_BASE)
|| typeIsOfBase(type, TYPE_ALTERNATIVE_BASE)
|| typeIsOfBase(type, TYPE_URL_BASE));
if (!isOfBase)
return -1;
HRESULT hr;
// For these types, the first 5 bits are the base type followed by 3 bits
// describing the size index. This index decides how many additional bits
// have to be read to get the type's length.
const unsigned char sizeIndex = (type & 0x7);
switch (sizeIndex) {
case 5: {
quint8 length;
hr = reader->ReadByte(&length);
RETURN_IF_FAILED("Could not read length from buffer", return -1);
return length;
} case 6: {
quint16 length;
hr = reader->ReadUInt16(&length);
RETURN_IF_FAILED("Could not read length from buffer", return -1);
return length;
} case 7: {
quint32 length;
hr = reader->ReadUInt32(&length);
RETURN_IF_FAILED("Could not read length from buffer", return -1);
return length;
}
}
return -1;
}
bool writeStringHelper(const QString &string, ComPtr<IDataWriter> writer)
{
HRESULT hr;
const int stringLength = string.length();
unsigned char type = TYPE_STRING_BASE;
if (stringLength < 0) {
qCWarning(QT_BT_WINRT) << "Can not write invalid string value to buffer";
return false;
} if (stringLength <= 0xff) {
type += 5;
hr = writer->WriteByte(type);
RETURN_FALSE_IF_FAILED("Could not write string type data.");
hr = writer->WriteByte(stringLength);
RETURN_FALSE_IF_FAILED("Could not write string length.");
} else if (stringLength <= 0xffff) {
type += 6;
hr = writer->WriteByte(type);
RETURN_FALSE_IF_FAILED("Could not write string type data.");
hr = writer->WriteUInt16(stringLength);
RETURN_FALSE_IF_FAILED("Could not write string length.");
} else {
type += 7;
hr = writer->WriteByte(type);
RETURN_FALSE_IF_FAILED("Could not write string type data.");
hr = writer->WriteUInt32(stringLength);
RETURN_FALSE_IF_FAILED("Could not write string length.");
}
HStringReference stringRef(reinterpret_cast<LPCWSTR>(string.utf16()));
quint32 bytesWritten;
hr = writer->WriteString(stringRef.Get(), &bytesWritten);
RETURN_FALSE_IF_FAILED("Could not write string to buffer.");
if (bytesWritten != string.length()) {
qCWarning(QT_BT_WINRT) << "Did not write full value to buffer";
return false;
}
return true;
}
bool repairProfileDescriptorListIfNeeded(ComPtr<IBuffer> &buffer)
{
ComPtr<IDataReaderStatics> dataReaderStatics;
HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(),
&dataReaderStatics);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IDataReader> reader;
hr = dataReaderStatics->FromBuffer(buffer.Get(), reader.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
BYTE type;
hr = reader->ReadByte(&type);
Q_ASSERT_SUCCEEDED(hr);
if (!typeIsOfBase(type, TYPE_SEQUENCE_BASE)) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "Malformed profile descriptor list read";
return false;
}
qint64 length = getLengthForBaseType(type, reader);
hr = reader->ReadByte(&type);
Q_ASSERT_SUCCEEDED(hr);
// We have to "repair" the structure if the outer sequence contains a uuid directly
if (type == TYPE_UUID16 && length == 4) {
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Repairing profile descriptor list";
quint16 uuid;
hr = reader->ReadUInt16(&uuid);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IDataWriter> writer;
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
&writer);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(TYPE_SEQUENCE_BASE + 5);
Q_ASSERT_SUCCEEDED(hr);
// 8 == length of nested sequence (outer sequence -> inner sequence -> uuid and version)
hr = writer->WriteByte(8);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(TYPE_SEQUENCE_BASE + 5);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(7);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(TYPE_UUID16);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt16(uuid);
Q_ASSERT_SUCCEEDED(hr);
// Write default version to make WinRT happy
hr = writer->WriteByte(TYPE_UINT16);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt16(0x100);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->DetachBuffer(&buffer);
Q_ASSERT_SUCCEEDED(hr);
}
return true;
}
static ComPtr<IBuffer> bufferFromAttribute(const QVariant &attribute)
{
ComPtr<IDataWriter> writer;
HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
&writer);
Q_ASSERT_SUCCEEDED(hr);
switch (int(attribute.type())) {
case QMetaType::Void:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::Void:";
hr = writer->WriteByte(TYPE_VOID);
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::UChar:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::UChar:" << attribute.value<quint8>();
hr = writer->WriteByte(TYPE_UINT8);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(attribute.value<quint8>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::UShort:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::UShort:" << attribute.value<quint16>();
hr = writer->WriteByte(TYPE_UINT16);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt16(attribute.value<quint16>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::UInt:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::UInt:" << attribute.value<quint32>();
hr = writer->WriteByte(TYPE_UINT32);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt32(attribute.value<quint32>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::ULongLong:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::ULongLong:" << attribute.value<quint64>();
hr = writer->WriteByte(TYPE_UINT64);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt64(attribute.value<quint64>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::Char:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::Char:" << attribute.value<qint8>();
hr = writer->WriteByte(TYPE_INT8);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(attribute.value<qint8>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::Short:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::Short:" << attribute.value<qint16>();
hr = writer->WriteByte(TYPE_INT16);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteInt16(attribute.value<qint16>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::Int:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::Int:" << attribute.value<qint32>();
hr = writer->WriteByte(TYPE_INT32);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteInt32(attribute.value<qint32>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::LongLong:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::LongLong:" << attribute.value<qint64>();
hr = writer->WriteByte(TYPE_INT64);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteInt64(attribute.value<qint64>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::QByteArray: {
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::QByteArray:" << attribute.value<QString>();
const QString stringValue = QString::fromLatin1(attribute.value<QByteArray>().toHex());
const bool writeSuccess = writeStringHelper(stringValue, writer);
if (!writeSuccess)
return nullptr;
break;
}
case QMetaType::QString: {
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::QString:" << attribute.value<QString>();
const QString stringValue = attribute.value<QString>();
const bool writeSucces = writeStringHelper(stringValue, writer);
if (!writeSucces)
return nullptr;
break;
}
case QMetaType::Bool:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering attribute of type QMetaType::Bool:" << attribute.value<bool>();
hr = writer->WriteByte(TYPE_BOOLEAN);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(attribute.value<bool>());
Q_ASSERT_SUCCEEDED(hr);
break;
case QMetaType::QUrl:
qCWarning(QT_BT_WINRT) << "Don't know how to register QMetaType::QUrl";
return nullptr;
break;
case QVariant::UserType:
if (attribute.userType() == qMetaTypeId<QBluetoothUuid>()) {
QBluetoothUuid uuid = attribute.value<QBluetoothUuid>();
const int minimumSize = uuid.minimumSize();
switch (uuid.minimumSize()) {
case 0:
qCWarning(QT_BT_WINRT) << "Don't know how to register Uuid of length 0";
return nullptr;
case 2:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering Uuid attribute with length 2:" << uuid;
hr = writer->WriteByte(TYPE_UUID16);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt16(uuid.toUInt16());
Q_ASSERT_SUCCEEDED(hr);
break;
case 4:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering Uuid attribute with length 4:" << uuid;
hr = writer->WriteByte(TYPE_UUID32);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt32(uuid.toUInt32());
Q_ASSERT_SUCCEEDED(hr);
break;
case 16:
default:
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registering Uuid attribute:" << uuid;
hr = writer->WriteByte(TYPE_UUID128);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteGuid(uuid);
Q_ASSERT_SUCCEEDED(hr);
break;
}
} else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
qCDebug(QT_BT_WINRT) << "Registering sequence attribute";
const QBluetoothServiceInfo::Sequence *sequence =
static_cast<const QBluetoothServiceInfo::Sequence *>(attribute.data());
ComPtr<IDataWriter> tmpWriter;
HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
&tmpWriter);
Q_ASSERT_SUCCEEDED(hr);
for (const QVariant &v : *sequence) {
ComPtr<IBuffer> tmpBuffer = bufferFromAttribute(v);
if (!tmpBuffer) {
qCWarning(QT_BT_WINRT) << "Could not create buffer from attribute in sequence";
return nullptr;
}
quint32 l;
hr = tmpBuffer->get_Length(&l);
Q_ASSERT_SUCCEEDED(hr);
hr = tmpWriter->WriteBuffer(tmpBuffer.Get());
Q_ASSERT_SUCCEEDED(hr);
}
ComPtr<IBuffer> tmpBuffer;
hr = tmpWriter->DetachBuffer(&tmpBuffer);
Q_ASSERT_SUCCEEDED(hr);
// write sequence length
quint32 length;
tmpBuffer->get_Length(&length);
Q_ASSERT_SUCCEEDED(hr);
unsigned char type = TYPE_SEQUENCE_BASE;
length += 1;
if (length <= 0xff) {
type += 5;
hr = writer->WriteByte(type);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteByte(length);
Q_ASSERT_SUCCEEDED(hr);
} else if (length <= 0xffff) {
type += 6;
hr = writer->WriteByte(type);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt16(length);
Q_ASSERT_SUCCEEDED(hr);
} else {
type += 7;
hr = writer->WriteByte(type);
Q_ASSERT_SUCCEEDED(hr);
hr = writer->WriteUInt32(length);
Q_ASSERT_SUCCEEDED(hr);
}
// write sequence data
hr = writer->WriteBuffer(tmpBuffer.Get());
Q_ASSERT_SUCCEEDED(hr);
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registered sequence attribute with length" << length;
} else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
qCWarning(QT_BT_WINRT) << "Don't know how to register user type Alternative";
return nullptr;
}
break;
default:
qCWarning(QT_BT_WINRT) << "Unknown variant type" << attribute.userType();
return nullptr;
}
ComPtr<IBuffer> buffer;
hr = writer->DetachBuffer(&buffer);
Q_ASSERT_SUCCEEDED(hr);
return buffer;
}
QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate()
: registered(false)
{
}
QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate()
{
}
bool QBluetoothServiceInfoPrivate::isRegistered() const
{
return registered;
}
bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &localAdapter)
{
Q_UNUSED(localAdapter);
if (registered)
return false;
if (protocolDescriptor(QBluetoothUuid::Rfcomm).isEmpty()) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "Only RFCOMM services can be registered on WinRT";
return false;
}
QBluetoothServerPrivate *sPriv = __fakeServerPorts.key(serverChannel());
if (!sPriv)
return false;
HRESULT hr;
QBluetoothUuid uuid = attributes.value(QBluetoothServiceInfo::ServiceId).value<QBluetoothUuid>();
ComPtr<IRfcommServiceIdStatics> serviceIdStatics;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Rfcomm_RfcommServiceId).Get(),
IID_PPV_ARGS(&serviceIdStatics));
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IRfcommServiceId> serviceId;
hr = serviceIdStatics->FromUuid(uuid, &serviceId);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IRfcommServiceProviderStatics> providerStatics;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_Rfcomm_RfcommServiceProvider).Get(),
IID_PPV_ARGS(&providerStatics));
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncOperation<RfcommServiceProvider *>> op;
hr = QEventDispatcherWinRT::runOnXamlThread([providerStatics, serviceId, &op]
{
HRESULT hr;
hr = providerStatics->CreateAsync(serviceId.Get(), &op);
return hr;
});
Q_ASSERT_SUCCEEDED(hr);
hr = QWinRTFunctions::await(op, serviceProvider.GetAddressOf());
if (hr == HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE)) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "No bluetooth adapter available.";
return false;
} else {
Q_ASSERT_SUCCEEDED(hr);
}
ComPtr<IStreamSocketListener> listener = sPriv->listener();
if (!listener) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "Could not obtain listener from server.";
return false;
}
HString serviceIdHString;
serviceId->AsString(serviceIdHString.GetAddressOf());
Q_ASSERT_SUCCEEDED(hr);
const QString serviceIdString = QString::fromWCharArray(WindowsGetStringRawBuffer(serviceIdHString.Get(), nullptr));
//tell the server what service name our listener should have
//and start the real listener
bool result = sPriv->initiateActiveListening(serviceIdString);
if (!result) {
return false;
}
result = writeSdpAttributes();
if (!result) {
qCWarning(QT_BT_WINRT) << "Could not write SDP attributes.";
return false;
}
qCDebug(QT_BT_WINRT) << "SDP attributes written.";
ComPtr<IRfcommServiceProvider2> serviceProvider2;
hr = serviceProvider.As(&serviceProvider2);
Q_ASSERT_SUCCEEDED(hr);
hr = QEventDispatcherWinRT::runOnXamlThread([listener, serviceProvider2] {
HRESULT hr;
hr = serviceProvider2->StartAdvertisingWithRadioDiscoverability(listener.Get(), true);
return hr;
});
if (FAILED(hr)) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "Could not start advertising. Check your SDP data.";
return false;
}
registered = true;
return true;
}
bool QBluetoothServiceInfoPrivate::unregisterService()
{
if (!registered)
return false;
QBluetoothServerPrivate *sPriv = __fakeServerPorts.key(serverChannel());
if (!sPriv) {
//QBluetoothServer::close() was called without prior call to unregisterService().
//Now it is unregistered anyway.
registered = false;
return true;
}
bool result = sPriv->deactivateActiveListening();
if (!result)
return false;
HRESULT hr;
hr = serviceProvider->StopAdvertising();
Q_ASSERT_SUCCEEDED(hr);
registered = false;
return true;
}
bool QBluetoothServiceInfoPrivate::writeSdpAttributes()
{
if (!serviceProvider)
return false;
ComPtr<IDataWriter> writer;
HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
&writer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IMap<UINT32, IBuffer *>> rawAttributes;
hr = serviceProvider->get_SdpRawAttributes(&rawAttributes);
Q_ASSERT_SUCCEEDED(hr);
const QList<quint16> keys = attributes.keys();
for (quint16 key : keys) {
// The SDP Class Id List and RFCOMM and L2CAP protocol descriptors are automatically
// generated by the RfcommServiceProvider. Do not specify it in the SDP raw attribute map.
if (key == QBluetoothServiceInfo::ServiceClassIds
|| key == QBluetoothServiceInfo::ProtocolDescriptorList)
continue;
const QVariant attribute = attributes.value(key);
HRESULT hr;
ComPtr<IBuffer> buffer = bufferFromAttribute(attribute);
if (!buffer) {
qCWarning(QT_BT_WINRT) << "Could not create buffer from attribute with id:" << key;
return false;
}
// Other backends support a wrong structure in profile descriptor list. In order to make
// WinRT accept the list without breaking existing apps we have to repair this structure.
if (key == QBluetoothServiceInfo::BluetoothProfileDescriptorList) {
if (!repairProfileDescriptorListIfNeeded(buffer)) {
qCWarning(QT_BT_WINRT) << Q_FUNC_INFO << "Error while checking/repairing structure of profile descriptor list";
return false;
}
}
hr = writer->WriteBuffer(buffer.Get());
Q_ASSERT_SUCCEEDED(hr);
hr = writer->DetachBuffer(&buffer);
Q_ASSERT_SUCCEEDED(hr);
boolean replaced;
hr = rawAttributes->Insert(key, buffer.Get(), &replaced);
Q_ASSERT_SUCCEEDED(hr);
Q_ASSERT(!replaced);
qCDebug(QT_BT_WINRT) << Q_FUNC_INFO << "Registered attribute" << QString::number(key, 16).rightJustified(4, '0') << "with value" << attribute;
}
return true;
}
QT_END_NAMESPACE