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 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
53 | |
54 | struct 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 | |
65 | struct AdvData { |
66 | quint8 length; |
67 | quint8 data[31]; |
68 | }; |
69 | |
70 | struct WhiteListParams { |
71 | quint8 addrType; |
72 | bdaddr_t addr; |
73 | }; |
74 | |
75 | |
76 | template<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 | |
81 | QLeAdvertiserBluez::QLeAdvertiserBluez(const QLowEnergyAdvertisingParameters ¶ms, |
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 | |
91 | QLeAdvertiserBluez::~QLeAdvertiserBluez() |
92 | { |
93 | disconnect(&m_hciManager, &HciManager::commandCompleted, this, |
94 | &QLeAdvertiserBluez::handleCommandCompleted); |
95 | doStopAdvertising(); |
96 | } |
97 | |
98 | void 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 | |
114 | void QLeAdvertiserBluez::doStopAdvertising() |
115 | { |
116 | toggleAdvertising(false); |
117 | sendNextCommand(); |
118 | } |
119 | |
120 | void QLeAdvertiserBluez::queueCommand(OpCodeCommandField ocf, const QByteArray &data) |
121 | { |
122 | m_pendingCommands << Command(ocf, data); |
123 | } |
124 | |
125 | void 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 | |
138 | void 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 | |
148 | void QLeAdvertiserBluez::queueReadTxPowerLevelCommand() |
149 | { |
150 | // Spec v4.2, Vol 2, Part E, 7.8.6 |
151 | queueCommand(OcfLeReadTxPowerLevel, QByteArray()); |
152 | } |
153 | |
154 | void QLeAdvertiserBluez::toggleAdvertising(bool enable) |
155 | { |
156 | // Spec v4.2, Vol 2, Part E, 7.8.9 |
157 | queueCommand(OcfLeSetAdvEnable, QByteArray(1, enable)); |
158 | } |
159 | |
160 | void 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(¶ms, 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 | |
189 | static quint16 forceIntoRange(quint16 val, quint16 min, quint16 max) |
190 | { |
191 | return qMin(qMax(val, min), max); |
192 | } |
193 | |
194 | void QLeAdvertiserBluez::setAdvertisingInterval(AdvParams ¶ms) |
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 | |
209 | void 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 | |
218 | void 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 | |
234 | template<typename T> static quint8 servicesType(bool dataComplete); |
235 | template<> quint8 servicesType<quint16>(bool dataComplete) |
236 | { |
237 | return dataComplete ? 0x3 : 0x2; |
238 | } |
239 | template<> quint8 servicesType<quint32>(bool dataComplete) |
240 | { |
241 | return dataComplete ? 0x5 : 0x4; |
242 | } |
243 | template<> quint8 servicesType<quint128>(bool dataComplete) |
244 | { |
245 | return dataComplete ? 0x7 : 0x6; |
246 | } |
247 | |
248 | template<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 | |
271 | void 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 | |
302 | void 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 | |
319 | void 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 | |
339 | void 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 | |
379 | void QLeAdvertiserBluez::setAdvertisingData() |
380 | { |
381 | // Spec v4.2, Vol 2, Part E, 7.8.7 |
382 | setData(false); |
383 | } |
384 | |
385 | void QLeAdvertiserBluez::setScanResponseData() |
386 | { |
387 | // Spec v4.2, Vol 2, Part E, 7.8.8 |
388 | setData(true); |
389 | } |
390 | |
391 | void 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 | |
408 | void 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 | |
455 | void QLeAdvertiserBluez::handleError() |
456 | { |
457 | m_pendingCommands.clear(); |
458 | // TODO: Unmonitor event |
459 | emit errorOccurred(); |
460 | } |
461 | |
462 | QT_END_NAMESPACE |
463 | |