diff --git a/include/BluetoothHciL2Socket.h b/include/BluetoothHciL2Socket.h index 812616a..5e6e113 100644 --- a/include/BluetoothHciL2Socket.h +++ b/include/BluetoothHciL2Socket.h @@ -7,6 +7,12 @@ class BluetoothHciSocket; // Include necessary headers #include "BluetoothStructs.h" +enum class BluetoothHciL2ConnectResult { + SETUP_FAILED, + CONNECTED, + CONNECTION_FAILED +}; + // Bluetooth HCI L2CAP Socket class class BluetoothHciL2Socket { public: @@ -29,8 +35,14 @@ class BluetoothHciL2Socket { /// Destructor ~BluetoothHciL2Socket(); - /// Connects to the remote device. - void connect(); + /** + * Connects to the remote device. + * + * SETUP_FAILED means no controller connection was requested, so the caller + * may safely use another transport. CONNECTION_FAILED means the kernel did + * request a controller connection and reported its failure. + */ + BluetoothHciL2ConnectResult connect(); /// Disconnects the socket. void disconnect(); diff --git a/src/BluetoothHciL2Socket.cpp b/src/BluetoothHciL2Socket.cpp index a6f4b84..6056dec 100644 --- a/src/BluetoothHciL2Socket.cpp +++ b/src/BluetoothHciL2Socket.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -50,25 +52,55 @@ BluetoothHciL2Socket::~BluetoothHciL2Socket() { } } -void BluetoothHciL2Socket::connect() { +BluetoothHciL2ConnectResult BluetoothHciL2Socket::connect() { this->_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if(this->_socket < 0) return; + if(this->_socket < 0) return BluetoothHciL2ConnectResult::SETUP_FAILED; if (bind(this->_socket, (struct sockaddr*)&_l2_src, sizeof(_l2_src)) < 0) { close(this->_socket); this->_socket = -1; - return; + return BluetoothHciL2ConnectResult::SETUP_FAILED; } - // the kernel needs to flush the socket before we continue - while (::connect(_socket, (struct sockaddr *)&_l2_dst, sizeof(_l2_dst)) == -1 ) { - if(errno == EINTR) { - continue; - } - close(_socket); - _socket = -1; - break; + const int socketFlags = fcntl(this->_socket, F_GETFL, 0); + if (socketFlags < 0 || fcntl(this->_socket, F_SETFL, socketFlags | O_NONBLOCK) < 0) { + close(this->_socket); + this->_socket = -1; + return BluetoothHciL2ConnectResult::SETUP_FAILED; + } + + if (::connect(this->_socket, (struct sockaddr *)&_l2_dst, sizeof(_l2_dst)) == 0) { + fcntl(this->_socket, F_SETFL, socketFlags); + return BluetoothHciL2ConnectResult::CONNECTED; + } + + if (errno != EINPROGRESS) { + close(this->_socket); + this->_socket = -1; + return BluetoothHciL2ConnectResult::SETUP_FAILED; } + + // EINPROGRESS confirms that the kernel accepted the request and initiated + // the controller connection. Preserve the previous synchronous behavior, + // but retain this distinction if the controller ultimately reports failure. + struct pollfd descriptor = { this->_socket, POLLOUT, 0 }; + int pollResult; + do { + pollResult = poll(&descriptor, 1, -1); + } while (pollResult < 0 && errno == EINTR); + + int connectError = 0; + socklen_t connectErrorLength = sizeof(connectError); + if (pollResult < 0 || + getsockopt(this->_socket, SOL_SOCKET, SO_ERROR, &connectError, &connectErrorLength) < 0 || + connectError != 0) { + close(this->_socket); + this->_socket = -1; + return BluetoothHciL2ConnectResult::CONNECTION_FAILED; + } + + fcntl(this->_socket, F_SETFL, socketFlags); + return BluetoothHciL2ConnectResult::CONNECTED; } void BluetoothHciL2Socket::disconnect() { diff --git a/src/BluetoothHciSocket.cpp b/src/BluetoothHciSocket.cpp index 649160f..999df40 100644 --- a/src/BluetoothHciSocket.cpp +++ b/src/BluetoothHciSocket.cpp @@ -350,13 +350,19 @@ bool BluetoothHciSocket::kernelConnectWorkArounds(char * data, int length) { this->_l2sockets_connecting[bdaddr_dst] = l2socket_ptr; // Attempt to connect - l2socket_ptr->connect(); + const auto connectResult = l2socket_ptr->connect(); - // Check if connected successfully - if (!l2socket_ptr->isConnected()) { + if (connectResult == BluetoothHciL2ConnectResult::SETUP_FAILED) { this->_l2sockets_connecting.erase(bdaddr_dst); return false; } + + if (connectResult == BluetoothHciL2ConnectResult::CONNECTION_FAILED) { + // The kernel already sent the controller connection command. Do not + // fall through to Write() and create a second raw HCI attempt. + this->_l2sockets_connecting.erase(bdaddr_dst); + return true; + } } }