Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qohosbluetoothsocket_p.h"
++
++ #include <QtBluetooth/private/qohosbluetoothaccess_p.h>
++ #include <QtBluetooth/private/qohosbluetoothcommon_p.h>
++ #include <QtBluetooth/private/qohosbluetoothenums_p.h>
++ #include <QtBluetooth/private/qohosbluetoothremotedevice_p.h>
++
++ #include <QtCore/q26numeric.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/private/qcore_ohos_p.h>
++ #include <QtCore/private/qnapi_p.h>
++ #include <QtCore/private/qohosjstools_p.h>
++ #include <QtCore/private/qohoslogger_p.h>
++
++ #include <cstddef>
++ #include <cstring>
++ #include <memory>
++ #include <optional>
++ #include <string>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_OHOS)
++
++ namespace QtOhosBluetooth {
++
++ namespace {
++
++ std::shared_ptr<int> makeSppSocketHandle(int socketDescriptor, const char *closeCallExpression)
++ {
++ return QtOhos::makeSharedPtrWithAttachedExtraData(
++ std::make_shared<int>(socketDescriptor),
++ QtOhos::makeDestroyNotifier(
++ [socketDescriptor, closeCallExpression]() {
++ QOhosJsThreadGateway::invoke(
++ [socketDescriptor, closeCallExpression](QOhosJsState &jsState) {
++ try {
++ jsState.eval(closeCallExpression, {socketDescriptor});
++ } catch (const Napi::Error &error) {
++ const auto errorCode =
++ tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
++ .value_or(QOhosBluetoothErrorCode::OperationFailed);
++ qOhosPrintfWarning(
++ "%s: cannot close the socket %d with %s, error code: %u",
++ Q_FUNC_INFO, socketDescriptor, closeCallExpression,
++ qToUnderlying(errorCode));
++ }
++ });
++ }));
++ }
++
++ }
++
++ std::shared_ptr<int> makeSppClientSocketHandle(int socketDescriptor)
++ {
++ return makeSppSocketHandle(socketDescriptor, "@ohos.bluetooth.socket.sppCloseClientSocket(*)");
++ }
++
++ std::shared_ptr<int> makeSppServerSocketHandle(int socketDescriptor)
++ {
++ return makeSppSocketHandle(socketDescriptor, "@ohos.bluetooth.socket.sppCloseServerSocket(*)");
++ }
++
++ namespace {
++
++ using SppType = QtOhosBluetooth::enums::ohos::bluetooth::socket::SppType;
++
++ std::shared_ptr<void> registerSocketSppReadListener(
++ QOhosJsState &jsState, int socketDescriptor, QOhosConsumer<QByteArray> sppReadListener,
++ QOhosConsumer<QOhosBluetoothErrorCode> errorCodeConsumer)
++ {
++ return registerQOhosOnOffMethodsBasedEventHandler(
++ jsState.eval<QNapi::Object>("@ohos.bluetooth.socket"), "sppRead",
++ [sppReadListener = std::move(sppReadListener)](const QOhosCallbackInfo &cbInfo) {
++ auto arrayBuffer = cbInfo.getFirstArg<QNapi::ArrayBuffer>(Q_FUNC_INFO);
++ sppReadListener(
++ QByteArray(
++ static_cast<const char *>(arrayBuffer.Data()),
++ static_cast<qsizetype>(arrayBuffer.ByteLength())));
++ },
++ {
++ .extraOnArg = std::make_optional(QNapi::ValueWrapper(socketDescriptor)),
++ .extraOffArg = std::make_optional(QNapi::ValueWrapper(socketDescriptor)),
++ .optOnCallExceptionHandler =
++ [errorCodeConsumer = std::move(errorCodeConsumer)](const Napi::Error &error) {
++ errorCodeConsumer(
++ tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
++ .value_or(QOhosBluetoothErrorCode::OperationFailed));
++ },
++ });
++ }
++
++ std::shared_ptr<void> registerSocketSppReadHandler(
++ int socketDescriptor, QOhosConsumer<QByteArray> receivedDataConsumer,
++ QOhosConsumer<QOhosBluetoothErrorCode> errorCodeConsumer)
++ {
++ auto sharedReceivedDataConsumer = QtOhos::moveToSharedPtr(std::move(receivedDataConsumer));
++ auto weakReceivedDataConsumer = QtOhos::makeWeakPtr(sharedReceivedDataConsumer);
++
++ auto socketSppReadHandler = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) {
++ return registerSocketSppReadListener(
++ jsState, socketDescriptor,
++ [weakReceivedDataConsumer](QByteArray receivedData) {
++ QtOhos::invokeInQtThread(
++ [weakReceivedDataConsumer, receivedData = std::move(receivedData)]() {
++ auto receivedDataConsumer = weakReceivedDataConsumer.lock();
++ if (receivedDataConsumer)
++ (*receivedDataConsumer)(std::move(receivedData));
++ });
++ },
++ std::move(errorCodeConsumer));
++ },
++ Q_FUNC_INFO);
++
++ if (!socketSppReadHandler)
++ return nullptr;
++
++ return QtOhos::makeSharedPtrWithAttachedExtraData(
++ socketSppReadHandler, sharedReceivedDataConsumer);
++ }
++
++ std::optional<bool> tryReadSocketConnectedState(QOhosJsState &jsState, int socketDescriptor)
++ {
++ try {
++ return jsState
++ .eval<QNapi::Boolean>(
++ "@ohos.bluetooth.socket.isConnected(*)", {socketDescriptor})
++ .Value();
++ } catch (const Napi::Error &error) {
++ const auto errorCode =
++ tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
++ .value_or(QOhosBluetoothErrorCode::OperationFailed);
++ qOhosPrintfWarning(
++ "%s: cannot read the socket connected state, error code: %u", Q_FUNC_INFO,
++ qToUnderlying(errorCode));
++ return std::nullopt;
++ }
++ }
++
++ std::optional<std::string> tryGetDeviceIdFromSocket(int socket)
++ {
++ if (socket == invalidSocketDescriptor)
++ return std::nullopt;
++
++ return QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::optional<std::string> {
++ try {
++ return jsState.eval<QNapi::String>(
++ "@ohos.bluetooth.socket.getDeviceId(*)", {socket});
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the device id of the socket %d, error code: %u",
++ Q_FUNC_INFO, socket, qToUnderlying(*optErrorCode));
++ }
++ return std::nullopt;
++ }
++ });
++ }
++
++ }
++
++ QOhosBluetoothSocketProxy::QOhosBluetoothSocketProxy()
++ {
++ QObject::connect(
++ QOhosBluetoothAccessProxy::instance().get(),
++ &QOhosBluetoothAccessProxy::stateChanged,
++ this,
++ [this](QOhosBluetoothAccessProxy::BluetoothState bluetoothState) {
++ if (bluetoothState == QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
++ return;
++
++ if (m_pendingConnectRequestId) {
++ qCWarning(
++ QT_BT_OHOS, "%s: bluetooth is turned off. Aborting the connect request ...",
++ Q_FUNC_INFO);
++ onConnectFailed(
++ *m_pendingConnectRequestId, QOhosBluetoothErrorCode::BluetoothDisabled);
++ return;
++ }
++
++ if (!m_jsScopeData)
++ return;
++
++ qCWarning(
++ QT_BT_OHOS, "%s: bluetooth is turned off. Closing the socket ...", Q_FUNC_INFO);
++ reportError(QOhosBluetoothErrorCode::BluetoothDisabled);
++ close();
++ });
++ }
++
++ std::unordered_map<int, std::shared_ptr<int>> &QOhosBluetoothSocketProxy::pendingSocketHandles()
++ {
++ static std::unordered_map<int, std::shared_ptr<int>> handles;
++ return handles;
++ }
++
++ bool QOhosBluetoothSocketProxy::registerPendingSocketHandle(std::shared_ptr<int> socketHandle)
++ {
++ if (!socketHandle) {
++ qCCritical(QT_BT_OHOS, "%s: no socket handle to register", Q_FUNC_INFO);
++ return false;
++ }
++
++ const auto socketDescriptor = *socketHandle;
++
++ auto &handles = pendingSocketHandles();
++ if (handles.find(socketDescriptor) != handles.end()) {
++ qCCritical(
++ QT_BT_OHOS,
++ "%s: socket descriptor %d is already registered as pending. Rejecting the socket ...",
++ Q_FUNC_INFO, socketDescriptor);
++ return false;
++ }
++
++ handles[socketDescriptor] = std::move(socketHandle);
++
++ return true;
++ }
++
++ void QOhosBluetoothSocketProxy::dropPendingSocketHandle(int socketDescriptor)
++ {
++ pendingSocketHandles().erase(socketDescriptor);
++ }
++
++ std::shared_ptr<int> QOhosBluetoothSocketProxy::takePendingSocketHandle(int socketDescriptor)
++ {
++ auto &handles = pendingSocketHandles();
++ auto found = handles.find(socketDescriptor);
++ if (found == handles.end())
++ return nullptr;
++
++ auto socketHandle = std::move(found->second);
++ handles.erase(found);
++ return socketHandle;
++ }
++
++ bool QOhosBluetoothSocketProxy::connect(const QString &deviceId, const QString &uuid, bool secure)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
++ reportError(QOhosBluetoothErrorCode::PermissionDenied);
++ return false;
++ }
++
++ const auto connectRequestId = ++m_connectRequestsCounter;
++ m_pendingConnectRequestId = connectRequestId;
++
++ auto selfRef = QtOhos::makeQThreadSafeRef(this);
++ auto optConnectErrorCode = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::optional<QOhosBluetoothErrorCode> {
++ auto sppOptions = QNapi::makeObject(
++ jsState.env(),
++ {
++ {"uuid", uuid.toStdString()},
++ {"secure", secure},
++ {"type", jsState.mapOhosEnumToJs(SppType::SPP_RFCOMM)},
++ });
++
++ try {
++ jsState.eval(
++ "@ohos.bluetooth.socket.sppConnect(*)",
++ {
++ deviceId.toStdString(),
++ sppOptions,
++ [selfRef, connectRequestId](const QNapi::CallbackInfo &callbackInfo) {
++ QNapi::Value error;
++ QNapi::Value data;
++ callbackInfo.getLeadingArgs(Q_FUNC_INFO, error, data);
++ auto socketId = data.IsNumber()
++ ? QNapi::checkedCast<QNapi::Number>(data).Int32Value()
++ : invalidSocketDescriptor;
++
++ if (socketId == invalidSocketDescriptor) {
++ const auto errorCode =
++ tryGetKnownErrorCode(error, Q_FUNC_INFO)
++ .value_or(QOhosBluetoothErrorCode::OperationFailed);
++ qOhosPrintfError(
++ "%s: sppConnect() failed with error code: %u", Q_FUNC_INFO,
++ qToUnderlying(errorCode));
++ selfRef.visitInQtThreadIfAlive(
++ [connectRequestId, errorCode](auto &self) {
++ self.onConnectFailed(connectRequestId, errorCode);
++ });
++ return;
++ }
++
++ auto socketHandle = makeSppClientSocketHandle(socketId);
++
++ selfRef.visitInQtThreadIfAlive(
++ [connectRequestId, socketHandle](auto &self) {
++ self.onSocketConnected(connectRequestId, socketHandle);
++ });
++ }
++ });
++ return std::nullopt;
++ } catch (const Napi::Error &error) {
++ return tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO);
++ }
++ });
++
++ if (optConnectErrorCode) {
++ qCCritical(
++ QT_BT_OHOS, "%s: sppConnect() failed with error code: %u", Q_FUNC_INFO,
++ qToUnderlying(*optConnectErrorCode));
++ onConnectFailed(connectRequestId, *optConnectErrorCode);
++ return false;
++ }
++
++ m_deviceId = deviceId.toStdString();
++
++ return true;
++ }
++
++ void QOhosBluetoothSocketProxy::onSocketConnected(
++ std::uint64_t connectRequestId, std::shared_ptr<int> socketHandle)
++ {
++ if (m_pendingConnectRequestId != connectRequestId) {
++ qCDebug(
++ QT_BT_OHOS,
++ "%s: the connection has been closed before it was established. Letting the socket "
++ "handle close the socket ...",
++ Q_FUNC_INFO);
++ return;
++ }
++
++ m_pendingConnectRequestId.reset();
++
++ if (!takeOverSocket(std::move(socketHandle))) {
++ m_deviceId.reset();
++ reportError(QOhosBluetoothErrorCode::OperationFailed);
++ }
++ }
++
++ void QOhosBluetoothSocketProxy::onConnectFailed(
++ std::uint64_t connectRequestId, QOhosBluetoothErrorCode errorCode)
++ {
++ if (m_pendingConnectRequestId != connectRequestId)
++ return;
++
++ m_pendingConnectRequestId.reset();
++ m_deviceId.reset();
++
++ reportError(errorCode);
++ }
++
++ bool QOhosBluetoothSocketProxy::takeOverSocket(std::shared_ptr<int> socketHandle)
++ {
++ if (!socketHandle) {
++ qCCritical(QT_BT_OHOS, "%s: no socket handle to take over", Q_FUNC_INFO);
++ return false;
++ }
++
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return false;
++
++ auto selfRef = QtOhos::makeQThreadSafeRef(this);
++ auto optSubscriptionErrorCode = std::make_shared<std::optional<QOhosBluetoothErrorCode>>();
++
++ auto sppReadHandlerHandle = registerSocketSppReadHandler(
++ *socketHandle,
++ [selfRef](QByteArray receivedData) {
++ if (receivedData.isEmpty())
++ return;
++
++ selfRef.visitInQtThreadIfAlive(
++ [receivedData = std::move(receivedData)](auto &self) {
++ Q_EMIT self.dataReceived(receivedData);
++ });
++ },
++ [optSubscriptionErrorCode](QOhosBluetoothErrorCode errorCode) {
++ *optSubscriptionErrorCode = errorCode;
++ });
++
++ if (!sppReadHandlerHandle) {
++ qCCritical(
++ QT_BT_OHOS,
++ "%s: cannot register the SPP read handler, error code: %u. Socket is not connected.",
++ Q_FUNC_INFO,
++ qToUnderlying(
++ optSubscriptionErrorCode->value_or(QOhosBluetoothErrorCode::OperationFailed)));
++ return false;
++ }
++
++ m_jsScopeData = QtOhos::makeProxyWithJsThreadDeleter(
++ QtOhos::moveToSharedPtr(
++ JsScopeData {
++ .socketHandle = socketHandle,
++ .sppReadHandlerHandle = std::move(sppReadHandlerHandle),
++ }));
++
++ const auto socketDescriptor = *socketHandle;
++ QMetaObject::invokeMethod(
++ this,
++ [this, socketDescriptor]() {
++ Q_EMIT connected(socketDescriptor);
++ },
++ Qt::QueuedConnection);
++
++ return true;
++ }
++
++ bool QOhosBluetoothSocketProxy::write(const char *data, qint64 maxSize)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
++ reportError(QOhosBluetoothErrorCode::PermissionDenied);
++ return false;
++ }
++
++ if (!m_jsScopeData) {
++ qCWarning(QT_BT_OHOS, "%s: the socket is already closed", Q_FUNC_INFO);
++ reportError(QOhosBluetoothErrorCode::OperationFailed);
++ return false;
++ }
++
++ const auto socketDescriptor = *m_jsScopeData->socketHandle;
++
++ struct WriteOutcome
++ {
++ std::optional<QOhosBluetoothErrorCode> optErrorCode = std::nullopt;
++ bool socketStillConnected = true;
++ };
++
++ const auto writeSize = q26::saturate_cast<std::size_t>(maxSize);
++ const auto writeOutcome = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> WriteOutcome {
++ try {
++ auto arrayBuffer = QNapi::ArrayBuffer::New(jsState.env(), writeSize);
++ std::memcpy(arrayBuffer.Data(), data, writeSize);
++ jsState.eval("@ohos.bluetooth.socket.sppWrite(*)", {socketDescriptor, arrayBuffer});
++ return {};
++ } catch (const Napi::Error &error) {
++ return {
++ .optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO),
++ .socketStillConnected =
++ tryReadSocketConnectedState(jsState, socketDescriptor).value_or(true),
++ };
++ }
++ });
++
++ if (!writeOutcome.optErrorCode)
++ return true;
++
++ reportError(*writeOutcome.optErrorCode);
++
++ if (!writeOutcome.socketStillConnected) {
++ qCWarning(
++ QT_BT_OHOS, "%s: the peer is no longer connected. Closing the socket ...", Q_FUNC_INFO);
++ close();
++ }
++
++ return false;
++ }
++
++ void QOhosBluetoothSocketProxy::close()
++ {
++ if (!m_jsScopeData && !m_pendingConnectRequestId)
++ return;
++
++ m_jsScopeData.reset();
++ m_pendingConnectRequestId.reset();
++ m_deviceId.reset();
++
++ QMetaObject::invokeMethod(this, &QOhosBluetoothSocketProxy::disconnected, Qt::QueuedConnection);
++ }
++
++ std::optional<QString> QOhosBluetoothSocketProxy::tryGetPeerDeviceAddress() const
++ {
++ const auto peerDeviceIdFromSocket = m_jsScopeData
++ ? tryGetDeviceIdFromSocket(*m_jsScopeData->socketHandle)
++ : std::optional<std::string>();
++ const auto &optPeerDeviceId = peerDeviceIdFromSocket
++ ? peerDeviceIdFromSocket
++ : m_deviceId;
++
++ return optPeerDeviceId
++ ? std::optional(QString::fromStdString(*optPeerDeviceId))
++ : std::nullopt;
++ }
++
++ std::optional<QString> QOhosBluetoothSocketProxy::tryGetPeerDeviceName() const
++ {
++ const auto deviceId = tryGetPeerDeviceAddress();
++ if (!deviceId)
++ return std::nullopt;
++
++ auto remoteDeviceProxy = QOhosBluetoothRemoteDeviceProxy::instance();
++ return remoteDeviceProxy->tryGetRemoteDeviceName(*deviceId);
++ }
++
++ void QOhosBluetoothSocketProxy::reportError(QOhosBluetoothErrorCode errorCode)
++ {
++ QMetaObject::invokeMethod(
++ this,
++ [this, errorCode]() {
++ Q_EMIT errorOccurred(errorCode);
++ },
++ Qt::QueuedConnection);
++ }
++
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qohosbluetoothsocket_p.cpp"