diff options
author | Alex Blasche <alexander.blasche@qt.io> | 2017-06-13 15:24:55 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@qt.io> | 2017-06-16 07:35:24 +0000 |
commit | 2acddda12f98a5022613d05bd62a3133c7de212b (patch) | |
tree | acc71b9c940c6b9a141c241a40afb0e5eb5aff03 /src | |
parent | ffa9e1978bfd168e961bc86301f10aad0f5aa6d6 (diff) |
Workaround for Android SDP discovery bug
Due to an Android platform bug, SDP discovery may return the wrong uuid
for the remote service. This bug was introduced by Android 6.0.1 and
tracked by https://issuetracker.google.com/issues/37076498.
The returned UUID is byte swapped. To increase
the QBluetoothSocket::connectToService() convenience QBluetoothSocket
uses a fallback which attempts to connect to the remote service assuming
the uuid was byte swapped. This will only happen if the uuid is not
derived from the official Bluetooth base UUID (aka the given UUID is
truly custom).
There is the slight chance that the reversed UUID is a different service
but that chance is very marginal when considering the amount of possible
custom UUIDs.
Task-number: QTBUG-61392
Change-Id: Ia41d670ab8d0666628f067e174965b698d0f26b0
Reviewed-by: Christian Stromme <christian.stromme@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/bluetooth/qbluetoothsocket_android.cpp | 113 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_p.h | 13 |
2 files changed, 114 insertions, 12 deletions
diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp index 85b69882..87c9ffc8 100644 --- a/src/bluetooth/qbluetoothsocket_android.cpp +++ b/src/bluetooth/qbluetoothsocket_android.cpp @@ -57,6 +57,7 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) Q_DECLARE_METATYPE(QAndroidJniObject) +Q_BLUETOOTH_EXPORT bool useReverseUuidWorkAroundConnect = true; /* BluetoothSocket.connect() can block up to 10s. Therefore it must be * in a separate thread. Unfortunately if BluetoothSocket.close() is @@ -79,10 +80,12 @@ class SocketConnectWorker : public QObject Q_OBJECT public: SocketConnectWorker(const QAndroidJniObject& socket, - const QAndroidJniObject& targetUuid) + const QAndroidJniObject& targetUuid, + const QBluetoothUuid& qtTargetUuid) : QObject(), mSocketObject(socket), - mTargetUuid(targetUuid) + mTargetUuid(targetUuid), + mQtTargetUuid(qtTargetUuid) { static int t = qRegisterMetaType<QAndroidJniObject>(); Q_UNUSED(t); @@ -91,7 +94,8 @@ public: signals: void socketConnectDone(const QAndroidJniObject &socket); void socketConnectFailed(const QAndroidJniObject &socket, - const QAndroidJniObject &targetUuid); + const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtUuid); public slots: void connectSocket() { @@ -103,7 +107,7 @@ public slots: env->ExceptionDescribe(); env->ExceptionClear(); - emit socketConnectFailed(mSocketObject, mTargetUuid); + emit socketConnectFailed(mSocketObject, mTargetUuid, mQtTargetUuid); QThread::currentThread()->quit(); return; } @@ -131,6 +135,8 @@ public slots: private: QAndroidJniObject mSocketObject; QAndroidJniObject mTargetUuid; + // same as mTargetUuid above - just the Qt C++ version rather than jni uuid + QBluetoothUuid mQtTargetUuid; }; class WorkerThread: public QThread @@ -144,10 +150,11 @@ public: // Runs in same thread as QBluetoothSocketPrivate void setupWorker(QBluetoothSocketPrivate* d_ptr, const QAndroidJniObject& socketObject, - const QAndroidJniObject& uuidObject, bool useFallback) + const QAndroidJniObject& uuidObject, bool useFallback, + const QBluetoothUuid& qtUuid = QBluetoothUuid()) { SocketConnectWorker* worker = new SocketConnectWorker( - socketObject, uuidObject); + socketObject, uuidObject, qtUuid); worker->moveToThread(this); connect(this, &QThread::finished, worker, &QObject::deleteLater); @@ -173,6 +180,30 @@ private: QPointer<SocketConnectWorker> workerPointer; }; +/* + * This function is part of a workaround for QTBUG-61392 + * + * Returns null uuid if the given \a serviceUuid is not a uuid + * derived from the Bluetooth base uuid. + */ +static QBluetoothUuid reverseUuid(const QBluetoothUuid &serviceUuid) +{ + if (serviceUuid.isNull()) + return QBluetoothUuid(); + + bool isBaseUuid = false; + serviceUuid.toUInt32(&isBaseUuid); + if (isBaseUuid) + return QBluetoothUuid(); + + const quint128 original = serviceUuid.toUInt128(); + quint128 reversed; + for (int i = 0; i < 16; i++) + reversed.data[15-i] = original.data[i]; + + return QBluetoothUuid(reversed); +} + QBluetoothSocketPrivate::QBluetoothSocketPrivate() : socket(-1), socketType(QBluetoothServiceInfo::UnknownProtocol), @@ -207,7 +238,7 @@ bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channel) { - qCWarning(QT_BT_ANDROID) << "Falling back to workaround."; + qCWarning(QT_BT_ANDROID) << "Falling back to getServiceChannel() workaround."; QAndroidJniEnvironment env; @@ -321,6 +352,60 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe return true; } +/* + * Workaround for QTBUG-61392 + */ +bool QBluetoothSocketPrivate::fallBackReversedConnect(const QBluetoothUuid &uuid) +{ + Q_Q(QBluetoothSocket); + + qCWarning(QT_BT_ANDROID) << "Falling back to reverse uuid workaround."; + const QBluetoothUuid reverse = reverseUuid(uuid); + if (reverse.isNull()) + return false; + + //cut leading { and trailing } {xxx-xxx} + QString tempUuid = reverse.toString(); + tempUuid.chop(1); //remove trailing '}' + tempUuid.remove(0, 1); //remove first '{' + + QAndroidJniEnvironment env; + const QAndroidJniObject inputString = QAndroidJniObject::fromString(tempUuid); + const QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", + "(Ljava/lang/String;)Ljava/util/UUID;", + inputString.object<jstring>()); + + if (secFlags == QBluetooth::NoSecurity) { + qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; + socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", + "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", + uuidObject.object<jobject>()); + } else { + qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; + socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", + "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", + uuidObject.object<jobject>()); + } + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + + socketObject = remoteDevice = QAndroidJniObject(); + errorString = QBluetoothSocket::tr("Cannot connect to %1", + "%1 = uuid").arg(reverse.toString()); + q->setSocketError(QBluetoothSocket::ServiceNotFoundError); + q->setSocketState(QBluetoothSocket::UnconnectedState); + return false; + } + + WorkerThread *workerThread = new WorkerThread(); + workerThread->setupWorker(this, socketObject, uuidObject, USE_FALLBACK); + workerThread->start(); + emit connectJavaSocket(); + + return true; +} /* * The call order during a connectToService() is as follows: @@ -332,10 +417,12 @@ bool QBluetoothSocketPrivate::fallBackConnect(QAndroidJniObject uuid, int channe * 4. if threaded connect fails call defaultSocketConnectFailed() via signals * 5. call fallBackConnect() if Android version 22 or below * -> Android 23+ complete failure of entire connectToService() - * 6. if threaded connect on fallback channel succeeds call socketConnectSuccess() + * 6. call fallBackReversedConnect() if Android version 23 or above + * -> if failure entire connectToService() fails + * 7. if threaded connect on one of above fallbacks succeeds call socketConnectSuccess() * via signals * -> done - * 7. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() + * 8. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() * -> complete failure of entire connectToService() * */ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, @@ -416,7 +503,7 @@ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, } WorkerThread *workerThread = new WorkerThread(); - workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK); + workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK, uuid); workerThread->start(); emit connectJavaSocket(); } @@ -482,7 +569,8 @@ void QBluetoothSocketPrivate::socketConnectSuccess(const QAndroidJniObject &sock } void QBluetoothSocketPrivate::defaultSocketConnectFailed( - const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid) + const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtTargetUuid) { Q_Q(QBluetoothSocket); @@ -494,6 +582,9 @@ void QBluetoothSocketPrivate::defaultSocketConnectFailed( bool success = false; if (QtAndroid::androidSdkVersion() <= 22) success = fallBackConnect(targetUuid, FALLBACK_CHANNEL); + else if (useReverseUuidWorkAroundConnect) // version 23+ has Android bug (see QTBUG-61392) + success = fallBackReversedConnect(qtTargetUuid); + if (!success) { errorString = QBluetoothSocket::tr("Connection to service failed"); socketObject = remoteDevice = QAndroidJniObject(); diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h index 956f8f02..c1af9471 100644 --- a/src/bluetooth/qbluetoothsocket_p.h +++ b/src/bluetooth/qbluetoothsocket_p.h @@ -127,6 +127,7 @@ public: #endif #ifdef QT_ANDROID_BLUETOOTH bool fallBackConnect(QAndroidJniObject uuid, int channel); + bool fallBackReversedConnect(const QBluetoothUuid &uuid); #endif @@ -197,7 +198,8 @@ public: public slots: void socketConnectSuccess(const QAndroidJniObject &socket); void defaultSocketConnectFailed(const QAndroidJniObject & socket, - const QAndroidJniObject &targetUuid); + const QAndroidJniObject &targetUuid, + const QBluetoothUuid &qtTargetUuid); void fallbackSocketConnectFailed(const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid); void inputThreadError(int errorCode); @@ -286,6 +288,15 @@ static inline quint64 convertAddress(quint8 (&from)[6], quint64 *to = 0) return result; } +#ifdef Q_OS_ANDROID +// QTBUG-61392 related +// Private API to disable the silent behavior to reverse a remote service's +// UUID. In rare cases the workaround behavior might not be desirable as +// it may lead to connects to incorrect services. +extern bool useReverseUuidWorkAroundConnect; + +#endif + QT_END_NAMESPACE |