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

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