1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qbluetoothserviceinfo.h"
5#include "qbluetoothserviceinfo_p.h"
6
7#include "bluez/bluez5_helper_p.h"
8#include "bluez/profilemanager1_p.h"
9
10#include <QtCore/QLoggingCategory>
11#include <QtCore/QXmlStreamWriter>
12#include <QtCore/QAtomicInt>
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::StringLiterals;
17using namespace QtBluetoothPrivate; // for D-Bus wrappers
18
19Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
20
21static QAtomicInt pathCounter;
22
23static void writeAttribute(QXmlStreamWriter *stream, const QVariant &attribute)
24{
25 const QString unsignedFormat(QStringLiteral("0x%1"));
26
27 switch (attribute.typeId()) {
28 case QMetaType::Void:
29 stream->writeEmptyElement(QStringLiteral("nil"));
30 break;
31 case QMetaType::UChar:
32 stream->writeEmptyElement(QStringLiteral("uint8"));
33 stream->writeAttribute(QStringLiteral("value"),
34 value: unsignedFormat.arg(a: attribute.value<quint8>(), fieldWidth: 2, base: 16,
35 fillChar: QLatin1Char('0')));
36 break;
37 case QMetaType::UShort:
38 stream->writeEmptyElement(QStringLiteral("uint16"));
39 stream->writeAttribute(QStringLiteral("value"),
40 value: unsignedFormat.arg(a: attribute.value<quint16>(), fieldWidth: 4, base: 16,
41 fillChar: QLatin1Char('0')));
42 break;
43 case QMetaType::UInt:
44 stream->writeEmptyElement(QStringLiteral("uint32"));
45 stream->writeAttribute(QStringLiteral("value"),
46 value: unsignedFormat.arg(a: attribute.value<quint32>(), fieldWidth: 8, base: 16,
47 fillChar: QLatin1Char('0')));
48 break;
49 case QMetaType::Char:
50 stream->writeEmptyElement(QStringLiteral("int8"));
51 stream->writeAttribute(QStringLiteral("value"),
52 value: QString::number(attribute.value<qint8>()));
53 break;
54 case QMetaType::Short:
55 stream->writeEmptyElement(QStringLiteral("int16"));
56 stream->writeAttribute(QStringLiteral("value"),
57 value: QString::number(attribute.value<qint16>()));
58 break;
59 case QMetaType::Int:
60 stream->writeEmptyElement(QStringLiteral("int32"));
61 stream->writeAttribute(QStringLiteral("value"),
62 value: QString::number(attribute.value<qint32>()));
63 break;
64 case QMetaType::QByteArray:
65 stream->writeEmptyElement(QStringLiteral("text"));
66 stream->writeAttribute(QStringLiteral("value"),
67 value: QString::fromLatin1(ba: attribute.value<QByteArray>().toHex().constData()));
68 stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("hex"));
69 break;
70 case QMetaType::QString:
71 stream->writeEmptyElement(QStringLiteral("text"));
72 stream->writeAttribute(QStringLiteral("value"), value: attribute.value<QString>());
73 stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("normal"));
74 break;
75 case QMetaType::Bool:
76 stream->writeEmptyElement(QStringLiteral("boolean"));
77 if (attribute.value<bool>())
78 stream->writeAttribute(QStringLiteral("value"), QStringLiteral("true"));
79 else
80 stream->writeAttribute(QStringLiteral("value"), QStringLiteral("false"));
81 break;
82 case QMetaType::QUrl:
83 stream->writeEmptyElement(QStringLiteral("url"));
84 stream->writeAttribute(QStringLiteral("value"), value: attribute.value<QUrl>().toString());
85 break;
86 default:
87 if (attribute.userType() == qMetaTypeId<QBluetoothUuid>()) {
88 stream->writeEmptyElement(QStringLiteral("uuid"));
89
90 QBluetoothUuid uuid = attribute.value<QBluetoothUuid>();
91 switch (uuid.minimumSize()) {
92 case 0:
93 stream->writeAttribute(QStringLiteral("value"),
94 value: unsignedFormat.arg(a: quint16(0), fieldWidth: 4, base: 16, fillChar: QLatin1Char('0')));
95 break;
96 case 2:
97 stream->writeAttribute(QStringLiteral("value"),
98 value: unsignedFormat.arg(a: uuid.toUInt16(), fieldWidth: 4, base: 16,
99 fillChar: QLatin1Char('0')));
100 break;
101 case 4:
102 stream->writeAttribute(QStringLiteral("value"),
103 value: unsignedFormat.arg(a: uuid.toUInt32(), fieldWidth: 8, base: 16,
104 fillChar: QLatin1Char('0')));
105 break;
106 case 16:
107 stream->writeAttribute(QStringLiteral("value"), value: uuid.toString().mid(position: 1, n: 36));
108 break;
109 default:
110 stream->writeAttribute(QStringLiteral("value"), value: uuid.toString().mid(position: 1, n: 36));
111 }
112 } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
113 stream->writeStartElement(QStringLiteral("sequence"));
114 const QBluetoothServiceInfo::Sequence *sequence =
115 static_cast<const QBluetoothServiceInfo::Sequence *>(attribute.data());
116 for (const QVariant &v : *sequence)
117 writeAttribute(stream, attribute: v);
118 stream->writeEndElement();
119 } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
120 const QBluetoothServiceInfo::Alternative *alternative =
121 static_cast<const QBluetoothServiceInfo::Alternative *>(attribute.data());
122 for (const QVariant &v : *alternative)
123 writeAttribute(stream, attribute: v);
124 stream->writeEndElement();
125 } else {
126 qCWarning(QT_BT_BLUEZ) << "Unknown variant type" << attribute.userType();
127 }
128 }
129}
130
131QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate()
132: serviceRecord(0), registered(false)
133{
134 initializeBluez5();
135 service = new OrgBluezProfileManager1Interface(QStringLiteral("org.bluez"),
136 QStringLiteral("/org/bluez"),
137 QDBusConnection::systemBus(), this);
138}
139
140QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate()
141{
142}
143
144bool QBluetoothServiceInfoPrivate::isRegistered() const
145{
146 return registered;
147}
148
149bool QBluetoothServiceInfoPrivate::unregisterService()
150{
151 if (!registered)
152 return false;
153
154 if (profilePath.isEmpty())
155 return false;
156
157 QDBusPendingReply<> reply = service->UnregisterProfile(profile: QDBusObjectPath(profilePath));
158 reply.waitForFinished();
159 if (reply.isError()) {
160 qCWarning(QT_BT_BLUEZ) << "Cannot unregister profile:" << profilePath
161 << reply.error().message();
162 return false;
163 }
164 profilePath.clear();
165
166 registered = false;
167 return true;
168}
169
170// TODO Implement local adapter behavior
171bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress & /*localAdapter*/)
172{
173 if (registered)
174 return false;
175
176 QString xmlServiceRecord;
177
178 QXmlStreamWriter stream(&xmlServiceRecord);
179 stream.setAutoFormatting(true);
180
181 stream.writeStartDocument(QStringLiteral("1.0"));
182
183 stream.writeStartElement(QStringLiteral("record"));
184
185 const QString unsignedFormat(QStringLiteral("0x%1"));
186
187 QMap<quint16, QVariant>::ConstIterator i = attributes.constBegin();
188 while (i != attributes.constEnd()) {
189 stream.writeStartElement(QStringLiteral("attribute"));
190 stream.writeAttribute(QStringLiteral("id"), value: unsignedFormat.arg(a: i.key(), fieldWidth: 4, base: 16, fillChar: QLatin1Char('0')));
191 writeAttribute(stream: &stream, attribute: i.value());
192 stream.writeEndElement();
193
194 ++i;
195 }
196
197 stream.writeEndElement();
198
199 stream.writeEndDocument();
200
201 // create path
202 profilePath = u"/qt/profile"_s;
203 profilePath.append(s: QString::fromLatin1(ba: "/%1%2/%3")
204 .arg(a: sanitizeNameForDBus(text: QCoreApplication::applicationName()))
205 .arg(a: QCoreApplication::applicationPid())
206 .arg(a: pathCounter.fetchAndAddOrdered(valueToAdd: 1)));
207
208 QVariantMap mapping;
209 mapping.insert(QStringLiteral("ServiceRecord"), value: xmlServiceRecord);
210 mapping.insert(QStringLiteral("Role"), QStringLiteral("server"));
211
212 // Strategy to pick service uuid
213 // 1.) use serviceUuid()
214 // 2.) use first custom uuid if available
215 // 3.) use first service class uuid
216 QBluetoothUuid profileUuid =
217 attributes.value(key: QBluetoothServiceInfo::ServiceId).value<QBluetoothUuid>();
218 QBluetoothUuid firstCustomUuid;
219 if (profileUuid.isNull()) {
220 const QVariant var = attributes.value(key: QBluetoothServiceInfo::ServiceClassIds);
221 if (var.isValid()) {
222 const QBluetoothServiceInfo::Sequence seq =
223 var.value<QBluetoothServiceInfo::Sequence>();
224 for (const auto &e : seq) {
225 auto tempUuid = e.value<QBluetoothUuid>();
226 if (tempUuid.isNull())
227 continue;
228
229 int size = tempUuid.minimumSize();
230 if (size == 2 || size == 4) { // Base UUID derived
231 if (profileUuid.isNull())
232 profileUuid = tempUuid;
233 } else if (firstCustomUuid.isNull()) {
234 firstCustomUuid = tempUuid;
235 }
236 }
237 }
238 }
239
240 if (!firstCustomUuid.isNull())
241 profileUuid = firstCustomUuid;
242
243 QString uuidString = profileUuid.toString(mode: QUuid::WithoutBraces);
244
245 qCDebug(QT_BT_BLUEZ) << "Registering profile under" << profilePath << uuidString;
246
247 QDBusPendingReply<> reply =
248 service->RegisterProfile(profile: QDBusObjectPath(profilePath), UUID: uuidString, options: mapping);
249 reply.waitForFinished();
250 if (reply.isError()) {
251 qCWarning(QT_BT_BLUEZ) << "Cannot register profile" << reply.error().message();
252 return false;
253 }
254
255 registered = true;
256 return true;
257}
258
259QT_END_NAMESPACE
260

source code of qtconnectivity/src/bluetooth/qbluetoothserviceinfo_bluez.cpp