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

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