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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | using namespace Qt::StringLiterals; |
17 | |
18 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
19 | |
20 | static QAtomicInt pathCounter; |
21 | |
22 | static 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 | |
130 | QBluetoothServiceInfoPrivate::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 | |
139 | QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate() |
140 | { |
141 | } |
142 | |
143 | bool QBluetoothServiceInfoPrivate::isRegistered() const |
144 | { |
145 | return registered; |
146 | } |
147 | |
148 | bool 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 |
170 | bool 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 | |
258 | QT_END_NAMESPACE |
259 | |