diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc index ef149a71..71845ca4 100644 --- a/cpp/core_v2/core.cc +++ b/cpp/core_v2/core.cc @@ -26,6 +26,8 @@ namespace location { namespace nearby { namespace connections { +constexpr absl::Duration Core::kWaitForDisconnect; + Core::~Core() { CountDownLatch latch(1); router_.ClientDisconnecting( diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index ae240509..94e2fc0f 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -35,6 +35,9 @@ namespace connections { using ::location::nearby::proto::connections::Medium; using ::securegcm::UKey2Handshake; +constexpr absl::Duration BasePcpHandler::kConnectionRequestReadTimeout; +constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; + BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager) : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index f5dbf43c..b0cfec25 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -30,6 +30,11 @@ namespace connections { using ::location::nearby::proto::connections::Medium; +constexpr absl::Duration EndpointManager::kKeepAliveWriteInterval; +constexpr absl::Duration EndpointManager::kKeepAliveReadTimeout; +constexpr absl::Duration EndpointManager::kProcessEndpointDisconnectionTimeout; +constexpr absl::Time EndpointManager::kInvalidTimestamp; + // A Runnable that continuously grabs the most recent EndpointChannel available // for an endpoint. // diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index 16b22c0a..afd9116a 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -20,7 +20,9 @@ cc_library( "ble_advertisement_header.cc", "ble_packet.cc", "bloom_filter.cc", + "bluetooth_classic.cc", "bluetooth_radio.cc", + "mediums.cc", "uuid.cc", ], hdrs = [ @@ -30,14 +32,17 @@ cc_library( "ble_packet.h", "ble_peripheral.h", "bloom_filter.h", + "bluetooth_classic.h", "bluetooth_radio.h", "lost_entity_tracker.h", + "mediums.h", "uuid.h", ], visibility = [ - "//core_v2/internal:__pkg__", + "//core_v2/internal:__subpackages__", ], deps = [ + "//core_v2:core_types", "//platform_v2/base", "//platform_v2/public:comm", "//platform_v2/public:logging", @@ -67,6 +72,7 @@ cc_library( cc_test( name = "core_v2_internal_mediums_test", + size = "small", srcs = [ "advertisement_read_result_test.cc", "ble_advertisement_header_test.cc", @@ -74,6 +80,7 @@ cc_test( "ble_packet_test.cc", "ble_peripheral_test.cc", "bloom_filter_test.cc", + "bluetooth_classic_test.cc", "bluetooth_radio_test.cc", "lost_entity_tracker_test.cc", "uuid_test.cc", @@ -82,6 +89,7 @@ cc_test( deps = [ ":mediums", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep "//platform_v2/public:comm", "//platform_v2/public:logging", diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc new file mode 100644 index 00000000..1e7f428d --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -0,0 +1,391 @@ +// 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 "core_v2/internal/mediums/bluetooth_classic.h" + +#include +#include +#include + +#include "core_v2/internal/mediums/uuid.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : radio_(radio) {} + +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(); +} + +bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) { + MutexLock lock(&mutex_); + + if (device_name.empty()) { + NEARBY_LOG(INFO, + "Refusing to turn on BT discoverability. Empty device name."); + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is off."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is not available."); + return false; + } + + if (IsDiscoverable()) { + NEARBY_LOG(INFO, + "Refusing to turn on BT discoverability; new name='%s'; " + "current name='%s'", + device_name.c_str(), adapter_.GetName().c_str()); + return false; + } + + if (!ModifyDeviceName(device_name)) { + NEARBY_LOG(INFO, + "Failed to turn on BT discoverability; " + "failed to set name to %s", + device_name.c_str()); + return false; + } + + if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) { + NEARBY_LOG(INFO, + "Failed to turn on BT discoverability; " + "failed to set scan_mode to %d", + ScanMode::kConnectableDiscoverable); + + // Don't forget to perform this rollback of the partial state changes we've + // made til now. + RestoreDeviceName(); + return false; + } + + NEARBY_LOG(INFO, "Turned on BT discoverability with device_name=%s", + device_name.c_str()); + return true; +} + +bool BluetoothClassic::TurnOffDiscoverability() { + MutexLock lock(&mutex_); + + if (!IsDiscoverable()) { + NEARBY_LOG(INFO, "Can't turn off BT discoverability; it is already off"); + return false; + } + + RestoreScanMode(); + RestoreDeviceName(); + + NEARBY_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); +} + +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_LOG(INFO, "Failed to restore original Bluetooth scan mode to %d", + 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_)) { + NEARBY_LOG(INFO, "Failed to restore original Bluetooth device name to %s", + original_device_name_.c_str()); + return false; + } + original_device_name_.clear(); + return true; +} + +bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) { + MutexLock lock(&mutex_); + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't enabled."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't available."); + return false; + } + + if (IsDiscovering()) { + NEARBY_LOG(INFO, + "Refusing to start discovery of BT devices because another " + "discovery is already in-progress."); + return false; + } + + if (!medium_.StartDiscovery(callback)) { + NEARBY_LOG(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_LOG(INFO, + "Can't stop discovery of BT devices because it never started."); + return false; + } + + if (!medium_.StopDiscovery()) { + NEARBY_LOG(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_name, AcceptedConnectionCallback callback) { + MutexLock lock(&mutex_); + + if (service_name.empty()) { + NEARBY_LOG( + INFO, + "Refusing to start accepting BT connections; service name is empty."); + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, + "Can't create BT server socket [service=%s]; BT is disabled.", + service_name.c_str()); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG( + INFO, + "Can't start accepting BT connections [service=%s]; BT not available.", + service_name.c_str()); + return false; + } + + if (IsAcceptingConnectionsLocked(service_name)) { + NEARBY_LOG(INFO, + "Refusing to start accepting BT connections [service=%s]; BT " + "server is already in-progress with the same name.", + service_name.c_str()); + return false; + } + + BluetoothServerSocket socket = medium_.ListenForService( + service_name, GenerateUuidFromString(service_name)); + if (!socket.IsValid()) { + NEARBY_LOG(INFO, "Failed to start accepting Bluetooth connections for %s.", + service_name.c_str()); + return false; + } + + // Mark the fact that there's an in-progress Bluetooth server accepting + // connections. + auto owned_socket = + server_sockets_.emplace(service_name, 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([callback = std::move(callback), + server_socket = std::move(owned_socket), + service_name]() mutable { + while (true) { + BluetoothSocket client_socket = server_socket.Accept(); + if (!client_socket.IsValid()) { + server_socket.Close(); + break; + } + + callback.accepted_cb(std::move(client_socket)); + } + }); + + return true; +} + +bool BluetoothClassic::IsAcceptingConnections(const std::string& service_name) { + MutexLock lock(&mutex_); + + return IsAcceptingConnectionsLocked(service_name); +} + +bool BluetoothClassic::IsAcceptingConnectionsLocked( + const std::string& service_name) { + return server_sockets_.find(service_name) != server_sockets_.end(); +} + +bool BluetoothClassic::StopAcceptingConnections( + const std::string& service_name) { + MutexLock lock(&mutex_); + + if (service_name.empty()) { + NEARBY_LOG(INFO, + "Unable to stop accepting BT connections because the " + "service_name is empty."); + return false; + } + + const auto& it = server_sockets_.find(service_name); + if (it == server_sockets_.end()) { + NEARBY_LOG(INFO, + "Can't stop accepting BT connections for %s because it was " + "never started.", + service_name.c_str()); + 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_LOG(INFO, "Failed to close BT server socket for %s.", + service_name.c_str()); + return false; + } + + return true; +} + +BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, + const std::string& service_name) { + MutexLock lock(&mutex_); + NEARBY_LOG(INFO, "BluetoothClassic::Connect: device=%p", &bluetooth_device); + // Socket to return. To allow for NRVO to work, it has to be a single object. + BluetoothSocket socket; + + if (service_name.empty()) { + NEARBY_LOG( + INFO, + "Refusing to create client BT socket because service_name is empty."); + return socket; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, + "Can't create client BT socket [service=%s]: BT isn't enabled.", + service_name.c_str()); + return socket; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG( + INFO, "Can't create client BT socket [service=%s]; BT isn't available.", + service_name.c_str()); + return socket; + } + + socket = medium_.ConnectToService(bluetooth_device, + GenerateUuidFromString(service_name)); + if (!socket.IsValid()) { + NEARBY_LOG(INFO, "Failed to Connect via BT [service=%s]", + service_name.c_str()); + } + + return socket; +} + +std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) { + return std::string(Uuid(data)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h new file mode 100644 index 00000000..aa3d2310 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -0,0 +1,192 @@ +// 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. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +class BluetoothClassic { + public: + using DiscoveredDeviceCallback = BluetoothClassicMedium::DiscoveryCallback; + using ScanMode = BluetoothAdapter::ScanMode; + + // Callback that is invoked when a new connection is accepted. + struct AcceptedConnectionCallback { + std::function accepted_cb = + DefaultCallback(); + }; + + explicit BluetoothClassic(BluetoothRadio& bluetooth_radio); + ~BluetoothClassic(); + + // Returns true, if BT communications are supported by a platform. + bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets custom device name, and then enables BT discoverable mode. + // Returns true, if name and scan mode are successfully set, and false + // otherwise. + // Called by server. + bool TurnOnDiscoverability(const std::string& device_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables BT discoverability, and restores scan mode and device name to + // what they were before the call to TurnOnDiscoverability(). + // Returns false if no successful call TurnOnDiscoverability() was previously + // made, otherwise returns true. + // Called by server. + bool TurnOffDiscoverability() ABSL_LOCKS_EXCLUDED(mutex_); + + // Enables BT discovery mode. Will report any discoverable devices in range + // through a callback. + // Returns true, if discovery mode was enabled, false otherwise. + // Called by client. + bool StartDiscovery(DiscoveredDeviceCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables BT discovery mode. + // Returns true, if discovery mode was previously enabled, false otherwise. + // Called by client. + bool StopDiscovery() ABSL_LOCKS_EXCLUDED(mutex_); + + // Starts a worker thread, creates a BT server socket, associates it with a + // service name; in a worker thread repeatedly calls ServerSocket::Accept(). + // Any connected sockets returned from Accept() are passed to a callback. + // Returns true, if server socket was successfully created, false otherwise. + // Called by server. + bool StartAcceptingConnections(const std::string& service_name, + AcceptedConnectionCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true, if object is currently running a Accept() loop. + bool IsAcceptingConnections(const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes server socket corresponding to a service name. This automatically + // terminates Accept() loop, if it were running. + // Called by server. + bool StopAcceptingConnections(const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if this object owns a valid platform implementation. + bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return medium_.IsValid(); + } + + // Returns true if this object has a valid BluetoothAdapter reference. + bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return adapter_.IsValid(); + } + + // Establishes connection to BT service that was might be started on another + // device with StartAcceptingConnections() using the same service_name. + // Blocks until connection is established, or server-side is terminated. + // Returns socket instance. On success, BluetoothSocket.IsValid() return true. + // Called by client. + BluetoothSocket Connect(BluetoothDevice& bluetooth_device, + const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + struct ScanInfo { + bool valid = false; + }; + + static constexpr int kMaxConcurrentAcceptLoops = 5; + + // Constructs UUID object from arbitrary string, using MD5 hash, and then + // converts UUID to a readable UUID string and returns it. + static std::string GenerateUuidFromString(const std::string& data); + + // Same as IsAvailable(), but must be called with mutex_ held. + bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsAcceptingConnections(), but must be called with mutex_ held. + bool IsAcceptingConnectionsLocked(const std::string& service_name) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns true, if discoverability is enabled with TurnOnDiscoverability(). + bool IsDiscoverable() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Assignes a different name to BT adapter. + // Returns true if successful. Stores original device name. + bool ModifyDeviceName(const std::string& device_name) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Changes current scan mode. This is an implementation of + // TurnDiscoveradility() method. Stores original scan mode. + bool ModifyScanMode(ScanMode scan_mode) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Restores original device name (the one before the very first call to + // ModifyDeviceName()). Returns true if successful. + bool RestoreScanMode() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Restores original device scan mode (the one before the very first call to + // ModifyScanMode()). Returns true if successful. + bool RestoreDeviceName() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns true if device is currently in discovery mode. + bool IsDiscovering() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_); + BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){ + radio_.GetBluetoothAdapter()}; + BluetoothClassicMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_}; + + // A bundle of state required to do a Bluetooth Classic scan. When non-null, + // we are currently performing a Bluetooth scan. + ScanInfo scan_info_ ABSL_GUARDED_BY(mutex_); + + // The original scan mode (that controls visibility to scanners) of the device + // before we modified it. Restored when we stop advertising. + ScanMode original_scan_mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kUnknown; + + // The original Bluetooth device name, before we modified it. If non-empty, we + // are currently Bluetooth discoverable. Restored when we stop advertising. + std::string original_device_name_ ABSL_GUARDED_BY(mutex_); + + // A thread pool dedicated to running all the accept loops from + // StartAcceptingConnections(). + MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops}; + + // A map of service Name -> ServerSocket. If map is non-empty, we + // are currently listening for incoming connections. + // BluetoothServerSocket instances are used from accept_loops_runner_, + // and thus require pointer stability. + absl::flat_hash_map server_sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc new file mode 100644 index 00000000..8796d277 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc @@ -0,0 +1,208 @@ +// 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 "core_v2/internal/mediums/bluetooth_classic.h" + +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); + +class BluetoothClassicTest : public ::testing::Test { + protected: + using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; + + BluetoothClassicTest() { + env_.Reset(); + radio_a_ = std::make_unique(); + radio_b_ = std::make_unique(); + bt_a_ = std::make_unique(*radio_a_); + bt_b_ = std::make_unique(*radio_b_); + radio_a_->GetBluetoothAdapter().SetName("Device-A"); + radio_b_->GetBluetoothAdapter().SetName("Device-B"); + radio_a_->Enable(); + radio_b_->Enable(); + env_.Sync(); + } + + ~BluetoothClassicTest() override { + env_.Sync(false); + radio_a_->Disable(); + radio_b_->Disable(); + bt_a_.reset(); + bt_b_.reset(); + env_.Sync(false); + radio_a_.reset(); + radio_b_.reset(); + env_.Reset(); + } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; + + std::unique_ptr radio_a_; + std::unique_ptr radio_b_; + std::unique_ptr bt_a_; + std::unique_ptr bt_b_; +}; + +TEST_F(BluetoothClassicTest, CanConstructValidObject) { + EXPECT_TRUE(bt_a_->IsMediumValid()); + EXPECT_TRUE(bt_a_->IsAdapterValid()); + EXPECT_TRUE(bt_a_->IsAvailable()); + EXPECT_TRUE(bt_b_->IsMediumValid()); + EXPECT_TRUE(bt_b_->IsAdapterValid()); + EXPECT_TRUE(bt_b_->IsAvailable()); + EXPECT_NE(&radio_a_->GetBluetoothAdapter(), &radio_b_->GetBluetoothAdapter()); +} + +TEST_F(BluetoothClassicTest, CanStartAdvertising) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); +} + +TEST_F(BluetoothClassicTest, CanStopAdvertising) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); + EXPECT_TRUE(bt_a_->TurnOffDiscoverability()); +} + +TEST_F(BluetoothClassicTest, CanStartDiscovery) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); + CountDownLatch latch(1); + EXPECT_TRUE(bt_b_->StartDiscovery({ + .device_discovered_cb = + [&latch](BluetoothDevice& device) { latch.CountDown(); }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_a_->TurnOffDiscoverability()); +} + +TEST_F(BluetoothClassicTest, CanStopDiscovery) { + CountDownLatch latch(1); + EXPECT_TRUE(bt_a_->StartDiscovery({ + .device_discovered_cb = + [&latch](BluetoothDevice& device) { latch.CountDown(); }, + })); + EXPECT_FALSE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_a_->StopDiscovery()); +} + +TEST_F(BluetoothClassicTest, CanStartAcceptingConnections) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + constexpr absl::string_view kServiceName{"service name"}; + + BluetoothRadio& radio_for_client = *radio_a_; + BluetoothRadio& radio_for_server = *radio_b_; + BluetoothClassic& bt_client = *bt_a_; + BluetoothClassic& bt_server = *bt_b_; + + EXPECT_TRUE(radio_for_client.IsEnabled()); + EXPECT_TRUE(radio_for_server.IsEnabled()); + + EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(), kDeviceName); + CountDownLatch latch(1); + BluetoothDevice discovered_device; + EXPECT_TRUE(bt_client.StartDiscovery({ + .device_discovered_cb = + [&latch, &discovered_device](BluetoothDevice& device) { + discovered_device = device; + NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device, + &device.GetImpl()); + latch.CountDown(); + }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.TurnOffDiscoverability()); + EXPECT_TRUE(discovered_device.IsValid()); + EXPECT_TRUE( + bt_server.StartAcceptingConnections(std::string(kServiceName), {})); + // Allow StartAcceptingConnections do something, before stopping it. + // This is best effort, because no callbacks are invoked in this scenario. + SystemClock::Sleep(kWaitDuration); + EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName))); +} + +TEST_F(BluetoothClassicTest, CanConnect) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + constexpr absl::string_view kServiceName{"service name"}; + + BluetoothRadio& radio_for_client = *radio_a_; + BluetoothRadio& radio_for_server = *radio_b_; + BluetoothClassic& bt_client = *bt_a_; + BluetoothClassic& bt_server = *bt_b_; + + EXPECT_TRUE(radio_for_client.IsEnabled()); + EXPECT_TRUE(radio_for_server.IsEnabled()); + + EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(), + std::string(kDeviceName)); + CountDownLatch latch(1); + BluetoothDevice discovered_device; + EXPECT_TRUE(bt_client.StartDiscovery({ + .device_discovered_cb = + [&latch, &discovered_device](BluetoothDevice& device) { + discovered_device = device; + NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device, + &device.GetImpl()); + latch.CountDown(); + }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.TurnOffDiscoverability()); + ASSERT_TRUE(discovered_device.IsValid()); + BluetoothSocket socket_for_server; + CountDownLatch accept_latch(1); + EXPECT_TRUE(bt_server.StartAcceptingConnections( + std::string(kServiceName), + { + .accepted_cb = + [&socket_for_server, &accept_latch](BluetoothSocket socket) { + socket_for_server = std::move(socket); + accept_latch.CountDown(); + }, + })); + BluetoothSocket socket_for_client = + bt_client.Connect(discovered_device, std::string(kServiceName)); + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName))); + EXPECT_TRUE(socket_for_server.IsValid()); + EXPECT_TRUE(socket_for_client.IsValid()); + EXPECT_TRUE(socket_for_server.GetRemoteDevice().IsValid()); + EXPECT_TRUE(socket_for_client.GetRemoteDevice().IsValid()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.cc b/cpp/core_v2/internal/mediums/bluetooth_radio.cc index e7ec394e..52970ec8 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_radio.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.cc @@ -22,6 +22,8 @@ namespace location { namespace nearby { namespace connections { +constexpr absl::Duration BluetoothRadio::kPauseBetweenToggle; + BluetoothRadio::BluetoothRadio() { if (!IsAdapterValid()) { NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported"); diff --git a/cpp/core_v2/internal/mediums/mediums.cc b/cpp/core_v2/internal/mediums/mediums.cc new file mode 100644 index 00000000..51661834 --- /dev/null +++ b/cpp/core_v2/internal/mediums/mediums.cc @@ -0,0 +1,31 @@ +// 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 "core_v2/internal/mediums/mediums.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothRadio& Mediums::GetBluetoothRadio() { + return bluetooth_radio_; +} + +BluetoothClassic& Mediums::GetBluetoothClassic() { + return bluetooth_classic_; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/mediums.h b/cpp/core_v2/internal/mediums/mediums.h new file mode 100644 index 00000000..1a41f28b --- /dev/null +++ b/cpp/core_v2/internal/mediums/mediums.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ +#define CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ + +#include "core_v2/internal/mediums/bluetooth_classic.h" +#include "core_v2/internal/mediums/bluetooth_radio.h" + +namespace location { +namespace nearby { +namespace connections { + +// Facilitates convenient and reliable usage of various wireless mediums. +class Mediums { + public: + Mediums() = default; + ~Mediums() = default; + + // Returns a handle to the Bluetooth radio. + BluetoothRadio& GetBluetoothRadio(); + + // Returns a handle to the Bluetooth Classic medium. + BluetoothClassic& GetBluetoothClassic(); + + private: + // The order of declaration is critical for both construction and + // destruction. + // + // 1) Construction: The individual mediums have a dependency on the + // corresponding radio, so the radio must be initialized first. + // + // 2) Destruction: The individual mediums should be shut down before the + // corresponding radio. + BluetoothRadio bluetooth_radio_; + BluetoothClassic bluetooth_classic_{bluetooth_radio_}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 49394a83..c411de8b 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -17,7 +17,7 @@ #include #include -#include "google/protobuf/message_lite.h" +#include "core/internal/message_lite.h" #include "platform_v2/base/byte_array.h" namespace location { @@ -28,7 +28,7 @@ namespace { using ExceptionOrOfflineFrame = ExceptionOr; using Medium = proto::connections::Medium; -using MessageLite = ::google3_proto_compat::MessageLite; +using MessageLite = ::google::protobuf::MessageLite; ByteArray ToBytes(OfflineFrame&& frame) { ByteArray bytes(frame.ByteSizeLong()); diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index 21267003..e3fe2dac 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -79,9 +79,11 @@ class ImplementationPlatform { // Protocol implementations, domain-specific support static std::unique_ptr CreateBluetoothAdapter(); - static std::unique_ptr CreateBluetoothClassicMedium(); - static std::unique_ptr CreateBleMedium(); - static std::unique_ptr CreateBleV2Medium(); + static std::unique_ptr CreateBluetoothClassicMedium( + BluetoothAdapter&); + static std::unique_ptr CreateBleMedium(BluetoothAdapter&); + static std::unique_ptr CreateBleV2Medium( + BluetoothAdapter&); static std::unique_ptr CreateServerSyncMedium(); static std::unique_ptr CreateWifiMedium(); static std::unique_ptr CreateWifiLanMedium(); diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 8f5176e9..1bbb3b86 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -35,8 +35,23 @@ MediumEnvironment& MediumEnvironment::Instance() { return *env; } +void MediumEnvironment::Start() { + if (!enabled_.exchange(true)) { + NEARBY_LOG(INFO, "MediumEnvironment::Start()"); + Reset(); + } +} + +void MediumEnvironment::Stop() { + if (enabled_.exchange(false)) { + NEARBY_LOG(INFO, "MediumEnvironment::Stop()"); + Sync(false); + } +} + void MediumEnvironment::Reset() { RunOnMediumEnvironmentThread([this]() { + NEARBY_LOG(INFO, "MediumEnvironment::Reset()"); bluetooth_adapters_.clear(); bluetooth_mediums_.clear(); }); @@ -45,6 +60,7 @@ void MediumEnvironment::Reset() { void MediumEnvironment::Sync(bool enable_notifications) { enable_notifications_ = enable_notifications; + NEARBY_LOG(INFO, "MediumEnvironment::sync(%d)", enable_notifications); int count = 0; do { CountDownLatch latch(1); @@ -64,6 +80,7 @@ void MediumEnvironment::Sync(bool enable_notifications) { void MediumEnvironment::OnBluetoothAdapterChangedState( api::BluetoothAdapter& adapter, api::BluetoothDevice& adapter_device, std::string name, bool enabled, api::BluetoothAdapter::ScanMode mode) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &adapter, &adapter_device, name = std::move(name), enabled, mode]() { NEARBY_LOG(INFO, @@ -88,6 +105,7 @@ void MediumEnvironment::OnDeviceStateChanged( BluetoothMediumContext& info, api::BluetoothDevice& device, const std::string& name, api::BluetoothAdapter::ScanMode mode, bool enabled) { + if (!enabled_) return; auto item = info.devices.find(&device); if (item == info.devices.end()) { NEARBY_LOG( @@ -150,6 +168,7 @@ void MediumEnvironment::RunOnMediumEnvironmentThread( void MediumEnvironment::RegisterBluetoothMedium( api::BluetoothClassicMedium& medium, api::BluetoothAdapter& medium_adapter) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium, &medium_adapter]() { auto& context = bluetooth_mediums_ .insert({&medium, @@ -170,6 +189,7 @@ void MediumEnvironment::RegisterBluetoothMedium( void MediumEnvironment::UpdateBluetoothMedium( api::BluetoothClassicMedium& medium, BluetoothDiscoveryCallback callback) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium, callback = std::move(callback)]() { auto item = bluetooth_mediums_.find(&medium); @@ -192,6 +212,7 @@ void MediumEnvironment::UpdateBluetoothMedium( void MediumEnvironment::UnregisterBluetoothMedium( api::BluetoothClassicMedium& medium) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium]() { auto item = bluetooth_mediums_.extract(&medium); if (item.empty()) return; diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index eeee0c0d..c8ebbad4 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -41,6 +41,16 @@ class MediumEnvironment { // Creates and returns a reference to the global test environment instance. static MediumEnvironment& Instance(); + // Global ON/OFF switch for medium environment. + // Start & Stop work as On/Off switch for this object. + // Default state (after creation) is ON, to make it compatible with early + // tests that are already using it and relying on it being ON. + + // Enables Medium environment. + void Start(); + // Disables Medium environment. + void Stop(); + // Clears state. No notifications are sent. void Reset(); @@ -109,6 +119,7 @@ class MediumEnvironment { api::BluetoothAdapter::ScanMode mode, bool enabled); void RunOnMediumEnvironmentThread(std::function runnable); + std::atomic_bool enabled_ = true; std::atomic_int job_count_ = 0; std::atomic_bool enable_notifications_ = false; SingleThreadExecutor executor_; diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 8ccb7d7f..cd94ab09 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -53,10 +53,12 @@ cc_library( testonly = True, srcs = [ "bluetooth_adapter.cc", + "bluetooth_classic.cc", "webrtc.cc", ], hdrs = [ "bluetooth_adapter.h", + "bluetooth_classic.h", "webrtc.h", ], visibility = [ @@ -65,8 +67,12 @@ cc_library( deps = [ ":types", "//platform_v2/api:comm", + "//platform_v2/base", + "//platform_v2/base:logging", "//platform_v2/base:test_util", "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", "//absl/strings", "//absl/synchronization", "//webrtc/api:create_peerconnection_factory", #buildcleaner: keep diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc index aee1559b..7dbe41e2 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -17,6 +17,7 @@ #include #include "platform_v2/base/medium_environment.h" +#include "platform_v2/impl/g3/bluetooth_classic.h" namespace location { namespace nearby { @@ -25,9 +26,13 @@ namespace g3 { BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) : adapter_(*adapter) {} +std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } + BluetoothAdapter::~BluetoothAdapter() { SetStatus(Status::kDisabled); } -std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } +void BluetoothAdapter::SetMedium(api::BluetoothClassicMedium* medium) { + medium_ = medium; +} bool BluetoothAdapter::SetStatus(Status status) { BluetoothAdapter::ScanMode mode; diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h index 39aaca42..90d4d18e 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.h +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -84,9 +84,13 @@ class BluetoothAdapter : public api::BluetoothAdapter { BluetoothDevice& GetDevice() { return device_; } + void SetMedium(api::BluetoothClassicMedium* medium); + api::BluetoothClassicMedium* GetMedium() { return medium_; } + private: mutable absl::Mutex mutex_; BluetoothDevice device_{this}; + api::BluetoothClassicMedium* medium_ = nullptr; ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; bool enabled_ ABSL_GUARDED_BY(mutex_) = false; diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc new file mode 100644 index 00000000..fb32c315 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -0,0 +1,254 @@ +// 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 "platform_v2/impl/g3/bluetooth_classic.h" + +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/logging.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +void BluetoothSocket::Connect(BluetoothSocket& other) { + absl::MutexLock lock(&mutex_); + remote_socket_ = &other; +} + +bool BluetoothSocket::IsConnected() const { + absl::MutexLock lock(&mutex_); + return IsConnectedLocked(); +} + +bool BluetoothSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +bool BluetoothSocket::IsConnectedLocked() const { + return remote_socket_ != nullptr; +} + +InputStream& BluetoothSocket::GetInputStream() { + auto* remote_socket = GetRemoteSocket(); + CHECK(remote_socket != nullptr); + return remote_socket->GetLocalInputStream(); +} + +OutputStream& BluetoothSocket::GetOutputStream() { + return GetLocalOutputStream(); +} + +InputStream& BluetoothSocket::GetLocalInputStream() { + absl::MutexLock lock(&mutex_); + return output_.GetInputStream(); +} + +OutputStream& BluetoothSocket::GetLocalOutputStream() { + absl::MutexLock lock(&mutex_); + return output_.GetOutputStream(); +} + +Exception BluetoothSocket::Close() { + BluetoothSocket* remote_socket = nullptr; + { + absl::MutexLock lock(&mutex_); + if (!closed_) { + remote_socket = remote_socket_; + output_.GetOutputStream().Close(); + output_.GetInputStream().Close(); + closed_ = true; + } + } + if (remote_socket != nullptr) { + remote_socket->Close(); + } + return {Exception::kSuccess}; +} + +BluetoothSocket* BluetoothSocket::GetRemoteSocket() { + absl::MutexLock lock(&mutex_); + return remote_socket_; +} + +BluetoothDevice* BluetoothSocket::GetRemoteDevice() { + BluetoothAdapter* remote_adapter = nullptr; + { + absl::MutexLock lock(&mutex_); + if (remote_socket_ == nullptr || remote_socket_->adapter_ == nullptr) { + return nullptr; + } + remote_adapter = remote_socket_->adapter_; + } + return remote_adapter ? &remote_adapter->GetDevice() : nullptr; +} + +std::unique_ptr BluetoothServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + while (pending_sockets_.empty()) { + cond_.Wait(&mutex_); + if (closed_) break; + } + // whether or not we were running in the wait loop, return early if closed. + if (closed_) return {}; + auto* remote_socket = + pending_sockets_.extract(pending_sockets_.begin()).value(); + CHECK(remote_socket); + auto local_socket = std::make_unique(adapter_); + local_socket->Connect(*remote_socket); + remote_socket->Connect(*local_socket); + cond_.SignalAll(); + return local_socket; +} + +bool BluetoothServerSocket::Connect(BluetoothSocket& socket) { + absl::MutexLock lock(&mutex_); + if (closed_) return false; + if (socket.IsConnected()) { + NEARBY_LOG(ERROR, + "Failed to connect to BT server socket: already connected"); + return true; // already connected. + } + // add client socket to the pending list + pending_sockets_.emplace(&socket); + cond_.SignalAll(); + while (!socket.IsConnected()) { + cond_.Wait(&mutex_); + if (closed_) return false; + } + return true; +} + +void BluetoothServerSocket::SetCloseNotifier(std::function notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +BluetoothServerSocket::~BluetoothServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +Exception BluetoothServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception BluetoothServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + +BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter) + // TODO(apolyudov): implement and use downcast<> with static assertions. + : adapter_(static_cast(&adapter)) { + adapter_->SetMedium(this); + auto& env = MediumEnvironment::Instance(); + env.RegisterBluetoothMedium(*this, GetAdapter()); +} + +BluetoothClassicMedium::~BluetoothClassicMedium() { + adapter_->SetMedium(nullptr); + auto& env = MediumEnvironment::Instance(); + env.UnregisterBluetoothMedium(*this); +} + +bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) { + auto& env = MediumEnvironment::Instance(); + env.UpdateBluetoothMedium(*this, std::move(callback)); + return true; +} + +bool BluetoothClassicMedium::StopDiscovery() { + auto& env = MediumEnvironment::Instance(); + env.UpdateBluetoothMedium(*this, {}); + return true; +} + +std::unique_ptr BluetoothClassicMedium::ConnectToService( + api::BluetoothDevice& remote_device, const std::string& service_uuid) { + NEARBY_LOG(INFO, + "G3 ConnectToService [self]: medium=%p, adapter=%p, device=%p", + this, &GetAdapter(), &GetAdapter().GetDevice()); + // First, find an instance of remote medium, that exposed this device. + auto& adapter = static_cast(remote_device).GetAdapter(); + auto* medium = static_cast(adapter.GetMedium()); + + if (!medium) return {}; // Adapter is not bound to medium. Bail out. + + BluetoothServerSocket* server_socket = nullptr; + NEARBY_LOG( + INFO, + "G3 ConnectToService [peer]: medium=%p, adapter=%p, device=%p, uuid=%s", + medium, &adapter, &remote_device, service_uuid.c_str()); + // Then, find our server socket context in this medium. + { + absl::MutexLock medium_lock(&medium->mutex_); + auto item = medium->sockets_.find(service_uuid); + server_socket = item != sockets_.end() ? item->second : nullptr; + if (server_socket == nullptr) { + NEARBY_LOG(ERROR, "Failed to find BT Server socket: uuid=%s", + service_uuid.c_str()); + return {}; + } + } + + auto socket = std::make_unique(&GetAdapter()); + // Finally, Request to connect to this socket. + if (!server_socket->Connect(*socket)) { + NEARBY_LOG(ERROR, "Failed to connect to existing BT Server socket: uuid=%s", + service_uuid.c_str()); + return {}; + } + + NEARBY_LOG(INFO, "G3 ConnectToService: connected: socket=%p", socket.get()); + return socket; +} + +std::unique_ptr +BluetoothClassicMedium::ListenForService(const std::string& service_name, + const std::string& service_uuid) { + auto socket = std::make_unique(GetAdapter()); + socket->SetCloseNotifier([this, uuid = service_uuid]() { + absl::MutexLock lock(&mutex_); + sockets_.erase(uuid); + }); + NEARBY_LOG(INFO, "Adding service: medium=%p, uuid=%s", this, + service_uuid.c_str()); + absl::MutexLock lock(&mutex_); + sockets_.emplace(service_uuid, socket.get()); + return socket; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h new file mode 100644 index 00000000..a10452b4 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -0,0 +1,232 @@ +// 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. + +#ifndef PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/pipe.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket : public api::BluetoothSocket { + public: + BluetoothSocket() = default; + explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {} + ~BluetoothSocket() override = default; + + // Connects to another BluetoothSocket, to form a functional low-level + // channel. From this point on, and until Close is called, connection exists. + void Connect(BluetoothSocket& other); + + // NOTE: + // It is an undefined behavior if GetInputStream() or GetOutputStream() is + // called for a not-connected BluetoothSocket, i.e. any object that is not + // returned by BluetoothClassicMedium::ConnectToService() for client side or + // BluetoothServerSocket::Accept() for server side of connection. + + // Returns the InputStream of this connected BluetoothSocket. + InputStream& GetInputStream() override; + + // Returns the OutputStream of this connected BluetoothSocket. + // This stream is for local side to write. + OutputStream& GetOutputStream() override; + + // Returns address of a remote BluetoothSocket or nullptr. + BluetoothSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if socket is closed. + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes both input and output streams, marks Socket as closed. + // After this call object should be treated as not connected. + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + // Returns valid BluetoothDevice pointer if there is a connection, and + // nullptr otherwise. + BluetoothDevice* GetRemoteDevice() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns InputStream of our side of a connection. + // This is what the remote side is supposed to read from. + // This is a helper for GetInputStream() method. + InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns OutputStream of our side of a connection. + // This is what the local size is supposed to write to. + // This is a helper for GetOutputStream() method. + OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // Output pipe is initialized by constructor, it remains always valid, until + // it is closed. it represents output part of a local socket. Input part of a + // local socket comes from the peer socket, after connection. + Pipe output_; + mutable absl::Mutex mutex_; + BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. + BluetoothSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket : public api::BluetoothServerSocket { + public: + explicit BluetoothServerSocket(BluetoothAdapter& adapter) + : adapter_(&adapter) {} + ~BluetoothServerSocket() override; + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + // + // Called by the server side of a connection. + // Returns BluetoothSocket to the server side. + // If not null, returned socket is connected to its remote (client-side) peer. + std::unique_ptr Accept() override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Blocks until either: + // - connection is available, or + // - server socket is closed, or + // - error happens. + // + // Called by the client side of a connection. + // socket is an initialized BluetoothSocket, associated with a client + // BluetoothAdapter. + // Returns true, if socket is successfully connected. + bool Connect(BluetoothSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_); + + // Called by the server side of a connection before passing ownership of + // BluetoothServerSocker to user, to track validity of a pointer to this + // server socket, + void SetCloseNotifier(std::function notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + // Calls close_notifier if it was previously set, and marks socket as closed. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + absl::Mutex mutex_; + absl::CondVar cond_; + BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. + absl::flat_hash_set pending_sockets_ + ABSL_GUARDED_BY(mutex_); + std::function close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium : public api::BluetoothClassicMedium { + public: + explicit BluetoothClassicMedium(api::BluetoothAdapter& adapter); + ~BluetoothClassicMedium() override; + + // NOTE(DiscoveryCallback): + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Connects to existing remote BT service. + // + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // On success, returns a new BluetoothSocket. + // On error, returns nullptr. + std::unique_ptr ConnectToService( + api::BluetoothDevice& remote_device, + const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothAdapter& GetAdapter() { return *adapter_; } + + // Creates BT service, and begins listening for remote attempts to connect. + // + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns nullptr on error. + std::unique_ptr ListenForService( + const std::string& service_name, const std::string& service_uuid) override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + BluetoothAdapter* adapter_; // Our device adapter; read-only. + absl::flat_hash_map sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index fec43769..02ba4786 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -35,6 +35,7 @@ #include "platform_v2/impl/g3/atomic_boolean.h" #include "platform_v2/impl/g3/atomic_reference_any.h" #include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/bluetooth_classic.h" #include "platform_v2/impl/g3/condition_variable.h" #include "platform_v2/impl/g3/count_down_latch.h" #include "platform_v2/impl/g3/multi_thread_executor.h" @@ -110,15 +111,18 @@ std::unique_ptr ImplementationPlatform::CreateOutputFile( } std::unique_ptr -ImplementationPlatform::CreateBluetoothClassicMedium() { - return std::unique_ptr(); +ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return absl::make_unique(adapter); } -std::unique_ptr ImplementationPlatform::CreateBleMedium() { +std::unique_ptr ImplementationPlatform::CreateBleMedium( + api::BluetoothAdapter& adapter) { return std::unique_ptr(); } -std::unique_ptr ImplementationPlatform::CreateBleV2Medium() { +std::unique_ptr ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { return std::unique_ptr(); } diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 7aa0fc0d..81859c10 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -42,11 +42,13 @@ cc_library( "//platform_v2/public:__pkg__", ], deps = [ + ":logging", "//platform_v2/api:platform", "//platform_v2/api:types", "//platform_v2/base", "//platform_v2/base:util", "//absl/base:core_headers", + "//absl/container:flat_hash_map", "//absl/time", "//absl/types:any", ], @@ -54,8 +56,12 @@ cc_library( cc_library( name = "comm", + srcs = [ + "bluetooth_classic.cc", + ], hdrs = [ "bluetooth_adapter.h", + "bluetooth_classic.h", "webrtc.h", ], visibility = [ @@ -63,8 +69,12 @@ cc_library( "//platform_v2/public:__pkg__", ], deps = [ + ":logging", + ":types", "//platform_v2/api:comm", "//platform_v2/api:platform", + "//platform_v2/base", + "//absl/container:flat_hash_map", "//absl/strings", "//webrtc/api:libjingle_peerconnection_api", ], @@ -87,10 +97,12 @@ cc_library( cc_test( name = "public_test", + size = "small", srcs = [ "atomic_boolean_test.cc", "atomic_reference_test.cc", "bluetooth_adapter_test.cc", + "bluetooth_classic_test.cc", "count_down_latch_test.cc", "crypto_test.cc", "future_test.cc", @@ -107,6 +119,7 @@ cc_test( ":logging", ":types", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep "//testing/base/public:gunit_main", "//absl/synchronization", diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h index a0e801eb..693e9e7b 100644 --- a/cpp/platform_v2/public/bluetooth_adapter.h +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -18,55 +18,81 @@ #include #include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" #include "platform_v2/api/platform.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice final { + public: + BluetoothDevice() = default; + BluetoothDevice(const BluetoothDevice&) = default; + BluetoothDevice& operator=(const BluetoothDevice&) = default; + explicit BluetoothDevice(api::BluetoothDevice* device) : impl_(device) {} + ~BluetoothDevice() = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const { return impl_->GetName(); } + + api::BluetoothDevice& GetImpl() { return *impl_; } + bool IsValid() const { return impl_ != nullptr; } + + private: + api::BluetoothDevice* impl_; +}; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html -class BluetoothAdapter : public api::BluetoothAdapter { +class BluetoothAdapter final { public: using Status = api::BluetoothAdapter::Status; using ScanMode = api::BluetoothAdapter::ScanMode; BluetoothAdapter() : impl_(api::ImplementationPlatform::CreateBluetoothAdapter()) {} - ~BluetoothAdapter() override = default; + ~BluetoothAdapter() = default; BluetoothAdapter(BluetoothAdapter&&) = default; BluetoothAdapter& operator=(BluetoothAdapter&&) = default; // Synchronously sets the status of the BluetoothAdapter to 'status', and // returns true if the operation was a success. - bool SetStatus(Status status) override { return impl_->SetStatus(status); } + bool SetStatus(Status status) { return impl_->SetStatus(status); } Status GetStatus() const { return IsEnabled() ? Status::kEnabled : Status::kDisabled; } // Returns true if the BluetoothAdapter's current status is // Status::Value::kEnabled. - bool IsEnabled() const override { return impl_->IsEnabled(); } + bool IsEnabled() const { return impl_->IsEnabled(); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() // // Returns ScanMode::kUnknown on error. - ScanMode GetScanMode() const override { return impl_->GetScanMode(); } + ScanMode GetScanMode() const { return impl_->GetScanMode(); } // Synchronously sets the scan mode of the adapter, and returns true if the // operation was a success. - bool SetScanMode(ScanMode scan_mode) override { + bool SetScanMode(ScanMode scan_mode) { return impl_->SetScanMode(scan_mode); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() // Returns an empty string on error - std::string GetName() const override { return impl_->GetName(); } + std::string GetName() const { return impl_->GetName(); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) - bool SetName(absl::string_view name) override { return impl_->SetName(name); } + bool SetName(absl::string_view name) { return impl_->SetName(name); } bool IsValid() const { return impl_ != nullptr; } + // Returns reference to platform implementation. + // This is used to communicate with platform code, and for debugging purposes. + // Returned reference will remain valid for while BluetoothAdapter object is + // itself valid. It matches Core() object lifetime. + api::BluetoothAdapter& GetImpl() { return *impl_; } + private: std::unique_ptr impl_; }; diff --git a/cpp/platform_v2/public/bluetooth_classic.cc b/cpp/platform_v2/public/bluetooth_classic.cc new file mode 100644 index 00000000..83d8cda0 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic.cc @@ -0,0 +1,99 @@ +// 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 "platform_v2/public/bluetooth_classic.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +BluetoothClassicMedium::~BluetoothClassicMedium() { StopDiscovery(); } + +BluetoothSocket BluetoothClassicMedium::ConnectToService( + BluetoothDevice& remote_device, const std::string& service_uuid) { + NEARBY_LOG(INFO, + "BluetoothClassicMedium::ConnectToService: device=%p [impl=%p]", + &remote_device, &remote_device.GetImpl()); + return BluetoothSocket( + impl_->ConnectToService(remote_device.GetImpl(), service_uuid)); +} + +bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) { + { + MutexLock lock(&mutex_); + if (discovery_enabled_) { + NEARBY_LOG(INFO, "BT Discovery already enabled; impl=%p", &GetImpl()); + return false; + } + discovery_callback_ = std::move(callback); + devices_.clear(); + discovery_enabled_ = true; + NEARBY_LOG(INFO, "BT Discovery enabled; impl=%p", &GetImpl()); + } + return impl_->StartDiscovery({ + .device_discovered_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto pair = devices_.emplace( + &device, absl::make_unique()); + auto& context = *pair.first->second; + if (!pair.second) { + NEARBY_LOG(INFO, "Adding (again) device=%p, impl=%p", + &context.device, &device); + return; + } + context.device = BluetoothDevice(&device); + NEARBY_LOG(INFO, "Adding device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_discovered_cb(context.device); + }, + .device_name_changed_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto& context = *devices_[&device]; + NEARBY_LOG(INFO, "Renaming device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_name_changed_cb(context.device); + }, + .device_lost_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto item = devices_.extract(&device); + auto& context = *item.mapped(); + NEARBY_LOG(INFO, "Removing device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_lost_cb(context.device); + }, + }); +} + +bool BluetoothClassicMedium::StopDiscovery() { + { + MutexLock lock(&mutex_); + if (!discovery_enabled_) return true; + discovery_enabled_ = false; + discovery_callback_ = {}; + devices_.clear(); + NEARBY_LOG(INFO, "BT Discovery disabled: impl=%p", &GetImpl()); + } + return impl_->StopDiscovery(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h new file mode 100644 index 00000000..3029bb78 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -0,0 +1,219 @@ +// 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. + +#ifndef PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket final { + public: + BluetoothSocket() = default; + BluetoothSocket(const BluetoothSocket&) = default; + BluetoothSocket& operator=(const BluetoothSocket&) = default; + explicit BluetoothSocket(std::unique_ptr socket) + : impl_(socket.release()) {} + ~BluetoothSocket() = default; + + // Returns the InputStream of this connected BluetoothSocket. + InputStream& GetInputStream() { return impl_->GetInputStream(); } + + // Returns the OutputStream of this connected BluetoothSocket. + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + + // Closes both input and output streams, marks Socket as closed. + // After this call object should be treated as not connected. + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + BluetoothDevice GetRemoteDevice() { + return BluetoothDevice(impl_->GetRemoteDevice()); + } + + // Returns true if a socket is usable. If this method returns false, + // it is not safe to call any other method. + // NOTE(socket validity): + // Socket created by a default public constructor is not valid, because + // it is missing platform implementation. + // The only way to obtain a valid socket is through connection, such as + // an object returned by either BluetoothClassicMedium::ConnectTotService or + // BluetoothServerSocket::Accept(). + // These methods may also return an invalid socket if connection failed for + // any reason. + bool IsValid() const { return impl_ != nullptr; } + + // Returns reference to platform implementation. + // This is used to communicate with platform code, and for debugging purposes. + // Returned reference will remain valid for while BluetoothSocket object is + // itself valid. Typically BluetoothSocket lifetime matches duration of the + // connection, and is controlled by end user, since they hold the instance. + api::BluetoothSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket final { + public: + BluetoothServerSocket() = default; + BluetoothServerSocket(const BluetoothServerSocket&) = default; + BluetoothServerSocket& operator=(const BluetoothServerSocket&) = default; + ~BluetoothServerSocket() = default; + explicit BluetoothServerSocket( + std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + BluetoothSocket Accept() { return BluetoothSocket(impl_->Accept()); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + bool IsValid() const { return impl_ != nullptr; } + api::BluetoothServerSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium final { + public: + using Platform = api::ImplementationPlatform; + struct DiscoveryCallback { + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + std::function device_discovered_cb = + DefaultCallback(); + std::function device_name_changed_cb = + DefaultCallback(); + std::function device_lost_cb = + DefaultCallback(); + }; + struct DeviceDiscoveryInfo { + BluetoothDevice device; + }; + + explicit BluetoothClassicMedium(BluetoothAdapter& adapter) + : impl_(Platform::CreateBluetoothClassicMedium(adapter.GetImpl())), + adapter_(adapter) {} + + ~BluetoothClassicMedium(); + + // NOTE(DiscoveryCallback): + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback callback); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery(); + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns a new BluetoothSocket. On Success, BluetoothSocket::IsValid() + // returns true. + BluetoothSocket ConnectToService(BluetoothDevice& remote_device, + const std::string& service_uuid); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns a new BluetoothServerSocket. + // On Success, BluetoothServerSocket::IsValid() returns true. + BluetoothServerSocket ListenForService(const std::string& service_name, + const std::string& service_uuid) { + return BluetoothServerSocket( + impl_->ListenForService(service_name, service_uuid)); + } + + bool IsValid() const { return impl_ != nullptr; } + + api::BluetoothClassicMedium& GetImpl() { return *impl_; } + BluetoothAdapter& GetAdapter() { return adapter_; } + + private: + Mutex mutex_; + std::unique_ptr impl_; + BluetoothAdapter& adapter_; + absl::flat_hash_map> + devices_ ABSL_GUARDED_BY(mutex_); + DiscoveryCallback discovery_callback_ ABSL_GUARDED_BY(mutex_); + bool discovery_enabled_ ABSL_GUARDED_BY(mutex_) = false; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/public/bluetooth_classic_test.cc b/cpp/platform_v2/public/bluetooth_classic_test.cc new file mode 100644 index 00000000..273eec94 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic_test.cc @@ -0,0 +1,211 @@ +// 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 "platform_v2/public/bluetooth_classic.h" + +#include + +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/single_thread_executor.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +class BluetoothClassicMediumTest : public ::testing::Test { + protected: + using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; + BluetoothClassicMediumTest() { + env_.Reset(); + adapter_a_ = std::make_unique(); + adapter_b_ = std::make_unique(); + bt_a_ = std::make_unique(*adapter_a_); + bt_b_ = std::make_unique(*adapter_b_); + adapter_a_->SetName("Device-A"); + adapter_b_->SetName("Device-B"); + adapter_a_->SetStatus(BluetoothAdapter::Status::kEnabled); + adapter_b_->SetStatus(BluetoothAdapter::Status::kEnabled); + env_.Sync(); + } + ~BluetoothClassicMediumTest() override { + env_.Sync(false); + adapter_a_->SetStatus(BluetoothAdapter::Status::kDisabled); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + bt_a_.reset(); + bt_b_.reset(); + env_.Sync(false); + adapter_a_.reset(); + adapter_b_.reset(); + env_.Reset(); + } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; + + std::unique_ptr adapter_a_; + std::unique_ptr adapter_b_; + std::unique_ptr bt_a_; + std::unique_ptr bt_b_; +}; + +TEST_F(BluetoothClassicMediumTest, ConstructorDestructorWorks) { + // Make sure we can create functional adapters. + ASSERT_TRUE(adapter_a_->IsValid()); + ASSERT_TRUE(adapter_b_->IsValid()); + + // Make sure we can create 2 distinct adapters. + // NOTE: multiple adapters are supported on a test platform, but not + // necessarily on every available HW platform. + // Often, HW platform supports only one BT adapter. + EXPECT_NE(&adapter_a_->GetImpl(), &adapter_b_->GetImpl()); + + // Make sure we can create functional mediums. + ASSERT_TRUE(bt_a_->IsValid()); + ASSERT_TRUE(bt_b_->IsValid()); + + // Make sure we can create 2 distinct mediums. + EXPECT_NE(&bt_a_->GetImpl(), &bt_b_->GetImpl()); +} + +TEST_F(BluetoothClassicMediumTest, CanStartDiscovery) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + .device_lost_cb = + [this, &lost_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device lost: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + lost_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + EXPECT_FALSE(adapter_b_->IsEnabled()); + EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result()); +} + +TEST_F(BluetoothClassicMediumTest, CanStopDiscovery) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + .device_lost_cb = + [this, &lost_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device lost: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + lost_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + bt_a_->StopDiscovery(); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + EXPECT_FALSE(adapter_b_->IsEnabled()); + EXPECT_FALSE(lost_latch.Await(absl::Milliseconds(1000)).result()); +} + +TEST_F(BluetoothClassicMediumTest, CanListenForService) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + std::string service_name{"service"}; + std::string service_uuid("service-uuid"); + BluetoothServerSocket server_socket = + bt_b_->ListenForService(service_name, service_uuid); + EXPECT_TRUE(server_socket.IsValid()); + server_socket.Close(); +} + +TEST_F(BluetoothClassicMediumTest, CanConnectToService) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + BluetoothDevice* discovered_device = nullptr; + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch, &discovered_device](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + discovered_device = &device; + found_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + std::string service_name{"service"}; + std::string service_uuid("service-uuid"); + BluetoothServerSocket server_socket = + bt_b_->ListenForService(service_name, service_uuid); + EXPECT_TRUE(server_socket.IsValid()); + BluetoothSocket socket_a; + BluetoothSocket socket_b; + EXPECT_FALSE(socket_a.IsValid()); + EXPECT_FALSE(socket_b.IsValid()); + { + SingleThreadExecutor server_executor; + SingleThreadExecutor client_executor; + client_executor.Execute( + [this, &socket_a, discovered_device, &service_uuid, &server_socket]() { + socket_a = bt_a_->ConnectToService(*discovered_device, service_uuid); + if (!socket_a.IsValid()) server_socket.Close(); + }); + server_executor.Execute( + [&socket_b, &server_socket]() { + socket_b = server_socket.Accept(); + if (!socket_b.IsValid()) server_socket.Close(); + }); + } + EXPECT_TRUE(socket_a.IsValid()); + EXPECT_TRUE(socket_b.IsValid()); + server_socket.Close(); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 0beb117a..6f353a19 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - syntax = "proto2"; package location.nearby.proto.connections; diff --git a/script/handle_master.sh b/script/handle_master.sh new file mode 100755 index 00000000..82a35602 --- /dev/null +++ b/script/handle_master.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# 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. + +./oss.py --google3-filter --proto-lite-runtime .. diff --git a/script/handle_oss.sh b/script/handle_oss.sh index 68d3e1af..8c5dfdad 100755 --- a/script/handle_oss.sh +++ b/script/handle_oss.sh @@ -18,3 +18,5 @@ ./oss.py --all --no-subst --fix-oss-headers . ./oss.py --all --google3-filter --no-subst --fix-oss-headers --no-recurse .. ./oss.py --google3-filter --fix-oss-headers --proto-lite-runtime .. +cd .. +/google/bin/releases/opensource/thirdparty/cross/cross . diff --git a/script/oss.py b/script/oss.py index cb8119e0..bcf835bf 100755 --- a/script/oss.py +++ b/script/oss.py @@ -103,6 +103,7 @@ def post_process_oss_files(path, args): else: top_dirs = ["cpp", "proto"] transforms = ( + ("::google3_proto_compat::MessageLite", "::google::protobuf::MessageLite"), ("third_party/webrtc/files/stable/", ""), ("webrtc/files/stable/", ""), ("third_party/", ""),