| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtBluetooth module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qbluetoothserviceinfo.h" |
| #include "osxbtservicerecord_p.h" |
| #include "osxbluetooth_p.h" |
| |
| #include <QtCore/qvariant.h> |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qmap.h> |
| #include <QtCore/qurl.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace OSXBluetooth { |
| |
| // |
| // Returns a dictionary containing the Bluetooth RFCOMM service definition |
| // corresponding to the provided |uuid| and |options|. |
| namespace { |
| |
| typedef ObjCStrongReference<NSMutableDictionary> Dictionary; |
| typedef ObjCStrongReference<IOBluetoothSDPUUID> SDPUUid; |
| typedef ObjCStrongReference<NSNumber> Number; |
| typedef QBluetoothServiceInfo QSInfo; |
| typedef QSInfo::Sequence Sequence; |
| typedef QSInfo::AttributeId AttributeId; |
| |
| } |
| |
| #if 0 |
| QBluetoothUuid profile_uuid(const QBluetoothServiceInfo &serviceInfo) |
| { |
| // Strategy to pick service uuid: |
| // 1.) use serviceUuid() |
| // 2.) use first custom uuid if available |
| // 3.) use first service class uuid |
| QBluetoothUuid serviceUuid(serviceInfo.serviceUuid()); |
| |
| if (serviceUuid.isNull()) { |
| const QVariant var(serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds)); |
| if (var.isValid()) { |
| const Sequence seq(var.value<Sequence>()); |
| |
| for (int i = 0; i < seq.count(); ++i) { |
| QBluetoothUuid uuid(seq.at(i).value<QBluetoothUuid>()); |
| if (uuid.isNull()) |
| continue; |
| |
| const int size = uuid.minimumSize(); |
| if (size == 2 || size == 4) { // Base UUID derived |
| if (serviceUuid.isNull()) |
| serviceUuid = uuid; |
| } else { |
| return uuid; |
| } |
| } |
| } |
| } |
| |
| return serviceUuid; |
| } |
| #endif |
| |
| template<class IntType> |
| Number variant_to_nsnumber(const QVariant &); |
| |
| template<> |
| Number variant_to_nsnumber<unsigned char>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithUnsignedChar:var.value<unsigned char>()], true); |
| } |
| |
| template<> |
| Number variant_to_nsnumber<unsigned short>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithUnsignedShort:var.value<unsigned short>()], true); |
| } |
| |
| template<> |
| Number variant_to_nsnumber<unsigned>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithUnsignedInt:var.value<unsigned>()], true); |
| } |
| |
| template<> |
| Number variant_to_nsnumber<char>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithChar:var.value<char>()], true); |
| } |
| |
| template<> |
| Number variant_to_nsnumber<short>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithShort:var.value<short>()], true); |
| } |
| |
| template<> |
| Number variant_to_nsnumber<int>(const QVariant &var) |
| { |
| return Number([NSNumber numberWithInt:var.value<int>()], true); |
| } |
| |
| template<class ValueType> |
| void add_attribute(const QVariant &var, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| if (!var.canConvert<ValueType>()) |
| return; |
| |
| const Number num(variant_to_nsnumber<ValueType>(var)); |
| [dict setObject:num forKey:[NSString stringWithFormat:@"%d", int(key)]]; |
| } |
| |
| template<> |
| void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| if (!var.canConvert<QString>()) |
| return; |
| |
| const QString string(var.value<QString>()); |
| if (string.length()) { |
| if (NSString *const nsString = string.toNSString()) |
| [dict setObject:nsString forKey:[NSString stringWithFormat:@"%d", int(key)]]; |
| } |
| } |
| |
| template<> |
| void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| if (!var.canConvert<QBluetoothUuid>()) |
| return; |
| |
| SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>())); |
| [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%d", int(key)]]; |
| } |
| |
| template<> |
| void add_attribute<QUrl>(const QVariant &var, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| if (!var.canConvert<QUrl>()) |
| return; |
| |
| Q_UNUSED(var) |
| Q_UNUSED(key) |
| Q_UNUSED(dict) |
| |
| // TODO: not clear how should I pass an url in a dictionary, NSURL does not work. |
| } |
| |
| template<class ValueType> |
| void add_attribute(const QVariant &var, NSMutableArray *list); |
| |
| template<class ValueType> |
| void add_attribute(const QVariant &var, NSMutableArray *list) |
| { |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (!var.canConvert<ValueType>()) |
| return; |
| |
| const Number num(variant_to_nsnumber<ValueType>(var)); |
| [list addObject:num]; |
| } |
| |
| template<> |
| void add_attribute<QString>(const QVariant &var, NSMutableArray *list) |
| { |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (!var.canConvert<QString>()) |
| return; |
| |
| const QString string(var.value<QString>()); |
| if (string.length()) { |
| if (NSString *const nsString = string.toNSString()) |
| [list addObject:nsString]; |
| } |
| } |
| |
| template<> |
| void add_attribute<QBluetoothUuid>(const QVariant &var, NSMutableArray *list) |
| { |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (!var.canConvert<QBluetoothUuid>()) |
| return; |
| |
| SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>())); |
| [list addObject:ioUUID]; |
| } |
| |
| template<> |
| void add_attribute<QUrl>(const QVariant &var, NSMutableArray *list) |
| { |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (!var.canConvert<QUrl>()) |
| return; |
| |
| Q_UNUSED(var) |
| Q_UNUSED(list) |
| // TODO: not clear how should I pass an url in a dictionary, NSURL does not work. |
| } |
| |
| void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| QT_BT_MAC_AUTORELEASEPOOL; |
| |
| // Objective-C has literals (for arrays and dictionaries), but it will not compile |
| // on 10.7 or below, so quite a lot of code here. |
| |
| NSMutableArray *const descriptorList = [NSMutableArray array]; |
| |
| IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP]; |
| NSArray *const l2capList = [NSArray arrayWithObject:l2capUUID]; |
| |
| [descriptorList addObject:l2capList]; |
| // |
| IOBluetoothSDPUUID *const rfcommUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM]; |
| NSMutableDictionary *const rfcommDict = [NSMutableDictionary dictionary]; |
| [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"]; |
| [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementSize"]; |
| [rfcommDict setObject:[NSNumber numberWithInt:channelID] forKey:@"DataElementValue"]; |
| // |
| NSMutableArray *const rfcommList = [NSMutableArray array]; |
| [rfcommList addObject:rfcommUUID]; |
| [rfcommList addObject:rfcommDict]; |
| |
| [descriptorList addObject:rfcommList]; |
| [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d", |
| kBluetoothSDPAttributeIdentifierProtocolDescriptorList]]; |
| } |
| |
| void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| QT_BT_MAC_AUTORELEASEPOOL; |
| |
| // Objective-C has literals (for arrays and dictionaries), but it will not compile |
| // on 10.7 or below, so quite a lot of code here. |
| |
| NSMutableArray *const descriptorList = [NSMutableArray array]; |
| NSMutableArray *const l2capList = [NSMutableArray array]; |
| |
| IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP]; |
| [l2capList addObject:l2capUUID]; |
| |
| NSMutableDictionary *const l2capDict = [NSMutableDictionary dictionary]; |
| [l2capDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"]; |
| [l2capDict setObject:[NSNumber numberWithInt:2] forKey:@"DataElementSize"]; |
| [l2capDict setObject:[NSNumber numberWithInt:psm] forKey:@"DataElementValue"]; |
| [l2capList addObject:l2capDict]; |
| |
| [descriptorList addObject:l2capList]; |
| [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%d", |
| kBluetoothSDPAttributeIdentifierProtocolDescriptorList]]; |
| } |
| |
| bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list) |
| { |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (var.canConvert<Sequence>()) |
| return false; |
| |
| if (var.canConvert<QString>()) { |
| //ServiceName, ServiceDescription, ServiceProvider. |
| add_attribute<QString>(var, list); |
| } else if (var.canConvert<QBluetoothUuid>()) { |
| add_attribute<QBluetoothUuid>(var, list); |
| } else { |
| // Here we need 'key' to understand the type. |
| // We can have different integer types actually, so I have to check |
| // the 'key' to be sure the conversion is reasonable. |
| switch (key) { |
| case QSInfo::ServiceRecordHandle: |
| case QSInfo::ServiceRecordState: |
| case QSInfo::ServiceInfoTimeToLive: |
| add_attribute<unsigned>(var, list); |
| break; |
| case QSInfo::ServiceAvailability: |
| add_attribute<unsigned char>(var, list); |
| break; |
| case QSInfo::IconUrl: |
| case QSInfo::DocumentationUrl: |
| case QSInfo::ClientExecutableUrl: |
| add_attribute<QUrl>(var, list); |
| break; |
| default:; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dict (nil)"); |
| |
| const QVariant var(serviceInfo.attribute(key)); |
| if (var.canConvert<Sequence>()) |
| return false; |
| |
| if (var.canConvert<QString>()) { |
| //ServiceName, ServiceDescription, ServiceProvider. |
| add_attribute<QString>(var, key, dict); |
| } else if (var.canConvert<QBluetoothUuid>()) { |
| add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict); |
| } else { |
| // We can have different integer types actually, so I have to check |
| // the 'key' to be sure the conversion is reasonable. |
| switch (key) { |
| case QSInfo::ServiceRecordHandle: |
| case QSInfo::ServiceRecordState: |
| case QSInfo::ServiceInfoTimeToLive: |
| add_attribute<unsigned>(serviceInfo.attribute(key), key, dict); |
| break; |
| case QSInfo::ServiceAvailability: |
| add_attribute<unsigned char>(serviceInfo.attribute(key), key, dict); |
| break; |
| case QSInfo::IconUrl: |
| case QSInfo::DocumentationUrl: |
| case QSInfo::ClientExecutableUrl: |
| add_attribute<QUrl>(serviceInfo.attribute(key), key, dict); |
| break; |
| default:; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray *list) |
| { |
| // Add a "nested" sequence. |
| Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)"); |
| |
| if (var.isNull() || !var.canConvert<Sequence>()) |
| return false; |
| |
| const Sequence sequence(var.value<Sequence>()); |
| for (const QVariant &var : sequence) { |
| if (var.canConvert<Sequence>()) { |
| NSMutableArray *const nested = [NSMutableArray array]; |
| add_sequence_attribute(var, key, nested); |
| [list addObject:nested]; |
| } else { |
| add_attribute(var, key, list); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict) |
| { |
| Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)"); |
| |
| const QVariant &var(serviceInfo.attribute(key)); |
| if (var.isNull() || !var.canConvert<Sequence>()) |
| return false; |
| |
| QT_BT_MAC_AUTORELEASEPOOL; |
| |
| NSMutableArray *const list = [NSMutableArray array]; |
| const Sequence sequence(var.value<Sequence>()); |
| for (const QVariant &element : sequence) { |
| if (!add_sequence_attribute(element, key, list)) |
| add_attribute(element, key, list); |
| } |
| [dict setObject:list forKey:[NSString stringWithFormat:@"%d", int(key)]]; |
| |
| return true; |
| } |
| |
| Dictionary iobluetooth_service_dictionary(const QBluetoothServiceInfo &serviceInfo) |
| { |
| Dictionary dict; |
| |
| if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) |
| return dict; |
| |
| const QList<quint16> attributeIds(serviceInfo.attributes()); |
| if (!attributeIds.size()) |
| return dict; |
| |
| dict.reset([[NSMutableDictionary alloc] init]); |
| |
| for (quint16 key : attributeIds) { |
| if (key == QSInfo::ProtocolDescriptorList) // We handle it in a special way. |
| continue; |
| // TODO: check if non-sequence QVariant still must be |
| // converted into NSArray for some attribute ID. |
| if (!add_sequence_attribute(serviceInfo, AttributeId(key), dict)) |
| add_attribute(serviceInfo, AttributeId(key), dict); |
| } |
| |
| if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::L2capProtocol) { |
| add_l2cap_protocol_descriptor_list(serviceInfo.protocolServiceMultiplexer(), |
| dict); |
| } else { |
| add_rfcomm_protocol_descriptor_list(serviceInfo.serverChannel(), dict); |
| } |
| |
| return dict; |
| } |
| |
| } |
| |
| QT_END_NAMESPACE |