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 "qleadvertiser_p.h"
41
42#include "bluez/bluez_data_p.h"
43#include "bluez/hcimanager_p.h"
44#include "qbluetoothsocketbase_p.h"
45
46#include <QtCore/qloggingcategory.h>
47
48#include <cstring>
49
50QT_BEGIN_NAMESPACE
51
52Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
53
54struct AdvParams {
55 quint16 minInterval;
56 quint16 maxInterval;
57 quint8 type;
58 quint8 ownAddrType;
59 quint8 directAddrType;
60 bdaddr_t directAddr;
61 quint8 channelMap;
62 quint8 filterPolicy;
63} __attribute__ ((packed));
64
65struct AdvData {
66 quint8 length;
67 quint8 data[31];
68};
69
70struct WhiteListParams {
71 quint8 addrType;
72 bdaddr_t addr;
73};
74
75
76template<typename T> QByteArray byteArrayFromStruct(const T &data, int maxSize = -1)
77{
78 return QByteArray(reinterpret_cast<const char *>(&data), maxSize != -1 ? maxSize : sizeof data);
79}
80
81QLeAdvertiserBluez::QLeAdvertiserBluez(const QLowEnergyAdvertisingParameters &params,
82 const QLowEnergyAdvertisingData &advertisingData,
83 const QLowEnergyAdvertisingData &scanResponseData,
84 HciManager &hciManager, QObject *parent)
85 : QLeAdvertiser(params, advertisingData, scanResponseData, parent), m_hciManager(hciManager)
86{
87 connect(&m_hciManager, &HciManager::commandCompleted, this,
88 &QLeAdvertiserBluez::handleCommandCompleted);
89}
90
91QLeAdvertiserBluez::~QLeAdvertiserBluez()
92{
93 disconnect(&m_hciManager, &HciManager::commandCompleted, this,
94 &QLeAdvertiserBluez::handleCommandCompleted);
95 doStopAdvertising();
96}
97
98void QLeAdvertiserBluez::doStartAdvertising()
99{
100 if (!m_hciManager.monitorEvent(HciManager::CommandCompleteEvent)) {
101 handleError();
102 return;
103 }
104
105 m_sendPowerLevel = advertisingData().includePowerLevel()
106 || scanResponseData().includePowerLevel();
107 if (m_sendPowerLevel)
108 queueReadTxPowerLevelCommand();
109 else
110 queueAdvertisingCommands();
111 sendNextCommand();
112}
113
114void QLeAdvertiserBluez::doStopAdvertising()
115{
116 toggleAdvertising(false);
117 sendNextCommand();
118}
119
120void QLeAdvertiserBluez::queueCommand(OpCodeCommandField ocf, const QByteArray &data)
121{
122 m_pendingCommands << Command(ocf, data);
123}
124
125void QLeAdvertiserBluez::sendNextCommand()
126{
127 if (m_pendingCommands.isEmpty()) {
128 // TODO: Unmonitor event.
129 return;
130 }
131 const Command &c = m_pendingCommands.first();
132 if (!m_hciManager.sendCommand(OgfLinkControl, c.ocf, c.data)) {
133 handleError();
134 return;
135 }
136}
137
138void QLeAdvertiserBluez::queueAdvertisingCommands()
139{
140 toggleAdvertising(false); // Stop advertising first, in case it's currently active.
141 setWhiteList();
142 setAdvertisingParams();
143 setAdvertisingData();
144 setScanResponseData();
145 toggleAdvertising(true);
146}
147
148void QLeAdvertiserBluez::queueReadTxPowerLevelCommand()
149{
150 // Spec v4.2, Vol 2, Part E, 7.8.6
151 queueCommand(OcfLeReadTxPowerLevel, QByteArray());
152}
153
154void QLeAdvertiserBluez::toggleAdvertising(bool enable)
155{
156 // Spec v4.2, Vol 2, Part E, 7.8.9
157 queueCommand(OcfLeSetAdvEnable, QByteArray(1, enable));
158}
159
160void QLeAdvertiserBluez::setAdvertisingParams()
161{
162 // Spec v4.2, Vol 2, Part E, 7.8.5
163 AdvParams params;
164 static_assert(sizeof params == 15, "unexpected struct size");
165 using namespace std;
166 memset(&params, 0, sizeof params);
167 setAdvertisingInterval(params);
168 params.type = parameters().mode();
169 params.filterPolicy = parameters().filterPolicy();
170 if (params.filterPolicy != QLowEnergyAdvertisingParameters::IgnoreWhiteList
171 && advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited) {
172 qCWarning(QT_BT_BLUEZ) << "limited discoverability is incompatible with "
173 "using a white list; disabling filtering";
174 params.filterPolicy = QLowEnergyAdvertisingParameters::IgnoreWhiteList;
175 }
176 params.ownAddrType = QLowEnergyController::PublicAddress; // TODO: Make configurable.
177
178 // TODO: For ADV_DIRECT_IND.
179 // params.directAddrType = xxx;
180 // params.direct_bdaddr = xxx;
181
182 params.channelMap = 0x7; // All channels.
183
184 const QByteArray paramsData = byteArrayFromStruct(params);
185 qCDebug(QT_BT_BLUEZ) << "advertising parameters:" << paramsData.toHex();
186 queueCommand(OcfLeSetAdvParams, paramsData);
187}
188
189static quint16 forceIntoRange(quint16 val, quint16 min, quint16 max)
190{
191 return qMin(qMax(val, min), max);
192}
193
194void QLeAdvertiserBluez::setAdvertisingInterval(AdvParams &params)
195{
196 const double multiplier = 0.625;
197 const quint16 minVal = parameters().minimumInterval() / multiplier;
198 const quint16 maxVal = parameters().maximumInterval() / multiplier;
199 Q_ASSERT(minVal <= maxVal);
200 const quint16 specMinimum =
201 parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd
202 || parameters().mode() == QLowEnergyAdvertisingParameters::AdvNonConnInd ? 0xa0 : 0x20;
203 const quint16 specMaximum = 0x4000;
204 params.minInterval = qToLittleEndian(forceIntoRange(minVal, specMinimum, specMaximum));
205 params.maxInterval = qToLittleEndian(forceIntoRange(maxVal, specMinimum, specMaximum));
206 Q_ASSERT(params.minInterval <= params.maxInterval);
207}
208
209void QLeAdvertiserBluez::setPowerLevel(AdvData &advData)
210{
211 if (m_sendPowerLevel) {
212 advData.data[advData.length++] = 2;
213 advData.data[advData.length++]= 0xa;
214 advData.data[advData.length++] = m_powerLevel;
215 }
216}
217
218void QLeAdvertiserBluez::setFlags(AdvData &advData)
219{
220 // TODO: Discoverability flags are incompatible with ADV_DIRECT_IND
221 quint8 flags = 0;
222 if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityLimited)
223 flags |= 0x1;
224 else if (advertisingData().discoverability() == QLowEnergyAdvertisingData::DiscoverabilityGeneral)
225 flags |= 0x2;
226 flags |= 0x4; // "BR/EDR not supported". Otherwise clients might try to connect over Bluetooth classic.
227 if (flags) {
228 advData.data[advData.length++] = 2;
229 advData.data[advData.length++] = 0x1;
230 advData.data[advData.length++] = flags;
231 }
232}
233
234template<typename T> static quint8 servicesType(bool dataComplete);
235template<> quint8 servicesType<quint16>(bool dataComplete)
236{
237 return dataComplete ? 0x3 : 0x2;
238}
239template<> quint8 servicesType<quint32>(bool dataComplete)
240{
241 return dataComplete ? 0x5 : 0x4;
242}
243template<> quint8 servicesType<quint128>(bool dataComplete)
244{
245 return dataComplete ? 0x7 : 0x6;
246}
247
248template<typename T> static void addServicesData(AdvData &data, const QVector<T> &services)
249{
250 if (services.isEmpty())
251 return;
252 const int spaceAvailable = sizeof data.data - data.length;
253 const int maxServices = qMin<int>((spaceAvailable - 2) / sizeof(T), services.count());
254 if (maxServices <= 0) {
255 qCWarning(QT_BT_BLUEZ) << "services data does not fit into advertising data packet";
256 return;
257 }
258 const bool dataComplete = maxServices == services.count();
259 if (!dataComplete) {
260 qCWarning(QT_BT_BLUEZ) << "only" << maxServices << "out of" << services.count()
261 << "services fit into the advertising data";
262 }
263 data.data[data.length++] = 1 + maxServices * sizeof(T);
264 data.data[data.length++] = servicesType<T>(dataComplete);
265 for (int i = 0; i < maxServices; ++i) {
266 putBtData(services.at(i), data.data + data.length);
267 data.length += sizeof(T);
268 }
269}
270
271void QLeAdvertiserBluez::setServicesData(const QLowEnergyAdvertisingData &src, AdvData &dest)
272{
273 QVector<quint16> services16;
274 QVector<quint32> services32;
275 QVector<quint128> services128;
276 const QList<QBluetoothUuid> services = src.services();
277 for (const QBluetoothUuid &service : services) {
278 bool ok;
279 const quint16 service16 = service.toUInt16(&ok);
280 if (ok) {
281 services16 << service16;
282 continue;
283 }
284 const quint32 service32 = service.toUInt32(&ok);
285 if (ok) {
286 services32 << service32;
287 continue;
288 }
289
290 // QBluetoothUuid::toUInt128() is always Big-Endian
291 // convert it to host order
292 quint128 hostOrder;
293 quint128 qtUuidOrder = service.toUInt128();
294 ntoh128(&qtUuidOrder, &hostOrder);
295 services128 << hostOrder;
296 }
297 addServicesData(dest, services16);
298 addServicesData(dest, services32);
299 addServicesData(dest, services128);
300}
301
302void QLeAdvertiserBluez::setManufacturerData(const QLowEnergyAdvertisingData &src, AdvData &dest)
303{
304 if (src.manufacturerId() == QLowEnergyAdvertisingData::invalidManufacturerId())
305 return;
306 if (dest.length >= sizeof dest.data - 1 - 1 - 2 - src.manufacturerData().count()) {
307 qCWarning(QT_BT_BLUEZ) << "manufacturer data does not fit into advertising data packet";
308 return;
309 }
310
311 dest.data[dest.length++] = src.manufacturerData().count() + 1 + 2;
312 dest.data[dest.length++] = 0xff;
313 putBtData(src.manufacturerId(), dest.data + dest.length);
314 dest.length += sizeof(quint16);
315 std::memcpy(dest.data + dest.length, src.manufacturerData(), src.manufacturerData().count());
316 dest.length += src.manufacturerData().count();
317}
318
319void QLeAdvertiserBluez::setLocalNameData(const QLowEnergyAdvertisingData &src, AdvData &dest)
320{
321 if (src.localName().isEmpty())
322 return;
323 if (dest.length >= sizeof dest.data - 3) {
324 qCWarning(QT_BT_BLUEZ) << "local name does not fit into advertising data";
325 return;
326 }
327
328 const QByteArray localNameUtf8 = src.localName().toUtf8();
329 const int fullSize = localNameUtf8.count() + 1 + 1;
330 const int size = qMin<int>(fullSize, sizeof dest.data - dest.length);
331 const bool isComplete = size == fullSize;
332 dest.data[dest.length++] = size - 1;
333 const int dataType = isComplete ? 0x9 : 0x8;
334 dest.data[dest.length++] = dataType;
335 std::memcpy(dest.data + dest.length, localNameUtf8, size - 2);
336 dest.length += size - 2;
337}
338
339void QLeAdvertiserBluez::setData(bool isScanResponseData)
340{
341 // Spec v4.2, Vol 3, Part C, 11 and Supplement, Part 1
342 AdvData theData;
343 static_assert(sizeof theData == 32, "unexpected struct size");
344 theData.length = 0;
345
346 const QLowEnergyAdvertisingData &sourceData = isScanResponseData
347 ? scanResponseData() : advertisingData();
348
349 if (!sourceData.rawData().isEmpty()) {
350 theData.length = qMin<int>(sizeof theData.data, sourceData.rawData().count());
351 std::memcpy(theData.data, sourceData.rawData().constData(), theData.length);
352 } else {
353 if (sourceData.includePowerLevel())
354 setPowerLevel(theData);
355 if (!isScanResponseData)
356 setFlags(theData);
357
358 // Insert new constant-length data here.
359
360 setLocalNameData(sourceData, theData);
361 setServicesData(sourceData, theData);
362 setManufacturerData(sourceData, theData);
363 }
364
365 std::memset(theData.data + theData.length, 0, sizeof theData.data - theData.length);
366 const QByteArray dataToSend = byteArrayFromStruct(theData);
367
368 if (!isScanResponseData) {
369 qCDebug(QT_BT_BLUEZ) << "advertising data:" << dataToSend.toHex();
370 queueCommand(OcfLeSetAdvData, dataToSend);
371 } else if ((parameters().mode() == QLowEnergyAdvertisingParameters::AdvScanInd
372 || parameters().mode() == QLowEnergyAdvertisingParameters::AdvInd)
373 && theData.length > 0) {
374 qCDebug(QT_BT_BLUEZ) << "scan response data:" << dataToSend.toHex();
375 queueCommand(OcfLeSetScanResponseData, dataToSend);
376 }
377}
378
379void QLeAdvertiserBluez::setAdvertisingData()
380{
381 // Spec v4.2, Vol 2, Part E, 7.8.7
382 setData(false);
383}
384
385void QLeAdvertiserBluez::setScanResponseData()
386{
387 // Spec v4.2, Vol 2, Part E, 7.8.8
388 setData(true);
389}
390
391void QLeAdvertiserBluez::setWhiteList()
392{
393 // Spec v4.2, Vol 2, Part E, 7.8.15-16
394 if (parameters().filterPolicy() == QLowEnergyAdvertisingParameters::IgnoreWhiteList)
395 return;
396 queueCommand(OcfLeClearWhiteList, QByteArray());
397 const QList<QLowEnergyAdvertisingParameters::AddressInfo> whiteListInfos
398 = parameters().whiteList();
399 for (const auto &addressInfo : whiteListInfos) {
400 WhiteListParams commandParam;
401 static_assert(sizeof commandParam == 7, "unexpected struct size");
402 commandParam.addrType = addressInfo.type;
403 convertAddress(addressInfo.address.toUInt64(), commandParam.addr.b);
404 queueCommand(OcfLeAddToWhiteList, byteArrayFromStruct(commandParam));
405 }
406}
407
408void QLeAdvertiserBluez::handleCommandCompleted(quint16 opCode, quint8 status,
409 const QByteArray &data)
410{
411 if (m_pendingCommands.isEmpty())
412 return;
413 const quint16 ocf = ocfFromOpCode(opCode);
414 const Command currentCmd = m_pendingCommands.first();
415 if (currentCmd.ocf != ocf)
416 return; // Not one of our commands.
417 m_pendingCommands.takeFirst();
418 if (status != 0) {
419 qCDebug(QT_BT_BLUEZ) << "command" << ocf << "failed with status" << status;
420 if (ocf == OcfLeSetAdvEnable && status == 0xc && currentCmd.data == QByteArray(1, '\0')) {
421 // we ignore OcfLeSetAdvEnable if it tries to disable an active advertisement
422 // it seems the platform often automatically turns off advertisements
423 // subsequently the explicit stopAdvertisement call fails when re-issued
424 qCDebug(QT_BT_BLUEZ) << "Advertising disable failed, ignoring";
425 sendNextCommand();
426 return;
427 }
428 if (ocf == OcfLeReadTxPowerLevel) {
429 qCDebug(QT_BT_BLUEZ) << "reading power level failed, leaving it out of the "
430 "advertising data";
431 m_sendPowerLevel = false;
432 } else {
433 handleError();
434 return;
435 }
436 } else {
437 qCDebug(QT_BT_BLUEZ) << "command" << ocf << "executed successfully";
438 }
439
440 switch (ocf) {
441 case OcfLeReadTxPowerLevel:
442 if (m_sendPowerLevel) {
443 m_powerLevel = data.at(0);
444 qCDebug(QT_BT_BLUEZ) << "TX power level is" << m_powerLevel;
445 }
446 queueAdvertisingCommands();
447 break;
448 default:
449 break;
450 }
451
452 sendNextCommand();
453}
454
455void QLeAdvertiserBluez::handleError()
456{
457 m_pendingCommands.clear();
458 // TODO: Unmonitor event
459 emit errorOccurred();
460}
461
462QT_END_NAMESPACE
463