blob: 23d55d13bcb93db60a20dd2ebe8b715a7f5259d1 [file] [log] [blame]
/****************************************************************************
**
** 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