Files
nearby/connections/implementation/mediums/bluetooth_classic.cc
T
2023-11-22 10:44:38 -08:00

464 lines
14 KiB
C++

// 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 <memory>
#include <string>
#include <utility>
#include "internal/platform/bluetooth_classic.h"
#include "internal/platform/logging.h"
#include "internal/platform/mutex_lock.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace connections {
namespace {
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
BluetoothClassic::BluetoothClassic(BluetoothRadio& radio)
: BluetoothClassic(radio, std::make_unique<BluetoothClassicMedium>(
radio.GetBluetoothAdapter())) {}
BluetoothClassic::BluetoothClassic(
BluetoothRadio& radio, std::unique_ptr<BluetoothClassicMedium> medium)
: radio_(radio),
adapter_(radio_.GetBluetoothAdapter()),
medium_(std::move(medium)) {}
BluetoothClassic::~BluetoothClassic() {
// Destructor is not taking locks, but methods it is calling are.
StopDiscovery();
while (!server_sockets_.empty()) {
StopAcceptingConnections(server_sockets_.begin()->first);
}
TurnOffDiscoverability();
// 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();
}
bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) {
NEARBY_LOGS(INFO) << "Turning on BT discoverability with device_name="
<< device_name;
MutexLock lock(&mutex_);
if (device_name.empty()) {
NEARBY_LOGS(INFO)
<< "Refusing to turn on BT discoverability. Empty device name.";
return false;
}
if (!radio_.IsEnabled()) {
NEARBY_LOGS(INFO) << "Can't turn on BT discoverability. BT is off.";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO)
<< "Can't turn on BT discoverability. BT is not available.";
return false;
}
if (IsDiscoverable()) {
NEARBY_LOGS(INFO) << "Refusing to turn on BT discoverability; new name='"
<< device_name << "'; current name='"
<< adapter_.GetName() << "'";
return false;
}
if (!ModifyDeviceName(device_name)) {
NEARBY_LOGS(INFO)
<< "Failed to turn on BT discoverability; failed to set name to "
<< device_name;
return false;
}
if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) {
NEARBY_LOGS(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 false;
}
NEARBY_LOGS(INFO) << "Turned on BT discoverability with device_name="
<< device_name;
return true;
}
bool BluetoothClassic::TurnOffDiscoverability() {
NEARBY_LOGS(INFO) << "Turning off Bluetooth discoverability.";
MutexLock lock(&mutex_);
if (!IsDiscoverable()) {
NEARBY_LOGS(INFO) << "Can't turn off BT discoverability; it is already off";
return false;
}
RestoreScanMode();
RestoreDeviceName();
NEARBY_LOGS(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_)) {
NEARBY_LOGS(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)) {
NEARBY_LOGS(INFO) << "Failed to restore original Bluetooth device name to "
<< original_device_name_;
return false;
}
original_device_name_.clear();
return true;
}
bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) {
MutexLock lock(&mutex_);
if (!radio_.IsEnabled()) {
NEARBY_LOGS(INFO) << "Can't discover BT devices because BT isn't enabled.";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO)
<< "Can't discover BT devices because BT isn't available.";
return false;
}
if (IsDiscovering()) {
NEARBY_LOGS(INFO)
<< "Refusing to start discovery of BT devices because another "
"discovery is already in-progress.";
return false;
}
if (!medium_->StartDiscovery(std::move(callback))) {
NEARBY_LOGS(INFO) << "Failed to start discovery of BT devices.";
return false;
}
// Mark the fact that we're currently performing a Bluetooth scan.
scan_info_.valid = true;
return true;
}
bool BluetoothClassic::StopDiscovery() {
MutexLock lock(&mutex_);
if (!IsDiscovering()) {
NEARBY_LOGS(INFO)
<< "Can't stop discovery of BT devices because it never started.";
return false;
}
if (!medium_->StopDiscovery()) {
NEARBY_LOGS(INFO) << "Failed to stop discovery of Bluetooth devices.";
return false;
}
scan_info_.valid = false;
return true;
}
bool BluetoothClassic::IsDiscovering() const { return scan_info_.valid; }
bool BluetoothClassic::StartAcceptingConnections(
const std::string& service_id, AcceptedConnectionCallback callback) {
MutexLock lock(&mutex_);
if (service_id.empty()) {
NEARBY_LOGS(INFO)
<< "Refusing to start accepting BT connections; service ID is empty.";
return false;
}
if (!radio_.IsEnabled()) {
NEARBY_LOGS(INFO) << "Can't create BT server socket [service=" << service_id
<< "]; BT is disabled.";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO) << "Can't start accepting BT connections [service="
<< service_id << "]; BT not available.";
return false;
}
if (IsAcceptingConnectionsLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Refusing to start accepting BT connections [service=" << service_id
<< "]; BT server is already in-progress with the same name.";
return false;
}
BluetoothServerSocket socket =
medium_->ListenForService(service_id, GenerateUuidFromString(service_id));
if (!socket.IsValid()) {
NEARBY_LOGS(INFO) << "Failed to start accepting Bluetooth connections for "
<< service_id;
return false;
}
// 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;
// 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]() mutable {
while (true) {
BluetoothSocket client_socket = server_socket.Accept();
if (!client_socket.IsValid()) {
server_socket.Close();
break;
}
if (callback) {
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()) {
NEARBY_LOGS(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()) {
NEARBY_LOGS(INFO) << "Can't stop accepting BT connections for "
<< service_id << " because it was never started.";
return false;
}
// 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()) {
NEARBY_LOGS(INFO) << "Failed to close BT server socket for " << service_id;
return false;
}
return true;
}
BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device,
const std::string& service_id,
CancellationFlag* cancellation_flag) {
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()) {
NEARBY_LOGS(WARNING)
<< "Attempt #"
<< service_id_to_connect_attempts_count_map_[service_id]
<< ": Cannot start creating client BT socket due to cancel.";
return BluetoothSocket();
}
NEARBY_LOGS(INFO) << "Attempt #"
<< service_id_to_connect_attempts_count_map_[service_id]
<< " to connect.";
auto wrapper_result =
AttemptToConnect(bluetooth_device, service_id, cancellation_flag);
if (wrapper_result.IsValid()) {
return wrapper_result;
}
service_id_to_connect_attempts_count_map_[service_id]++;
}
NEARBY_LOGS(WARNING) << "Giving up after " << kConnectAttemptsLimit
<< " attempts";
return BluetoothSocket();
}
BluetoothSocket BluetoothClassic::AttemptToConnect(
BluetoothDevice& bluetooth_device, const std::string& service_id,
CancellationFlag* cancellation_flag) {
MutexLock lock(&mutex_);
NEARBY_LOGS(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()) {
NEARBY_LOGS(INFO)
<< "Refusing to create client BT socket because service_id is empty.";
return socket;
}
if (!radio_.IsEnabled()) {
NEARBY_LOGS(INFO) << "Can't create client BT socket [service=" << service_id
<< "]: BT isn't enabled.";
return socket;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO) << "Can't create client BT socket [service=" << service_id
<< "]; BT isn't available.";
return socket;
}
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()) {
NEARBY_LOGS(INFO) << "Failed to Connect via BT [service=" << service_id
<< "]";
return BluetoothSocket();
}
return socket;
}
BluetoothDevice BluetoothClassic::GetRemoteDevice(
const std::string& mac_address) {
MutexLock lock(&mutex_);
if (!IsAvailableLocked()) {
return {};
}
return medium_->GetRemoteDevice(mac_address);
}
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