// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "connections/implementation/mediums/bluetooth_classic.h" #include #include #include #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "connections/implementation/mediums/bluetooth_radio.h" #include "connections/implementation/mediums/multiplex/multiplex_socket.h" #include "connections/medium_selector.h" #include "internal/flags/nearby_flags.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/bluetooth_classic.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/expected.h" #include "internal/platform/logging.h" #include "internal/platform/mutex_lock.h" #include "internal/platform/socket.h" #include "internal/platform/types.h" #include "internal/platform/uuid.h" namespace nearby { namespace connections { namespace { using location::nearby::proto::connections::OperationResultCode; std::string ScanModeToString(BluetoothAdapter::ScanMode mode) { switch (mode) { case BluetoothAdapter::ScanMode::kUnknown: return "Unknown"; case BluetoothAdapter::ScanMode::kNone: return "None"; case BluetoothAdapter::ScanMode::kConnectable: return "Connectable"; case BluetoothAdapter::ScanMode::kConnectableDiscoverable: return "ConnectableDiscoverable"; } } } // namespace using MultiplexSocket = mediums::multiplex::MultiplexSocket; BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : BluetoothClassic(radio, std::make_unique( radio.GetBluetoothAdapter())) {} BluetoothClassic::BluetoothClassic( BluetoothRadio& radio, std::unique_ptr medium) : radio_(radio), adapter_(radio_.GetBluetoothAdapter()), medium_(std::move(medium)) { is_multiplex_enabled_ = NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableMultiplex) && NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableMultiplexBluetooth); } BluetoothClassic::~BluetoothClassic() { // Destructor is not taking locks, but methods it is calling are. StopAllDiscovery(); while (!server_sockets_.empty()) { StopAcceptingConnections(server_sockets_.begin()->first); } TurnOffDiscoverability(); { MutexLock lock(&mutex_); LOG(INFO) << "Closing multiplex sockets for " << multiplex_sockets_.size() << " devices"; if (is_multiplex_enabled_) { for (auto& [bt_mac, multiplex_socket] : multiplex_sockets_) { LOG(INFO) << "Closing multiplex sockets for " << GetRemoteDevice(bt_mac).GetName(); multiplex_socket->Shutdown(); } } multiplex_sockets_.clear(); } // All the AcceptLoopRunnable objects in here should already have gotten an // opportunity to shut themselves down cleanly in the calls to // StopAcceptingConnections() above. accept_loops_runner_.Shutdown(); } bool BluetoothClassic::IsAvailable() const { MutexLock lock(&mutex_); return IsAvailableLocked(); } bool BluetoothClassic::IsAvailableLocked() const { return medium_->IsValid() && adapter_.IsValid() && adapter_.IsEnabled(); } ErrorOr BluetoothClassic::TurnOnDiscoverability( const std::string& device_name) { LOG(INFO) << "Turning on BT discoverability with device_name=" << device_name; MutexLock lock(&mutex_); if (device_name.empty()) { LOG(INFO) << "Refusing to turn on BT discoverability. Empty device name."; return {Error( OperationResultCode::NEARBY_BLUETOOTH_ADVERTISE_TO_BYTES_FAILURE)}; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't turn on BT discoverability. BT is off."; return {Error(OperationResultCode::MISCELLEANEOUS_BT_SYSTEM_SERVICE_NULL)}; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't turn on BT discoverability. BT is not available."; return { Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLUETOOTH_NOT_AVAILABLE)}; } if (IsDiscoverable()) { LOG(INFO) << "Refusing to turn on BT discoverability; new name='" << device_name << "'; current name='" << adapter_.GetName() << "'"; return {Error(OperationResultCode::CONNECTIVITY_BLUETOOTH_SCAN_FAILURE)}; } if (!ModifyDeviceName(device_name)) { LOG(INFO) << "Failed to turn on BT discoverability; failed to set name to " << device_name; return {Error(OperationResultCode:: MISCELLEANEOUS_BLUETOOTH_CHANGE_DEVICE_NAME_FAILURE)}; } if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) { LOG(INFO) << "Failed to turn on BT discoverability; failed to set " "scan_mode to " << ScanModeToString(ScanMode::kConnectableDiscoverable); // Don't forget to perform this rollback of the partial state changes we've // made til now. RestoreDeviceName(); return {Error( OperationResultCode::CONNECTIVITY_BLUETOOTH_CHANGE_SCAN_MODE_FAILURE)}; } LOG(INFO) << "Turned on BT discoverability with device_name=" << device_name; return {true}; } bool BluetoothClassic::TurnOffDiscoverability() { LOG(INFO) << "Turning off Bluetooth discoverability."; MutexLock lock(&mutex_); if (!IsDiscoverable()) { LOG(INFO) << "Can't turn off BT discoverability; it is already off"; return false; } RestoreScanMode(); RestoreDeviceName(); LOG(INFO) << "Turned Bluetooth discoverability off."; return true; } bool BluetoothClassic::IsDiscoverable() const { return (!original_device_name_.empty() && (adapter_.GetScanMode() == ScanMode::kConnectableDiscoverable)); } bool BluetoothClassic::ModifyDeviceName(const std::string& device_name) { if (original_device_name_.empty()) { original_device_name_ = adapter_.GetName(); } return adapter_.SetName(device_name, /* persist= */ false); } bool BluetoothClassic::ModifyScanMode(ScanMode scan_mode) { if (original_scan_mode_ == ScanMode::kUnknown) { original_scan_mode_ = adapter_.GetScanMode(); } if (!adapter_.SetScanMode(scan_mode)) { original_scan_mode_ = ScanMode::kUnknown; return false; } return true; } bool BluetoothClassic::RestoreScanMode() { if (original_scan_mode_ == ScanMode::kUnknown || !adapter_.SetScanMode(original_scan_mode_)) { LOG(INFO) << "Failed to restore original Bluetooth scan mode to " << ScanModeToString(original_scan_mode_); return false; } // Regardless of whether or not we could actually restore the Bluetooth scan // mode, reset our relevant state. original_scan_mode_ = ScanMode::kUnknown; return true; } bool BluetoothClassic::RestoreDeviceName() { if (original_device_name_.empty() || !adapter_.SetName(original_device_name_, /* persis= */ true)) { LOG(INFO) << "Failed to restore original Bluetooth device name to " << original_device_name_; return false; } original_device_name_.clear(); return true; } ErrorOr BluetoothClassic::StartDiscovery( const std::string& serviceId, DiscoveredDeviceCallback callback) { MutexLock lock(&mutex_); if (serviceId.empty()) { LOG(INFO) << "Refusing to start discovery; service ID is empty."; return {Error(OperationResultCode::NEARBY_LOCAL_CLIENT_STATE_WRONG)}; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't discover BT devices because BT isn't enabled."; return {Error(OperationResultCode::DEVICE_STATE_RADIO_ENABLING_FAILURE)}; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't discover BT devices because BT isn't available."; return { Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLUETOOTH_NOT_AVAILABLE)}; } if (IsDiscoveringLocked(serviceId)) { LOG(INFO) << "Refusing to start discovery of BT devices because another " "discovery is already in-progress for service_id=" << serviceId; return {Error(OperationResultCode::CLIENT_BLUETOOTH_DUPLICATE_DISCOVERING)}; } if (!HasDiscoveryCallbacks()) { BluetoothClassicMedium::DiscoveryCallback medium_callback{ .device_discovered_cb = [this](BluetoothDevice& device) { MutexLock lock(&discovery_callbacks_mutex_); for (auto& [service_id, callback] : discovery_callbacks_) { if (callback.device_discovered_cb) { callback.device_discovered_cb(device); } } }, .device_name_changed_cb = [this](BluetoothDevice& device) { MutexLock lock(&discovery_callbacks_mutex_); for (auto& [service_id, callback] : discovery_callbacks_) { if (callback.device_name_changed_cb) { callback.device_name_changed_cb(device); } } }, .device_lost_cb = [this](BluetoothDevice& device) { MutexLock lock(&discovery_callbacks_mutex_); for (auto& [service_id, callback] : discovery_callbacks_) { if (callback.device_lost_cb) { callback.device_lost_cb(device); } } }}; AddDiscoveryCallback(serviceId, std::move(callback)); if (!medium_->StartDiscovery(std::move(medium_callback))) { LOG(INFO) << "Failed to start discovery of BT devices."; RemoveDiscoveryCallback(serviceId); return {Error(OperationResultCode::CONNECTIVITY_BLUETOOTH_SCAN_FAILURE)}; } } // Mark the fact that we're currently performing a Bluetooth scan. scan_info_.valid = true; return {true}; } bool BluetoothClassic::StopDiscovery(const std::string& serviceId) { MutexLock lock(&mutex_); if (!IsDiscoveringLocked(serviceId)) { LOG(INFO) << "Can't stop discovery of BT devices because it never started."; return false; } RemoveDiscoveryCallback(serviceId); if (!HasDiscoveryCallbacks()) { if (!medium_->StopDiscovery()) { LOG(INFO) << "Failed to stop discovery of Bluetooth devices."; return false; } scan_info_.valid = false; } return true; } bool BluetoothClassic::IsDiscoveringLocked(const std::string& serviceId) const { MutexLock lock(&discovery_callbacks_mutex_); return scan_info_.valid && discovery_callbacks_.contains(serviceId); } void BluetoothClassic::StopAllDiscovery() { MutexLock lock(&mutex_); if (!medium_->StopDiscovery()) { LOG(INFO) << "Failed to stop discovery of Bluetooth devices."; } RemoveAllDiscoveryCallbacks(); scan_info_.valid = false; } ErrorOr BluetoothClassic::StartAcceptingConnections( const std::string& service_id, AcceptedConnectionCallback callback) { MutexLock lock(&mutex_); if (service_id.empty()) { LOG(INFO) << "Refusing to start accepting BT connections; service ID is empty."; return {Error(OperationResultCode::NEARBY_LOCAL_CLIENT_STATE_WRONG)}; } if (!radio_.IsEnabled()) { LOG(INFO) << "Can't create BT server socket [service=" << service_id << "]; BT is disabled."; return {Error(OperationResultCode::DEVICE_STATE_RADIO_ENABLING_FAILURE)}; } if (!IsAvailableLocked()) { LOG(INFO) << "Can't start accepting BT connections [service=" << service_id << "]; BT not available."; return { Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLUETOOTH_NOT_AVAILABLE)}; } if (IsAcceptingConnectionsLocked(service_id)) { LOG(INFO) << "Refusing to start accepting BT connections [service=" << service_id << "]; BT server is already in-progress with the same name."; return {Error( OperationResultCode::CLIENT_DUPLICATE_ACCEPTING_BT_CONNECTION_REQUEST)}; } BluetoothServerSocket socket = medium_->ListenForService(service_id, GenerateUuidFromString(service_id)); if (!socket.IsValid()) { LOG(INFO) << "Failed to start accepting Bluetooth connections for " << service_id; return {Error( OperationResultCode::CONNECTIVITY_BT_SERVER_SOCKET_CREATION_FAILURE)}; } // Mark the fact that there's an in-progress Bluetooth server accepting // connections. auto owned_socket = server_sockets_.emplace(service_id, std::move(socket)).first->second; if (is_multiplex_enabled_) { MultiplexSocket::ListenForIncomingConnection( service_id, Medium::BLUETOOTH, [&callback](const std::string& listening_service_id, MediumSocket* virtual_socket) mutable { if (callback) { callback(listening_service_id, *(down_cast(virtual_socket))); } }); } // Start the accept loop on a dedicated thread - this stays alive and // listening for new incoming connections until StopAcceptingConnections() // is invoked. accept_loops_runner_.Execute("bt-accept", [callback = std::move(callback), server_socket = std::move(owned_socket), service_id, this]() mutable { while (true) { BluetoothSocket client_socket = server_socket.Accept(); if (!client_socket.IsValid()) { LOG(INFO) << "Failed to accept connection for " << service_id; server_socket.Close(); break; } LOG(INFO) << "Accepted connection for " << service_id; bool callback_called = false; { MutexLock lock(&mutex_); if (is_multiplex_enabled_) { BluetoothSocket client_socket_bak = client_socket; auto physical_socket_ptr = std::make_shared(client_socket_bak); MultiplexSocket* multiplex_socket = MultiplexSocket::CreateIncomingSocket(physical_socket_ptr, service_id, 0); if (multiplex_socket != nullptr && multiplex_socket->GetVirtualSocket(service_id)) { multiplex_sockets_.emplace( client_socket.GetRemoteDevice().GetMacAddress(), multiplex_socket); MultiplexSocket::StopListeningForIncomingConnection( service_id, Medium::BLUETOOTH); LOG(INFO) << "Multiplex virtaul socket created for " << client_socket.GetRemoteDevice().GetName(); if (callback) { callback(service_id, *(down_cast( multiplex_socket->GetVirtualSocket(service_id)))); callback_called = true; } } } } if (callback && !callback_called) { LOG(INFO) << "Call back triggered for physical socket."; callback(service_id, std::move(client_socket)); } } }); return {true}; } bool BluetoothClassic::IsAcceptingConnections(const std::string& service_id) { MutexLock lock(&mutex_); return IsAcceptingConnectionsLocked(service_id); } bool BluetoothClassic::IsAcceptingConnectionsLocked( const std::string& service_id) { return server_sockets_.find(service_id) != server_sockets_.end(); } bool BluetoothClassic::StopAcceptingConnections(const std::string& service_id) { MutexLock lock(&mutex_); if (service_id.empty()) { LOG(INFO) << "Unable to stop accepting BT connections because the " "service_id is empty."; return false; } const auto& it = server_sockets_.find(service_id); if (it == server_sockets_.end()) { LOG(INFO) << "Can't stop accepting BT connections for " << service_id << " because it was never started."; return false; } if (is_multiplex_enabled_) { MultiplexSocket::StopListeningForIncomingConnection(service_id, Medium::BLUETOOTH); } // Closing the BluetoothServerSocket will kick off the suicide of the thread // in accept_loops_thread_pool_ that blocks on // BluetoothServerSocket.accept(). That may take some time to complete, but // there's no particular reason to wait around for it. auto item = server_sockets_.extract(it); // Store a handle to the BluetoothServerSocket, so we can use it after // removing the entry from server_sockets_; making it scoped // is a bonus that takes care of deallocation before we leave this method. BluetoothServerSocket& listening_socket = item.mapped(); // Regardless of whether or not we fail to close the existing // BluetoothServerSocket, remove it from server_sockets_ so that it // frees up this service for another round. // Finally, close the BluetoothServerSocket. if (!listening_socket.Close().Ok()) { LOG(INFO) << "Failed to close BT server socket for " << service_id; return false; } return true; } ErrorOr BluetoothClassic::Connect( BluetoothDevice& bluetooth_device, const std::string& service_id, CancellationFlag* cancellation_flag) { { MutexLock lock(&mutex_); if (is_multiplex_enabled_) { LOG(INFO) << "multiplex_sockets_ size:" << multiplex_sockets_.size(); auto it = multiplex_sockets_.find(bluetooth_device.GetMacAddress()); if (it != multiplex_sockets_.end()) { MultiplexSocket* multiplex_socket = it->second; if (multiplex_socket->IsEnabled()) { auto* virtual_socket = multiplex_socket->EstablishVirtualSocket(service_id); // Should not happen. auto* bluetooth_socket = down_cast(virtual_socket); if (bluetooth_socket == nullptr) { LOG(INFO) << "Failed to cast to BluetoothSocket for " << service_id << " with " << bluetooth_device.GetName(); return {Error(OperationResultCode:: NEARBY_BT_VIRTUAL_SOCKET_CREATION_FAILURE)}; } return *bluetooth_socket; } } } } service_id_to_connect_attempts_count_map_[service_id] = 1; while (service_id_to_connect_attempts_count_map_[service_id] <= kConnectAttemptsLimit) { if (cancellation_flag->Cancelled()) { LOG(WARNING) << "Attempt #" << service_id_to_connect_attempts_count_map_[service_id] << ": Cannot start creating client BT socket due to cancel."; return {Error(OperationResultCode:: CLIENT_CANCELLATION_CANCEL_BT_OUTGOING_CONNECTION)}; } ErrorOr wrapper_result = AttemptToConnect(bluetooth_device, service_id, cancellation_flag); LOG(INFO) << "Attempt #" << service_id_to_connect_attempts_count_map_[service_id] << " to connect: " << (wrapper_result.has_value() ? wrapper_result.value().IsValid() : false); if (wrapper_result.has_value() && wrapper_result.value().IsValid()) { return std::move(wrapper_result.value()); } service_id_to_connect_attempts_count_map_[service_id]++; } LOG(WARNING) << "Giving up after " << kConnectAttemptsLimit << " attempts"; return {Error( OperationResultCode::DEVICE_STATE_ERROR_UNFINISHED_UPGRADE_ATTEMPTS)}; } ErrorOr BluetoothClassic::AttemptToConnect( BluetoothDevice& bluetooth_device, const std::string& service_id, CancellationFlag* cancellation_flag) { MutexLock lock(&mutex_); LOG(INFO) << "BluetoothClassic::Connect: service_id=" << service_id << ", device=" << &bluetooth_device; // Socket to return. To allow for NRVO to work, it has to be a single object. BluetoothSocket socket{}; if (service_id.empty()) { LOG(WARNING) << "Refusing to create client BT socket because service_id is empty."; return {Error(OperationResultCode::NEARBY_LOCAL_CLIENT_STATE_WRONG)}; } if (!radio_.IsEnabled()) { LOG(WARNING) << "Can't create client BT socket [service=" << service_id << "]: BT isn't enabled."; return {Error(OperationResultCode::MISCELLEANEOUS_BT_SYSTEM_SERVICE_NULL)}; } if (!IsAvailableLocked()) { LOG(WARNING) << "Can't create client BT socket [service=" << service_id << "]; BT isn't available."; return { Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLUETOOTH_NOT_AVAILABLE)}; } if (!bluetooth_device.IsValid()) { LOG(WARNING) << "Bluetooth device is not valid."; return {Error( OperationResultCode::CONNECTIVITY_BLUETOOTH_DEVICE_OBTAIN_FAILURE)}; } socket = medium_->ConnectToService( bluetooth_device, GenerateUuidFromString(service_id), cancellation_flag); // If the socket isn't valid or if the cancellation flag has fired during // `ConnectToService`, return an empty socket. There is no need for a // CancellationFlagListener because the attempt logic is not asynchronous. if (!socket.IsValid() || cancellation_flag->Cancelled()) { LOG(INFO) << "Failed to Connect via BT [service=" << service_id << "]"; return {Error( OperationResultCode::CONNECTIVITY_BT_CLIENT_SOCKET_CREATION_FAILURE)}; } if (is_multiplex_enabled_) { // New MultiplexSocket but default disabled, should be enabled after // negotiated auto physical_socket_ptr = std::make_shared(socket); MultiplexSocket* multiplex_socket = MultiplexSocket::CreateOutgoingSocket( std::move(physical_socket_ptr), service_id); auto* virtual_socket = multiplex_socket->GetVirtualSocket(service_id); // Should not happen. auto* bluetooth_socket = down_cast(virtual_socket); if (bluetooth_socket == nullptr) { LOG(INFO) << "Failed to cast to BluetoothSocket for " << service_id << " with " << bluetooth_device.GetName(); return {Error( OperationResultCode::NEARBY_BT_VIRTUAL_SOCKET_CREATION_FAILURE)}; } LOG(INFO) << "Multiplex socket created for " << bluetooth_device.GetName(); multiplex_sockets_.emplace(bluetooth_device.GetMacAddress(), multiplex_socket); return *bluetooth_socket; } return socket; } bool BluetoothClassic::HasDiscoveryCallbacks() const { MutexLock lock(&discovery_callbacks_mutex_); return !discovery_callbacks_.empty(); } void BluetoothClassic::RemoveDiscoveryCallback(const std::string& service_id) { MutexLock lock(&discovery_callbacks_mutex_); if (discovery_callbacks_.contains(service_id)) { discovery_callbacks_.erase(service_id); } } void BluetoothClassic::AddDiscoveryCallback(const std::string& service_id, DiscoveredDeviceCallback callback) { MutexLock lock(&discovery_callbacks_mutex_); discovery_callbacks_.insert({service_id, std::move(callback)}); } void BluetoothClassic::RemoveAllDiscoveryCallbacks() { MutexLock lock(&discovery_callbacks_mutex_); discovery_callbacks_.clear(); } BluetoothDevice BluetoothClassic::GetRemoteDevice( const std::string& mac_address) { MutexLock lock(&mutex_); if (!IsAvailableLocked()) { return {}; } return medium_->GetRemoteDevice(mac_address); } bool BluetoothClassic::IsDiscovering(const std::string& serviceId) const { MutexLock lock(&mutex_); return IsDiscoveringLocked(serviceId); ; } std::string BluetoothClassic::GetMacAddress() const { MutexLock lock(&mutex_); if (!IsAvailableLocked()) { return {}; } return medium_->GetMacAddress(); } std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) { return std::string(Uuid(data)); } } // namespace connections } // namespace nearby