12#include <QtCore/qstring.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qlist.h>
25CBCharacteristicProperties cb_properties(
const QLowEnergyCharacteristicData &data)
28 return CBCharacteristicProperties(
int(data.properties()));
31CBAttributePermissions cb_permissions(
const QLowEnergyCharacteristicData &data)
33 using QLEC = QLowEnergyCharacteristic;
35 const auto props = data.properties();
36 CBAttributePermissions cbFlags = {};
38 if ((props & QLEC::Write) || (props & QLEC::WriteNoResponse)
39 || (props & QLEC::WriteSigned)) {
40 cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteable);
43 if (props & QLEC::Read)
44 cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadable);
46 if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
47 cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsWriteEncryptionRequired);
49 if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired)
50 cbFlags = CBAttributePermissions(cbFlags | CBAttributePermissionsReadEncryptionRequired);
55ObjCStrongReference<CBMutableCharacteristic> create_characteristic(
const QLowEnergyCharacteristicData &data)
57 const ObjCStrongReference<CBMutableCharacteristic> ch([[CBMutableCharacteristic alloc] initWithType:cb_uuid(data.uuid())
58 properties:cb_properties(data)
60 permissions:cb_permissions(data)],
61 RetainPolicy::noInitialRetain);
65ObjCStrongReference<CBMutableDescriptor> create_descriptor(
const QLowEnergyDescriptorData &data)
69
70
71
72
73
75 if (data.uuid() != QBluetoothUuid::DescriptorType::CharacteristicUserDescription &&
76 data.uuid() != QBluetoothUuid::DescriptorType::CharacteristicPresentationFormat) {
77 qCWarning(QT_BT_DARWIN) <<
"unsupported descriptor" << data.uuid();
85 ObjCStrongReference<NSObject> value;
86 if (data.uuid() == QBluetoothUuid::DescriptorType::CharacteristicUserDescription) {
87 const QString asQString(QString::fromUtf8(data.value()));
90 const auto nsData = data_from_bytearray(data.value());
94 const ObjCStrongReference<CBMutableDescriptor> d([[CBMutableDescriptor alloc]
95 initWithType:cb_uuid(data.uuid())
96 value:value], RetainPolicy::noInitialRetain);
100quint32 qt_countGATTEntries(
const QLowEnergyServiceData &data)
102 const auto maxu32 = std::numeric_limits<quint32>::max();
104 quint32 nEntries = 1 + quint32(data.includedServices().size());
105 for (
const auto &ch : data.characteristics()) {
106 if (maxu32 - 2 < nEntries)
109 if (maxu32 - ch.descriptors().size() < nEntries)
111 nEntries += ch.descriptors().size();
117bool qt_validate_value_range(
const QLowEnergyCharacteristicData &data)
119 if (data.minimumValueLength() > data.maximumValueLength()
120 || data.minimumValueLength() < 0) {
124 return data.value().size() <= data.maximumValueLength();
129@interface DarwinBTPeripheralManager (PrivateAPI)
131- (
void)addConnectedCentral:(CBCentral *)central;
132- (CBService *)findIncludedService:(
const QBluetoothUuid &)qtUUID;
134- (
void)addIncludedServices:(
const QLowEnergyServiceData &)data
135 to:(CBMutableService *)cbService
136 qtService:(QLowEnergyServicePrivate *)qtService;
138- (
void)addCharacteristicsAndDescriptors:(
const QLowEnergyServiceData &)data
139 to:(CBMutableService *)cbService
140 qtService:(QLowEnergyServicePrivate *)qtService;
142- (CBATTError)validateWriteRequest:(CBATTRequest *)request;
146@implementation DarwinBTPeripheralManager
148 ObjCScopedPointer<CBPeripheralManager> manager;
149 LECBManagerNotifier *notifier;
151 QLowEnergyHandle lastHandle;
155 std::vector<ObjCStrongReference<CBMutableService>> services;
156 decltype(services.size()) nextServiceToAdd;
159 std::map<QBluetoothUuid, CBService *> serviceIndex;
160 ObjCScopedPointer<NSMutableDictionary> advertisementData;
162 GenericLEMap<CBCharacteristic *> charMap;
163 GenericLEMap<ObjCStrongReference<NSMutableData>> charValues;
165 QMap<QLowEnergyHandle, ValueRange> valueRanges;
167 std::deque<UpdateRequest> updateQueue;
169 PeripheralState state;
170 NSUInteger maxNotificationValueLength;
171 decltype(services.size()) nOfFailedAds;
174- (
id)initWith:(LECBManagerNotifier *)aNotifier
176 if (self = [super init]) {
178 notifier = aNotifier;
179 state = PeripheralState::idle;
180 nextServiceToAdd = {};
181 maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
193- (QSharedPointer<QLowEnergyServicePrivate>)addService:(
const QLowEnergyServiceData &)data
195 using QLES = QLowEnergyService;
196 using namespace DarwinBluetooth;
198 const auto nEntries = qt_countGATTEntries(data);
199 if (!nEntries || nEntries > std::numeric_limits<QLowEnergyHandle>::max() - lastHandle) {
200 qCCritical(QT_BT_DARWIN) <<
"addService: not enough handles";
206 const BOOL primary = data.type() == QLowEnergyServiceData::ServiceTypePrimary;
207 const auto cbUUID = cb_uuid(data.uuid());
209 const ObjCStrongReference<CBMutableService>
210 newCBService([[CBMutableService alloc] initWithType:cbUUID primary:primary],
211 RetainPolicy::noInitialRetain);
214 qCCritical(QT_BT_DARWIN) <<
"addService: failed to create CBMutableService";
218 auto newQtService = QSharedPointer<QLowEnergyServicePrivate>::create();
219 newQtService->state = QLowEnergyService::LocalService;
220 newQtService->uuid = data.uuid();
221 newQtService->type = primary ? QLES::PrimaryService : QLES::IncludedService;
222 newQtService->startHandle = ++lastHandle;
225 [self addIncludedServices:data to:newCBService qtService:newQtService.data()];
226 [self addCharacteristicsAndDescriptors:data to:newCBService qtService:newQtService.data()];
228 services.push_back(newCBService);
229 serviceIndex[data.uuid()] = newCBService;
231 newQtService->endHandle = lastHandle;
236- (
void) setParameters:(
const QLowEnergyAdvertisingParameters &)parameters
237 data:(
const QLowEnergyAdvertisingData &)data
238 scanResponse:(
const QLowEnergyAdvertisingData &)scanResponse
240 Q_UNUSED(parameters);
246
247
248
249
250
251
252
253
254
255
256
260 advertisementData.reset([[NSMutableDictionary alloc] init],
261 DarwinBluetooth::RetainPolicy::noInitialRetain);
262 if (!advertisementData) {
263 qCWarning(QT_BT_DARWIN) <<
"setParameters: failed to allocate "
264 "NSMutableDictonary (advertisementData)";
268 auto localName = scanResponse.localName();
269 if (!localName.size())
270 localName = data.localName();
272 if (localName.size()) {
273 [advertisementData setObject:localName.toNSString()
274 forKey:CBAdvertisementDataLocalNameKey];
277 if (data.services().isEmpty() && scanResponse.services().isEmpty())
280 const ObjCScopedPointer<NSMutableArray> uuids([[NSMutableArray alloc] init],
281 DarwinBluetooth::RetainPolicy::noInitialRetain);
283 qCWarning(QT_BT_DARWIN) <<
"setParameters: failed to allocate "
284 "NSMutableArray (services uuids)";
289 for (
const auto &qtUUID : data.services()) {
290 const auto cbUUID = cb_uuid(qtUUID);
292 [uuids addObject:cbUUID];
295 for (
const auto &qtUUID : scanResponse.services()) {
296 const auto cbUUID = cb_uuid(qtUUID);
298 [uuids addObject:cbUUID];
302 [advertisementData setObject:uuids
303 forKey:CBAdvertisementDataServiceUUIDsKey];
307- (
void)startAdvertising
309 state = PeripheralState::waitingForPowerOn;
311 [manager setDelegate:nil];
312 manager.reset([[CBPeripheralManager alloc] initWithDelegate:self
313 queue:DarwinBluetooth::qt_LE_queue()],
314 DarwinBluetooth::RetainPolicy::noInitialRetain);
317- (
void)stopAdvertising
319 [manager stopAdvertising];
320 state = PeripheralState::idle;
326 notifier->disconnect();
327 notifier->deleteLater();
331 if (state == PeripheralState::advertising) {
332 [manager stopAdvertising];
333 [manager setDelegate:nil];
334 state = PeripheralState::idle;
338- (
void)write:(
const QByteArray &)value
339 charHandle:(QLowEnergyHandle)charHandle
341 using namespace DarwinBluetooth;
348 if (!charMap.contains(charHandle) || !valueRanges.contains(charHandle)) {
349 emit notifier->CBManagerError(QLowEnergyController::UnknownError);
353 const auto & range = valueRanges[charHandle];
354 if (value.size() < qsizetype(range.first) || value.size() > qsizetype(range.second)
356 || value.size() > DarwinBluetooth::maxValueLength) {
360 qCWarning(QT_BT_DARWIN) <<
"ignoring value of invalid length" << value.size();
364 emit notifier->characteristicWritten(charHandle, value);
366 const auto nsData = mutable_data_from_bytearray(value);
367 charValues[charHandle] = nsData;
371 const ObjCStrongReference<NSData> copy([NSData dataWithData:nsData], RetainPolicy::doInitialRetain);
372 updateQueue.push_back(UpdateRequest{charHandle, copy});
373 [self sendUpdateRequests];
376- (
void) addServicesToPeripheral
380 if (nextServiceToAdd < services.size())
381 [manager addService:services[nextServiceToAdd++]];
386- (
void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
388#pragma clang diagnostic push
389#pragma clang diagnostic ignored "-Wunguarded-availability-new"
391 if (peripheral != manager || !notifier)
394 if (peripheral.state == CBManagerStatePoweredOn) {
396 if (state == PeripheralState::waitingForPowerOn) {
397 [manager removeAllServices];
398 nextServiceToAdd = {};
399 state = PeripheralState::advertising;
401 [self addServicesToPeripheral];
407
408
409
411 maxNotificationValueLength = std::numeric_limits<NSUInteger>::max();
413 if (state == PeripheralState::advertising) {
414 state = PeripheralState::waitingForPowerOn;
415 }
else if (state == PeripheralState::connected) {
416 state = PeripheralState::idle;
417 emit notifier->disconnected();
422
423
424
426 if (peripheral.state == CBManagerStateUnsupported) {
427 state = PeripheralState::idle;
428 emit notifier->LEnotSupported();
429 }
else if (peripheral.state == CBManagerStateUnauthorized) {
430 state = PeripheralState::idle;
431 emit notifier->CBManagerError(QLowEnergyController::MissingPermissionsError);
434#pragma clang diagnostic pop
437- (
void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
438 error:(NSError *)error
440 if (peripheral != manager || !notifier)
444 NSLog(@
"failed to start advertising, error: %@", error);
445 state = PeripheralState::idle;
446 emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
450- (
void)peripheralManager:(CBPeripheralManager *)peripheral
451 didAddService:(CBService *)service error:(NSError *)error
455 if (peripheral != manager || !notifier)
459 NSLog(@
"failed to add a service, error: %@", error);
460 if (++nOfFailedAds == services.size()) {
461 emit notifier->CBManagerError(QLowEnergyController::AdvertisingError);
462 state = PeripheralState::idle;
467 if (nextServiceToAdd == services.size()) {
469 [manager startAdvertising:[advertisementData count] ? advertisementData.get() : nil];
471 [self addServicesToPeripheral];
475- (
void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
476 didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
478 Q_UNUSED(characteristic);
480 if (peripheral != manager || !notifier)
483 [self addConnectedCentral:central];
485 if (
const auto handle = charMap.key(characteristic))
486 emit notifier->notificationEnabled(handle,
true);
489- (
void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
490 didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
492 Q_UNUSED(characteristic);
494 if (peripheral != manager || !notifier)
497 const auto handle = charMap.key(characteristic);
498 if (![
static_cast<CBMutableCharacteristic*>(characteristic).subscribedCentrals count]
500 emit notifier->notificationEnabled(handle,
false);
503- (
void)peripheralManager:(CBPeripheralManager *)peripheral
504 didReceiveReadRequest:(CBATTRequest *)request
506 if (peripheral != manager || !notifier)
511 const auto handle = charMap.key(request.characteristic);
512 if (!handle || !charValues.contains(handle)) {
513 qCWarning(QT_BT_DARWIN) <<
"invalid read request, unknown characteristic";
514 [manager respondToRequest:request withResult:CBATTErrorInvalidHandle];
518 const auto &value = charValues[handle];
519 if (request.offset > [value length]) {
520 qCWarning(QT_BT_DARWIN) <<
"invalid offset in a read request";
521 [manager respondToRequest:request withResult:CBATTErrorInvalidOffset];
525 [self addConnectedCentral:request.central];
527 NSData *dataToSend = nil;
528 if (!request.offset) {
531 dataToSend = [value subdataWithRange:
532 NSMakeRange(request.offset, [value length] - request.offset)];
535 request.value = dataToSend;
536 [manager respondToRequest:request withResult:CBATTErrorSuccess];
540- (
void)writeValueForCharacteristic:(QLowEnergyHandle) charHandle
541 withWriteRequest:(CBATTRequest *)request
543 Q_ASSERT(charHandle);
546 Q_ASSERT(valueRanges.contains(charHandle));
547 const auto &range = valueRanges[charHandle];
548 Q_ASSERT(request.offset <= range.second
549 && request.value.length <= range.second - request.offset);
551 Q_ASSERT(charValues.contains(charHandle));
552 NSMutableData *
const value = charValues[charHandle];
553 if (request.offset + request.value.length > value.length)
554 [value increaseLengthBy:request.offset + request.value.length - value.length];
556 [value replaceBytesInRange:NSMakeRange(request.offset, request.value.length)
557 withBytes:request.value.bytes];
560- (
void)peripheralManager:(CBPeripheralManager *)peripheral
561 didReceiveWriteRequests:(NSArray *)requests
563 using namespace DarwinBluetooth;
567 if (peripheral != manager || !notifier) {
575 for (CBATTRequest *request in requests) {
576 const auto status = [self validateWriteRequest:request];
577 if (status != CBATTErrorSuccess) {
578 [manager respondToRequest:[requests objectAtIndex:0]
584 std::map<QLowEnergyHandle, NSUInteger> updated;
586 for (CBATTRequest *request in requests) {
588 [self addConnectedCentral:request.central];
589 const auto charHandle = charMap.key(request.characteristic);
590 const auto prevLen = updated[charHandle];
591 updated[charHandle] = std::max(request.offset + request.value.length,
593 [self writeValueForCharacteristic:charHandle withWriteRequest:request];
596 for (
const auto &pair : updated) {
597 const auto handle = pair.first;
598 NSMutableData *value = charValues[handle];
599 value.length = pair.second;
600 emit notifier->characteristicUpdated(handle, qt_bytearray(value));
601 const ObjCStrongReference<NSData> copy([NSData dataWithData:value],
602 RetainPolicy::doInitialRetain);
603 updateQueue.push_back(UpdateRequest{handle, copy});
606 if (requests.count) {
607 [manager respondToRequest:[requests objectAtIndex:0]
608 withResult:CBATTErrorSuccess];
611 [self sendUpdateRequests];
614- (
void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
616 if (peripheral != manager || !notifier) {
621 [self sendUpdateRequests];
624- (
void)sendUpdateRequests
628 while (updateQueue.size()) {
629 const auto &request = updateQueue.front();
630 if (charMap.contains(request.charHandle)) {
631 if (maxNotificationValueLength < [request.value length]) {
632 qCWarning(QT_BT_DARWIN) <<
"value of length" << [request.value length]
633 <<
"will possibly be truncated to"
634 << maxNotificationValueLength;
636 const BOOL res = [manager updateValue:request.value
637 forCharacteristic:
static_cast<CBMutableCharacteristic *>(charMap[request.charHandle])
638 onSubscribedCentrals:nil];
645 updateQueue.pop_front();
651- (
void)addConnectedCentral:(CBCentral *)central
661 maxNotificationValueLength = std::min(maxNotificationValueLength,
662 central.maximumUpdateValueLength);
666 if (state == PeripheralState::advertising) {
667 state = PeripheralState::connected;
668 emit notifier->connected();
672- (CBService *)findIncludedService:(
const QBluetoothUuid &)qtUUID
674 const auto it = serviceIndex.find(qtUUID);
675 if (it == serviceIndex.end())
681- (
void)addIncludedServices:(
const QLowEnergyServiceData &)data
682 to:(CBMutableService *)cbService
683 qtService:(QLowEnergyServicePrivate *)qtService
690 ObjCScopedPointer<NSMutableArray> included([[NSMutableArray alloc] init],
691 DarwinBluetooth::RetainPolicy::noInitialRetain);
693 qCWarning(QT_BT_DARWIN) <<
"addIncludedSerivces: failed "
694 "to allocate NSMutableArray";
698 for (
auto includedService : data.includedServices()) {
699 if (CBService *cbs = [self findIncludedService:includedService->serviceUuid()]) {
700 [included addObject:cbs];
701 qtService->includedServices << includedService->serviceUuid();
704 qCWarning(QT_BT_DARWIN) <<
"can not use" << includedService->serviceUuid()
705 <<
"as included, it has to be added first";
709 if ([included count])
710 cbService.includedServices = included;
713- (
void)addCharacteristicsAndDescriptors:(
const QLowEnergyServiceData &)data
714 to:(CBMutableService *)cbService
715 qtService:(QLowEnergyServicePrivate *)qtService
722 ObjCScopedPointer<NSMutableArray> newCBChars([[NSMutableArray alloc] init],
723 DarwinBluetooth::RetainPolicy::noInitialRetain);
725 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
726 "failed to allocate NSMutableArray "
731 for (
const auto &ch : data.characteristics()) {
732 if (!qt_validate_value_range(ch)) {
733 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
734 "invalid value size/min-max length";
739 if (ch.value().size() > DarwinBluetooth::maxValueLength) {
740 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
741 "value exceeds the maximal permitted "
743 << DarwinBluetooth::maxValueLength
744 <<
"octets) on the platform";
749 const auto cbChar(create_characteristic(ch));
751 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
752 "failed to allocate a characteristic";
756 const auto nsData(mutable_data_from_bytearray(ch.value()));
758 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
759 "addService: failed to allocate NSData (char value)";
763 [newCBChars addObject:cbChar];
765 const auto declHandle = ++lastHandle;
767 charMap[declHandle] = cbChar;
768 charValues[declHandle] = nsData;
769 valueRanges[declHandle] = ValueRange(ch.minimumValueLength(), ch.maximumValueLength());
771 QLowEnergyServicePrivate::CharData charData;
772 charData.valueHandle = declHandle;
773 charData.uuid = ch.uuid();
774 charData.properties = ch.properties();
775 charData.value = ch.value();
777 const ObjCScopedPointer<NSMutableArray> newCBDescs([[NSMutableArray alloc] init],
778 DarwinBluetooth::RetainPolicy::noInitialRetain);
780 qCWarning(QT_BT_DARWIN) <<
"addCharacteristicsAndDescritptors: "
781 "failed to allocate NSMutableArray "
786 for (
const auto &desc : ch.descriptors()) {
788 const auto cbDesc(create_descriptor(desc));
789 const auto descHandle = ++lastHandle;
793 [newCBDescs addObject:cbDesc];
796 QLowEnergyServicePrivate::DescData descData;
797 descData.uuid = desc.uuid();
798 descData.value = desc.value();
799 charData.descriptorList.insert(descHandle, descData);
802 if ([newCBDescs count])
803 cbChar.data().descriptors = newCBDescs.get();
805 qtService->characteristicList.insert(declHandle, charData);
808 if ([newCBChars count])
809 cbService.characteristics = newCBChars.get();
812- (CBATTError)validateWriteRequest:(CBATTRequest *)request
818 const auto handle = charMap.key(request.characteristic);
819 if (!handle || !charValues.contains(handle))
820 return CBATTErrorInvalidHandle;
822 Q_ASSERT(valueRanges.contains(handle));
824 const auto &range = valueRanges[handle];
825 if (request.offset > range.second)
826 return CBATTErrorInvalidOffset;
828 if (request.value.length > range.second - request.offset)
829 return CBATTErrorInvalidAttributeValueLength;
831 return CBATTErrorSuccess;
#define QT_BT_MAC_AUTORELEASEPOOL