diff --git a/Package.swift b/Package.swift index 5eab087a..123aa8ea 100644 --- a/Package.swift +++ b/Package.swift @@ -527,6 +527,7 @@ let package = Package( "internal/platform/byte_array_test.cc", "internal/platform/bluetooth_utils_test.cc", "internal/platform/credential_storage_impl_test.cc", + "internal/platform/implementation/g3/ble_v2_test.cc", "internal/platform/input_stream_test.cc", "internal/platform/mac_address_test.cc", "internal/platform/single_thread_executor_test.cc", diff --git a/internal/platform/implementation/g3/BUILD b/internal/platform/implementation/g3/BUILD index 12717cc6..e26c2700 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -118,6 +118,22 @@ cc_library( ], ) +cc_test( + name = "comm_test", + srcs = ["ble_v2_test.cc"], + deps = [ + ":comm", + ":g3", + "//internal/platform:base", + "//internal/platform:uuid", + "//internal/platform/implementation:comm", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:string_view", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "crypto", testonly = True, diff --git a/internal/platform/implementation/g3/ble_v2.cc b/internal/platform/implementation/g3/ble_v2.cc index 94095edf..c6d4f2a5 100644 --- a/internal/platform/implementation/g3/ble_v2.cc +++ b/internal/platform/implementation/g3/ble_v2.cc @@ -148,19 +148,18 @@ Exception BleV2ServerSocket::DoClose() { return {Exception::kSuccess}; } -BleV2Medium::BleV2Medium(api::BluetoothAdapter& adapter) - : adapter_(dynamic_cast(&adapter)) { - CHECK(adapter_); - adapter_->SetBleV2Medium(this); +BleV2Medium::BleV2Medium(BluetoothAdapter& adapter) : adapter_(adapter) { + CHECK(&adapter_); + adapter_.SetBleV2Medium(this); is_extended_advertisements_available_ = MediumEnvironment::Instance().IsBleExtendedAdvertisementsAvailable(); MediumEnvironment::Instance().RegisterBleV2Medium(*this, - adapter_->GetUniqueId()); + adapter_.GetUniqueId()); } BleV2Medium::~BleV2Medium() { - adapter_->SetBleV2Medium(nullptr); + adapter_.SetBleV2Medium(nullptr); MediumEnvironment::Instance().UnregisterBleV2Medium(*this); } @@ -183,7 +182,7 @@ bool BleV2Medium::StartAdvertising( absl::MutexLock lock(&mutex_); MediumEnvironment::Instance().UpdateBleV2MediumForAdvertising( - /*enabled=*/true, *this, adapter_->GetUniqueId(), advertising_data); + /*enabled=*/true, *this, adapter_.GetUniqueId(), advertising_data); return true; } @@ -193,7 +192,7 @@ bool BleV2Medium::StopAdvertising() { BleAdvertisementData empty_advertisement_data = {}; MediumEnvironment::Instance().UpdateBleV2MediumForAdvertising( - /*enabled=*/false, *this, adapter_->GetUniqueId(), + /*enabled=*/false, *this, adapter_.GetUniqueId(), empty_advertisement_data); return true; } @@ -221,7 +220,7 @@ std::unique_ptr BleV2Medium::StartAdvertising( } absl::MutexLock lock(&mutex_); MediumEnvironment::Instance().UpdateBleV2MediumForAdvertising( - /*enabled=*/true, *this, adapter_->GetUniqueId(), advertising_data); + /*enabled=*/true, *this, adapter_.GetUniqueId(), advertising_data); return std::make_unique( AdvertisingSession{.stop_advertising = [this] { return StopAdvertising() @@ -736,7 +735,7 @@ std::unique_ptr BleV2Medium::Connect( CancellationFlag* cancellation_flag) { LOG(INFO) << "G3 Ble Connect [self]: medium=" << this << ", adapter=" << &GetAdapter() - << ", peripheral id=" << adapter_->GetUniqueId() + << ", peripheral id=" << adapter_.GetUniqueId() << ", service_id=" << service_id; // First, find an instance of remote medium, that exposed this peripheral. BleV2Medium* remote_medium = dynamic_cast( diff --git a/internal/platform/implementation/g3/ble_v2.h b/internal/platform/implementation/g3/ble_v2.h index 0757146f..879aff4a 100644 --- a/internal/platform/implementation/g3/ble_v2.h +++ b/internal/platform/implementation/g3/ble_v2.h @@ -131,7 +131,7 @@ class BleV2ServerSocket : public api::ble_v2::BleServerSocket { // Container of operations that can be performed over the BLE medium. class BleV2Medium : public api::ble_v2::BleMedium { public: - explicit BleV2Medium(api::BluetoothAdapter& adapter); + explicit BleV2Medium(BluetoothAdapter& adapter); ~BleV2Medium() override; // Returns true once the Ble advertising has been initiated. @@ -188,7 +188,7 @@ class BleV2Medium : public api::ble_v2::BleMedium { bool IsExtendedAdvertisementsAvailable() override; - BluetoothAdapter& GetAdapter() { return *adapter_; } + BluetoothAdapter& GetAdapter() { return adapter_; } private: class GattClient; @@ -304,7 +304,7 @@ class BleV2Medium : public api::ble_v2::BleMedium { bool IsStopped(Borrowable server); absl::Mutex mutex_; - BluetoothAdapter* adapter_; // Our device adapter; read-only. + BluetoothAdapter& adapter_; // Our device adapter; read-only. absl::flat_hash_map server_sockets_ ABSL_GUARDED_BY(mutex_); absl::flat_hash_set> diff --git a/internal/platform/implementation/g3/ble_v2_test.cc b/internal/platform/implementation/g3/ble_v2_test.cc new file mode 100644 index 00000000..20ab43b8 --- /dev/null +++ b/internal/platform/implementation/g3/ble_v2_test.cc @@ -0,0 +1,171 @@ +// Copyright 2025 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 "internal/platform/implementation/g3/ble_v2.h" + +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/g3/bluetooth_adapter.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace g3 { +namespace { + +using ::testing::Return; + +constexpr absl::string_view kTestUuid = "00000000-0000-0000-0000-000000000000"; +constexpr absl::string_view kTestServiceId = "test_service_id"; + +class MockBluetoothAdapter : public BluetoothAdapter { + public: + MOCK_METHOD(bool, IsValid, (), (const)); + MOCK_METHOD(bool, SetStatus, (api::BluetoothAdapter::Status status), + (override)); + MOCK_METHOD(bool, IsEnabled, (), (const, override)); + MOCK_METHOD(api::BluetoothAdapter::Status, GetStatus, (), (const)); + MOCK_METHOD(bool, SetName, (absl::string_view name), (override)); + MOCK_METHOD(bool, SetName, (absl::string_view name, bool persist), + (override)); + MOCK_METHOD(std::string, GetName, (), (const, override)); + MOCK_METHOD(std::string, GetMacAddress, (), (const, override)); + MOCK_METHOD(bool, SetScanMode, (api::BluetoothAdapter::ScanMode scan_mode), + (override)); + MOCK_METHOD(api::BluetoothAdapter::ScanMode, GetScanMode, (), + (const, override)); + MOCK_METHOD(int, GetNumSlots, (), (const)); + MOCK_METHOD(api::ble_v2::BlePeripheral::UniqueId, GetUniqueId, (), (const)); + MOCK_METHOD(void, SetBleV2Medium, (api::ble_v2::BleMedium * medium)); +}; + +TEST(BleV2MediumTest, StartAdvertising) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + api::ble_v2::BleAdvertisementData advertising_data; + advertising_data.is_extended_advertisement = false; + advertising_data.service_data.insert( + {Uuid(kTestUuid), ByteArray("test_service_data")}); + + auto advertising_session = ble_v2_medium.StartAdvertising( + advertising_data, + {.tx_power_level = api::ble_v2::TxPowerLevel::kLow, + .is_connectable = true}, + api::ble_v2::BleMedium::AdvertisingCallback{ + .start_advertising_result = [](absl::Status) {}, + }); + EXPECT_TRUE(advertising_session != nullptr); +} + +TEST(BleV2MediumTest, StopAdvertising) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + EXPECT_TRUE(ble_v2_medium.StopAdvertising()); +} + +TEST(BleV2MediumTest, StartScanning) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto scanning_session = ble_v2_medium.StartScanning( + Uuid(kTestUuid), api::ble_v2::TxPowerLevel::kLow, + api::ble_v2::BleMedium::ScanningCallback{ + .start_scanning_result = [](absl::Status) {}, + .advertisement_found_cb = [](api::ble_v2::BlePeripheral::UniqueId, + api::ble_v2::BleAdvertisementData) {}, + }); + EXPECT_TRUE(scanning_session != nullptr); +} + +TEST(BleV2MediumTest, StopScanning) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + EXPECT_TRUE(ble_v2_medium.StopScanning()); +} + +TEST(BleV2MediumTest, StartGattServer) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto gatt_server = ble_v2_medium.StartGattServer({}); + EXPECT_TRUE(gatt_server != nullptr); +} + +TEST(BleV2MediumTest, ConnectToGattServer) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto gatt_client = ble_v2_medium.ConnectToGattServer( + /*peripheral_id=*/1234, api::ble_v2::TxPowerLevel::kLow, {}); + EXPECT_TRUE(gatt_client == nullptr); +} + +TEST(BleV2MediumTest, OpenServerSocket) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto server_socket = + ble_v2_medium.OpenServerSocket(std::string(kTestServiceId)); + EXPECT_TRUE(server_socket != nullptr); +} + +TEST(BleV2MediumTest, Connect) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto socket = ble_v2_medium.Connect( + std::string(kTestServiceId), api::ble_v2::TxPowerLevel::kLow, + /*remote_peripheral_id=*/1234, /*cancellation_flag=*/nullptr); + EXPECT_TRUE(socket == nullptr); +} + +TEST(BleV2MediumTest, OpenL2capServerSocket) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + auto server_socket = + ble_v2_medium.OpenL2capServerSocket(std::string(kTestServiceId)); + EXPECT_TRUE(server_socket == nullptr); +} + +TEST(BleV2MediumTest, IsExtendedAdvertisementsAvailable) { + MockBluetoothAdapter mock_bluetooth_adapter; + EXPECT_CALL(mock_bluetooth_adapter, IsEnabled()).WillRepeatedly(Return(true)); + + BleV2Medium ble_v2_medium(mock_bluetooth_adapter); + EXPECT_FALSE(ble_v2_medium.IsExtendedAdvertisementsAvailable()); +} + +} // namespace +} // namespace g3 +} // namespace nearby diff --git a/internal/platform/implementation/g3/platform.cc b/internal/platform/implementation/g3/platform.cc index 61d98127..e508c606 100644 --- a/internal/platform/implementation/g3/platform.cc +++ b/internal/platform/implementation/g3/platform.cc @@ -194,7 +194,8 @@ std::unique_ptr ImplementationPlatform::CreateBleMedium( std::unique_ptr ImplementationPlatform::CreateBleV2Medium(api::BluetoothAdapter& adapter) { - return std::make_unique(adapter); + return std::make_unique( + dynamic_cast(adapter)); } std::unique_ptr