1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qbluetoothserviceinfo.h"
41#include "qbluetoothserviceinfo_p.h"
42
43#include "bluez/manager_p.h"
44#include "bluez/service_p.h"
45#include "bluez/bluez5_helper_p.h"
46#include "bluez/profilemanager1_p.h"
47
48#include <QtCore/QLoggingCategory>
49#include <QtCore/QXmlStreamWriter>
50#include <QtCore/QAtomicInt>
51
52QT_BEGIN_NAMESPACE
53
54Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
55
56static const QLatin1String profilePathTemplate("/qt/profile");
57static QAtomicInt pathCounter;
58
59static void writeAttribute(QXmlStreamWriter *stream, const QVariant &attribute)
60{
61 const QString unsignedFormat(QStringLiteral("0x%1"));
62
63 switch (int(attribute.type())) {
64 case QMetaType::Void:
65 stream->writeEmptyElement(QStringLiteral("nil"));
66 break;
67 case QMetaType::UChar:
68 stream->writeEmptyElement(QStringLiteral("uint8"));
69 stream->writeAttribute(QStringLiteral("value"),
70 unsignedFormat.arg(attribute.value<quint8>(), 2, 16,
71 QLatin1Char('0')));
72 break;
73 case QMetaType::UShort:
74 stream->writeEmptyElement(QStringLiteral("uint16"));
75 stream->writeAttribute(QStringLiteral("value"),
76 unsignedFormat.arg(attribute.value<quint16>(), 4, 16,
77 QLatin1Char('0')));
78 break;
79 case QMetaType::UInt:
80 stream->writeEmptyElement(QStringLiteral("uint32"));
81 stream->writeAttribute(QStringLiteral("value"),
82 unsignedFormat.arg(attribute.value<quint32>(), 8, 16,
83 QLatin1Char('0')));
84 break;
85 case QMetaType::Char:
86 stream->writeEmptyElement(QStringLiteral("int8"));
87 stream->writeAttribute(QStringLiteral("value"),
88 QString::number(attribute.value<qint8>()));
89 break;
90 case QMetaType::Short:
91 stream->writeEmptyElement(QStringLiteral("int16"));
92 stream->writeAttribute(QStringLiteral("value"),
93 QString::number(attribute.value<qint16>()));
94 break;
95 case QMetaType::Int:
96 stream->writeEmptyElement(QStringLiteral("int32"));
97 stream->writeAttribute(QStringLiteral("value"),
98 QString::number(attribute.value<qint32>()));
99 break;
100 case QMetaType::QByteArray:
101 stream->writeEmptyElement(QStringLiteral("text"));
102 stream->writeAttribute(QStringLiteral("value"),
103 QString::fromLatin1(attribute.value<QByteArray>().toHex().constData()));
104 stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("hex"));
105 break;
106 case QMetaType::QString:
107 stream->writeEmptyElement(QStringLiteral("text"));
108 stream->writeAttribute(QStringLiteral("value"), attribute.value<QString>());
109 stream->writeAttribute(QStringLiteral("encoding"), QStringLiteral("normal"));
110 break;
111 case QMetaType::Bool:
112 stream->writeEmptyElement(QStringLiteral("boolean"));
113 if (attribute.value<bool>())
114 stream->writeAttribute(QStringLiteral("value"), QStringLiteral("true"));
115 else
116 stream->writeAttribute(QStringLiteral("value"), QStringLiteral("false"));
117 break;
118 case QMetaType::QUrl:
119 stream->writeEmptyElement(QStringLiteral("url"));
120 stream->writeAttribute(QStringLiteral("value"), attribute.value<QUrl>().toString());
121 break;
122 case QVariant::UserType:
123 if (attribute.userType() == qMetaTypeId<QBluetoothUuid>()) {
124 stream->writeEmptyElement(QStringLiteral("uuid"));
125
126 QBluetoothUuid uuid = attribute.value<QBluetoothUuid>();
127 switch (uuid.minimumSize()) {
128 case 0:
129 stream->writeAttribute(QStringLiteral("value"),
130 unsignedFormat.arg(quint16(0), 4, 16, QLatin1Char('0')));
131 break;
132 case 2:
133 stream->writeAttribute(QStringLiteral("value"),
134 unsignedFormat.arg(uuid.toUInt16(), 4, 16,
135 QLatin1Char('0')));
136 break;
137 case 4:
138 stream->writeAttribute(QStringLiteral("value"),
139 unsignedFormat.arg(uuid.toUInt32(), 8, 16,
140 QLatin1Char('0')));
141 break;
142 case 16:
143 stream->writeAttribute(QStringLiteral("value"), uuid.toString().mid(1, 36));
144 break;
145 default:
146 stream->writeAttribute(QStringLiteral("value"), uuid.toString().mid(1, 36));
147 }
148 } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) {
149 stream->writeStartElement(QStringLiteral("sequence"));
150 const QBluetoothServiceInfo::Sequence *sequence =
151 static_cast<const QBluetoothServiceInfo::Sequence *>(attribute.data());
152 for (const QVariant &v : *sequence)
153 writeAttribute(stream, v);
154 stream->writeEndElement();
155 } else if (attribute.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) {
156 const QBluetoothServiceInfo::Alternative *alternative =
157 static_cast<const QBluetoothServiceInfo::Alternative *>(attribute.data());
158 for (const QVariant &v : *alternative)
159 writeAttribute(stream, v);
160 stream->writeEndElement();
161 }
162 break;
163 default:
164 qCWarning(QT_BT_BLUEZ) << "Unknown variant type", attribute.userType();
165 }
166}
167
168QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate()
169: serviceRecord(0), registered(false)
170{
171 if (isBluez5()) {
172 serviceBluez5 = new OrgBluezProfileManager1Interface(
173 QStringLiteral("org.bluez"),
174 QStringLiteral("/org/bluez"),
175 QDBusConnection::systemBus(), this);
176 }
177}
178
179QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate()
180{
181}
182
183bool QBluetoothServiceInfoPrivate::isRegistered() const
184{
185 return registered;
186}
187
188bool QBluetoothServiceInfoPrivate::unregisterService()
189{
190 if (!registered)
191 return false;
192
193 if (serviceBluez5) { // Bluez 5
194 if (profilePath.isEmpty())
195 return false;
196
197 QDBusPendingReply<> reply = serviceBluez5->UnregisterProfile(
198 QDBusObjectPath(profilePath));
199 reply.waitForFinished();
200 if (reply.isError()) {
201 qCWarning(QT_BT_BLUEZ) << "Cannot unregister profile:"
202 << profilePath << reply.error().message();
203 return false;
204 }
205 profilePath.clear();
206 } else { // Bluez 4
207 if (!ensureSdpConnection(currentLocalAdapter))
208 return false;
209
210 QDBusPendingReply<> reply = service->RemoveRecord(serviceRecord);
211 reply.waitForFinished();
212 if (reply.isError())
213 return false;
214
215 serviceRecord = 0;
216 }
217
218 registered = false;
219 return true;
220}
221
222bool QBluetoothServiceInfoPrivate::ensureSdpConnection(const QBluetoothAddress &localAdapter)
223{
224 if (service && currentLocalAdapter == localAdapter)
225 return true;
226
227 delete service;
228
229 OrgBluezManagerInterface manager(QStringLiteral("org.bluez"), QStringLiteral("/"),
230 QDBusConnection::systemBus());
231
232
233 QDBusPendingReply<QDBusObjectPath> reply;
234 if (localAdapter.isNull())
235 reply = manager.DefaultAdapter();
236 else
237 reply = manager.FindAdapter(localAdapter.toString());
238 reply.waitForFinished();
239 if (reply.isError())
240 return false;
241
242 currentLocalAdapter = localAdapter;
243 service = new OrgBluezServiceInterface(QStringLiteral("org.bluez"), reply.value().path(),
244 QDBusConnection::systemBus(), this);
245
246 return true;
247}
248
249bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &localAdapter)
250{
251 if (serviceBluez5) { // Bluez 5
252 if (registered)
253 return false;
254 } else { // Bluez 4
255 //if new adapter unregister previous one first
256 if (registered && localAdapter != currentLocalAdapter)
257 unregisterService();
258
259 if (!ensureSdpConnection(localAdapter)) {
260 qCWarning(QT_BT_BLUEZ) << "SDP not connected. Cannot register";
261 return false;
262 }
263 }
264
265 QString xmlServiceRecord;
266
267 QXmlStreamWriter stream(&xmlServiceRecord);
268 stream.setAutoFormatting(true);
269
270 stream.writeStartDocument(QStringLiteral("1.0"));
271
272 stream.writeStartElement(QStringLiteral("record"));
273
274 const QString unsignedFormat(QStringLiteral("0x%1"));
275
276 QMap<quint16, QVariant>::ConstIterator i = attributes.constBegin();
277 while (i != attributes.constEnd()) {
278 stream.writeStartElement(QStringLiteral("attribute"));
279 stream.writeAttribute(QStringLiteral("id"), unsignedFormat.arg(i.key(), 4, 16, QLatin1Char('0')));
280 writeAttribute(&stream, i.value());
281 stream.writeEndElement();
282
283 ++i;
284 }
285
286 stream.writeEndElement();
287
288 stream.writeEndDocument();
289
290 if (serviceBluez5) { // Bluez 5
291 // create path
292 profilePath = profilePathTemplate;
293 profilePath.append(QString::fromLatin1("/%1%2/%3").
294 arg(sanitizeNameForDBus(QCoreApplication::applicationName())).
295 arg(QCoreApplication::applicationPid()).
296 arg(pathCounter.fetchAndAddOrdered(1)));
297
298 QVariantMap mapping;
299 mapping.insert(QStringLiteral("ServiceRecord"), xmlServiceRecord);
300
301 // Strategy to pick service uuid
302 // 1.) use serviceUuid()
303 // 2.) use first custom uuid if available
304 // 3.) use first service class uuid
305 QBluetoothUuid profileUuid = attributes.value(QBluetoothServiceInfo::ServiceId)
306 .value<QBluetoothUuid>();
307 QBluetoothUuid firstCustomUuid;
308 if (profileUuid.isNull()) {
309 const QVariant var = attributes.value(QBluetoothServiceInfo::ServiceClassIds);
310 if (var.isValid()) {
311 const QBluetoothServiceInfo::Sequence seq
312 = var.value<QBluetoothServiceInfo::Sequence>();
313 QBluetoothUuid tempUuid;
314
315 for (int i = 0; i < seq.count(); i++) {
316 tempUuid = seq.at(i).value<QBluetoothUuid>();
317 if (tempUuid.isNull())
318 continue;
319
320 int size = tempUuid.minimumSize();
321 if (size == 2 || size == 4) { // Base UUID derived
322 if (profileUuid.isNull())
323 profileUuid = tempUuid;
324 } else if (firstCustomUuid.isNull()){
325 firstCustomUuid = tempUuid;
326 }
327 }
328 }
329 }
330
331 if (!firstCustomUuid.isNull())
332 profileUuid = firstCustomUuid;
333
334 QString uuidString = profileUuid.toString();
335 uuidString.chop(1); // remove trailing '}'
336 uuidString.remove(0, 1); // remove beginning '{'
337
338 qCDebug(QT_BT_BLUEZ) << "Registering profile under" << profilePath
339 << uuidString;
340
341 QDBusPendingReply<> reply = serviceBluez5->RegisterProfile(
342 QDBusObjectPath(profilePath),
343 uuidString,
344 mapping);
345 reply.waitForFinished();
346 if (reply.isError()) {
347 qCWarning(QT_BT_BLUEZ) << "Cannot register profile" << reply.error().message();
348 return false;
349 }
350 } else { // Bluez 4
351 if (!registered) {
352 QDBusPendingReply<uint> reply = service->AddRecord(xmlServiceRecord);
353 reply.waitForFinished();
354 if (reply.isError()) {
355 qCWarning(QT_BT_BLUEZ) << "AddRecord returned error" << reply.error();
356 return false;
357 }
358
359 serviceRecord = reply.value();
360 } else {
361 QDBusPendingReply<> reply = service->UpdateRecord(serviceRecord, xmlServiceRecord);
362 reply.waitForFinished();
363 if (reply.isError()) {
364 qCWarning(QT_BT_BLUEZ) << "UpdateRecord returned error" << reply.error();
365 return false;
366 }
367 }
368 }
369
370 registered = true;
371 return true;
372}
373
374QT_END_NAMESPACE
375