Revision control

++ // Copyright (C) 2016 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
++
++ #include "qbluetoothserviceinfo.h"
++ #include "qbluetoothserviceinfo_p.h"
++
++ #include "bluez/bluez5_helper_p.h"
++ #include "bluez/profilemanager1_p.h"
++
++ #include <QtCore/QLoggingCategory>
++ #include <QtCore/QXmlStreamWriter>
++ #include <QtCore/QAtomicInt>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++ using namespace QtBluetoothPrivate; // for D-Bus wrappers
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
++
++ static QAtomicInt pathCounter;
++
++ static void writeAttribute(QXmlStreamWriter *stream, const QVariant &attribute)
++ {
++ const QString unsignedFormat(QStringLiteral("0x%1"));
++
++ switch (attribute.typeId()) {
++ case QMetaType::Void:
++ stream->writeEmptyElement(QStringLiteral("nil"));
++ break;
++ case QMetaType::UChar:
++ stream->writeEmptyElement(QStringLiteral("uint8"));
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(attribute.value<quint8>(), 2, 16,
++ QLatin1Char('0')));
++ break;
++ case QMetaType::UShort:
++ stream->writeEmptyElement(QStringLiteral("uint16"));
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(attribute.value<quint16>(), 4, 16,
++ QLatin1Char('0')));
++ break;
++ case QMetaType::UInt:
++ stream->writeEmptyElement(QStringLiteral("uint32"));
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(attribute.value<quint32>(), 8, 16,
++ QLatin1Char('0')));
++ break;
++ case QMetaType::Char:
++ stream->writeEmptyElement(QStringLiteral("int8"));
++ stream->writeAttribute(QStringLiteral("value"),
++ QString::number(attribute.value<qint8>()));
++ break;
++ case QMetaType::Short:
++ stream->writeEmptyElement(QStringLiteral("int16"));
++ stream->writeAttribute(QStringLiteral("value"),
++ QString::number(attribute.value<qint16>()));
++ break;
++ case QMetaType::Int:
++ stream->writeEmptyElement(QStringLiteral("int32"));
++ stream->writeAttribute(QStringLiteral("value"),
++ QString::number(attribute.value<qint32>()));
++ break;
++ case QMetaType::QByteArray:
++ stream->writeEmptyElement(QStringLiteral("text"));
++ stream->writeAttribute(QStringLiteral("value"),
++ QString::fromLatin1(attribute.value<QByteArray>().toHex().constData()));
++ stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("hex"));
++ break;
++ case QMetaType::QString:
++ stream->writeEmptyElement(QStringLiteral("text"));
++ stream->writeAttribute(QStringLiteral("value"), attribute.value<QString>());
++ stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("normal"));
++ break;
++ case QMetaType::Bool:
++ stream->writeEmptyElement(QStringLiteral("boolean"));
++ if (attribute.value<bool>())
++ stream->writeAttribute(QStringLiteral("value"), QStringLiteral("true"));
++ else
++ stream->writeAttribute(QStringLiteral("value"), QStringLiteral("false"));
++ break;
++ case QMetaType::QUrl:
++ stream->writeEmptyElement(QStringLiteral("url"));
++ stream->writeAttribute(QStringLiteral("value"), attribute.value<QUrl>().toString());
++ break;
++ default:
++ if (attribute.userType() == qMetaTypeId<QBluetoothUuid>()) {
++ stream->writeEmptyElement(QStringLiteral("uuid"));
++
++ QBluetoothUuid uuid = attribute.value<QBluetoothUuid>();
++ switch (uuid.minimumSize()) {
++ case 0:
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(quint16(0), 4, 16, QLatin1Char('0')));
++ break;
++ case 2:
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(uuid.toUInt16(), 4, 16,
++ QLatin1Char('0')));
++ break;
++ case 4:
++ stream->writeAttribute(QStringLiteral("value"),
++ unsignedFormat.arg(uuid.toUInt32(), 8, 16,
++ QLatin1Char('0')));
++ break;
++ case 16:
++ stream->writeAttribute(QStringLiteral("value"), uuid.toString().mid(1, 36));
++ break;
++ default:
++ stream->writeAttribute(QStringLiteral("value"), uuid.toString().mid(1, 36));
++ }
++ } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
++ stream->writeStartElement(QStringLiteral("sequence"));
++ const QBluetoothServiceInfo::Sequence *sequence =
++ static_cast<const QBluetoothServiceInfo::Sequence *>(attribute.data());
++ for (const QVariant &v : *sequence)
++ writeAttribute(stream, v);
++ stream->writeEndElement();
++ } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
++ const QBluetoothServiceInfo::Alternative *alternative =
++ static_cast<const QBluetoothServiceInfo::Alternative *>(attribute.data());
++ for (const QVariant &v : *alternative)
++ writeAttribute(stream, v);
++ stream->writeEndElement();
++ } else {
++ qCWarning(QT_BT_BLUEZ) << "Unknown variant type" << attribute.userType();
++ }
++ }
++ }
++
++ QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate()
++ : serviceRecord(0), registered(false)
++ {
++ initializeBluez5();
++ service = new OrgBluezProfileManager1Interface(QStringLiteral("org.bluez"),
++ QStringLiteral("/org/bluez"),
++ QDBusConnection::systemBus(), this);
++ }
++
++ QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate()
++ {
++ }
++
++ bool QBluetoothServiceInfoPrivate::isRegistered() const
++ {
++ return registered;
++ }
++
++ bool QBluetoothServiceInfoPrivate::unregisterService()
++ {
++ if (!registered)
++ return false;
++
++ if (profilePath.isEmpty())
++ return false;
++
++ QDBusPendingReply<> reply = service->UnregisterProfile(QDBusObjectPath(profilePath));
++ reply.waitForFinished();
++ if (reply.isError()) {
++ qCWarning(QT_BT_BLUEZ) << "Cannot unregister profile:" << profilePath
++ << reply.error().message();
++ return false;
++ }
++ profilePath.clear();
++
++ registered = false;
++ return true;
++ }
++
++ // TODO Implement local adapter behavior
++ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress & /*localAdapter*/)
++ {
++ if (registered)
++ return false;
++
++ QString xmlServiceRecord;
++
++ QXmlStreamWriter stream(&xmlServiceRecord);
++ stream.setAutoFormatting(true);
++
++ stream.writeStartDocument(QStringLiteral("1.0"));
++
++ stream.writeStartElement(QStringLiteral("record"));
++
++ const QString unsignedFormat(QStringLiteral("0x%1"));
++
++ QMap<quint16, QVariant>::ConstIterator i = attributes.constBegin();
++ while (i != attributes.constEnd()) {
++ stream.writeStartElement(QStringLiteral("attribute"));
++ stream.writeAttribute(QStringLiteral("id"), unsignedFormat.arg(i.key(), 4, 16, QLatin1Char('0')));
++ writeAttribute(&stream, i.value());
++ stream.writeEndElement();
++
++ ++i;
++ }
++
++ stream.writeEndElement();
++
++ stream.writeEndDocument();
++
++ // create path
++ profilePath = u"/qt/profile"_s;
++ profilePath.append(QString::fromLatin1("/%1%2/%3")
++ .arg(sanitizeNameForDBus(QCoreApplication::applicationName()))
++ .arg(QCoreApplication::applicationPid())
++ .arg(pathCounter.fetchAndAddOrdered(1)));
++
++ QVariantMap mapping;
++ mapping.insert(QStringLiteral("ServiceRecord"), xmlServiceRecord);
++ mapping.insert(QStringLiteral("Role"), QStringLiteral("server"));
++
++ // Strategy to pick service uuid
++ // 1.) use serviceUuid()
++ // 2.) use first custom uuid if available
++ // 3.) use first service class uuid
++ QBluetoothUuid profileUuid =
++ attributes.value(QBluetoothServiceInfo::ServiceId).value<QBluetoothUuid>();
++ QBluetoothUuid firstCustomUuid;
++ if (profileUuid.isNull()) {
++ const QVariant var = attributes.value(QBluetoothServiceInfo::ServiceClassIds);
++ if (var.isValid()) {
++ const QBluetoothServiceInfo::Sequence seq =
++ var.value<QBluetoothServiceInfo::Sequence>();
++ for (const auto &e : seq) {
++ auto tempUuid = e.value<QBluetoothUuid>();
++ if (tempUuid.isNull())
++ continue;
++
++ int size = tempUuid.minimumSize();
++ if (size == 2 || size == 4) { // Base UUID derived
++ if (profileUuid.isNull())
++ profileUuid = tempUuid;
++ } else if (firstCustomUuid.isNull()) {
++ firstCustomUuid = tempUuid;
++ }
++ }
++ }
++ }
++
++ if (!firstCustomUuid.isNull())
++ profileUuid = firstCustomUuid;
++
++ QString uuidString = profileUuid.toString(QUuid::WithoutBraces);
++
++ qCDebug(QT_BT_BLUEZ) << "Registering profile under" << profilePath << uuidString;
++
++ QDBusPendingReply<> reply =
++ service->RegisterProfile(QDBusObjectPath(profilePath), uuidString, mapping);
++ reply.waitForFinished();
++ if (reply.isError()) {
++ qCWarning(QT_BT_BLUEZ) << "Cannot register profile" << reply.error().message();
++ return false;
++ }
++
++ registered = true;
++ return true;
++ }
++
++ QT_END_NAMESPACE