chromium/chrome/services/sharing/nearby/platform/bluetooth_classic_medium_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/services/sharing/nearby/platform/bluetooth_classic_medium.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/services/sharing/nearby/test_support/fake_adapter.h"
#include "components/cross_device/nearby/nearby_features.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace nearby {
namespace chrome {

namespace {
const char kDeviceAddress1[] = "DeviceAddress1";
const char kDeviceAddress2[] = "DeviceAddress2";
const char kDeviceName1[] = "DeviceName1";
const char kDeviceName2[] = "DeviceName2";
const char kNearbySharingServiceName[] = "NearbySharing";
const device::BluetoothUUID kNearbySharingServiceUuid =
    device::BluetoothUUID("a82efa21-ae5c-3dde-9bbc-f16da7b16c5a");
const base::TimeDelta kStaleDeviceTimeout = base::Seconds(20);
}  // namespace

class BluetoothClassicMediumTest : public testing::Test {
 public:
  BluetoothClassicMediumTest() = default;
  ~BluetoothClassicMediumTest() override = default;
  BluetoothClassicMediumTest(const BluetoothClassicMediumTest&) = delete;
  BluetoothClassicMediumTest& operator=(const BluetoothClassicMediumTest&) =
      delete;

  void SetUp() override {
    auto fake_adapter = std::make_unique<bluetooth::FakeAdapter>();
    fake_adapter_ = fake_adapter.get();

    mojo::PendingRemote<bluetooth::mojom::Adapter> pending_adapter;

    mojo::MakeSelfOwnedReceiver(
        std::move(fake_adapter),
        pending_adapter.InitWithNewPipeAndPassReceiver());

    remote_adapter_.Bind(std::move(pending_adapter),
                         /*bind_task_runner=*/nullptr);

    bluetooth_classic_medium_ =
        std::make_unique<BluetoothClassicMedium>(remote_adapter_);

    discovery_callback_ = {
        .device_discovered_cb =
            [this](api::BluetoothDevice& device) {
              last_device_discovered_ = &device;
              std::move(on_device_discovered_callback_).Run();
            },
        .device_name_changed_cb =
            [this](api::BluetoothDevice& device) {
              last_device_name_changed_ = &device;
              std::move(on_device_name_changed_callback_).Run();
            },
        .device_lost_cb =
            [this](api::BluetoothDevice& device) {
              DCHECK_EQ(expected_last_device_lost_, &device);
              std::move(on_device_lost_callback_).Run();
            }};
  }

 protected:
  void StartDiscovery() {
    EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
    EXPECT_TRUE(bluetooth_classic_medium_->StartDiscovery(
        std::move(discovery_callback_)));
    EXPECT_TRUE(fake_adapter_->IsDiscoverySessionActive());
  }

  void StopDiscovery() {
    base::RunLoop run_loop;
    fake_adapter_->SetDiscoverySessionDestroyedCallback(run_loop.QuitClosure());
    EXPECT_TRUE(bluetooth_classic_medium_->StopDiscovery());
    run_loop.Run();

    EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
  }

  void NotifyDeviceAdded(const std::string& address, const std::string& name) {
    base::RunLoop run_loop;
    on_device_discovered_callback_ = run_loop.QuitClosure();
    fake_adapter_->NotifyDeviceAdded(CreateDeviceInfo(address, name));
    run_loop.Run();
  }

  void NotifyDeviceChanged(const std::string& address,
                           const std::string& name) {
    base::RunLoop run_loop;
    on_device_name_changed_callback_ = run_loop.QuitClosure();
    fake_adapter_->NotifyDeviceChanged(CreateDeviceInfo(address, name));
    run_loop.Run();
  }

  void NotifyDeviceRemoved(const std::string& address,
                           const std::string& name) {
    base::RunLoop run_loop;
    on_device_lost_callback_ = run_loop.QuitClosure();
    fake_adapter_->NotifyDeviceRemoved(CreateDeviceInfo(address, name));
    run_loop.Run();
  }

  raw_ptr<bluetooth::FakeAdapter> fake_adapter_;
  mojo::SharedRemote<bluetooth::mojom::Adapter> remote_adapter_;
  std::unique_ptr<BluetoothClassicMedium> bluetooth_classic_medium_;
  BluetoothClassicMedium::DiscoveryCallback discovery_callback_;

  raw_ptr<api::BluetoothDevice, DanglingUntriaged> last_device_discovered_ =
      nullptr;
  raw_ptr<api::BluetoothDevice, DanglingUntriaged> last_device_name_changed_ =
      nullptr;
  raw_ptr<api::BluetoothDevice, DanglingUntriaged> expected_last_device_lost_ =
      nullptr;

  base::OnceClosure on_device_discovered_callback_;
  base::OnceClosure on_device_name_changed_callback_;
  base::OnceClosure on_device_lost_callback_;

  bluetooth::mojom::DeviceInfoPtr CreateDeviceInfo(const std::string& address,
                                                   const std::string& name) {
    auto device_info = bluetooth::mojom::DeviceInfo::New();
    device_info->address = address;
    device_info->name = name;
    device_info->name_for_display = name;
    return device_info;
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};

TEST_F(BluetoothClassicMediumTest, TestDiscovery_StartDiscoveryError) {
  fake_adapter_->SetShouldDiscoverySucceed(false);
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
  EXPECT_FALSE(bluetooth_classic_medium_->StartDiscovery(
      std::move(discovery_callback_)));
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
}

TEST_F(BluetoothClassicMediumTest,
       TestDiscovery_BluetoothClassicScanningFlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{
          ::features::kEnableNearbyBluetoothClassicScanning});

  // When classic scanning flag is disabled, Discovery will fail.
  fake_adapter_->SetShouldDiscoverySucceed(true);
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
  EXPECT_FALSE(bluetooth_classic_medium_->StartDiscovery(
      std::move(discovery_callback_)));
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
}

TEST_F(BluetoothClassicMediumTest,
       TestDiscovery_BluetoothClassicScanningFlagEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBluetoothClassicScanning},
      /*disabled_features=*/{});

  // When classic scanning flag is enabled, normal Discovery operation is not
  // impacted.
  fake_adapter_->SetShouldDiscoverySucceed(false);
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());
  EXPECT_FALSE(bluetooth_classic_medium_->StartDiscovery(
      std::move(discovery_callback_)));
  EXPECT_FALSE(fake_adapter_->IsDiscoverySessionActive());

  fake_adapter_->SetShouldDiscoverySucceed(true);
  EXPECT_TRUE(bluetooth_classic_medium_->StartDiscovery(
      std::move(discovery_callback_)));
  EXPECT_TRUE(fake_adapter_->IsDiscoverySessionActive());
}

TEST_F(BluetoothClassicMediumTest,
       TestDiscovery_GetRemoteDevice_GetUndiscovered) {
  StartDiscovery();

  EXPECT_TRUE(bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress1));

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest,
       TestDiscovery_DeviceDiscovered_BluetoothClassicDevice) {
  StartDiscovery();

  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  EXPECT_TRUE(bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress1));
  EXPECT_EQ(last_device_discovered_,
            bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress1));
  EXPECT_EQ(kDeviceName1, last_device_discovered_->GetName());

  auto* first_device_discovered = last_device_discovered_.get();

  NotifyDeviceAdded(kDeviceAddress2, kDeviceName2);
  EXPECT_TRUE(bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress2));
  EXPECT_EQ(last_device_discovered_,
            bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress2));
  EXPECT_EQ(kDeviceName2, last_device_discovered_->GetName());

  EXPECT_NE(first_device_discovered, last_device_discovered_);

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest, TestDiscovery_StaleDeviceRemoved) {
  StartDiscovery();

  bool device_lost_called = false;
  on_device_lost_callback_ =
      base::BindOnce([](bool* called) { *called = true; }, &device_lost_called);

  task_environment_.FastForwardBy(0.75 * kStaleDeviceTimeout);
  EXPECT_FALSE(device_lost_called);

  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  EXPECT_TRUE(bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress1));
  EXPECT_EQ(last_device_discovered_,
            bluetooth_classic_medium_->GetRemoteDevice(kDeviceAddress1));
  EXPECT_EQ(kDeviceName1, last_device_discovered_->GetName());
  expected_last_device_lost_ = last_device_discovered_;

  // Trigger an adapter event before the device times out. Verify that the
  // device's time is renewed.
  task_environment_.FastForwardBy(0.75 * kStaleDeviceTimeout);
  EXPECT_FALSE(device_lost_called);
  NotifyDeviceChanged(kDeviceAddress1, kDeviceName2);
  task_environment_.FastForwardBy(0.75 * kStaleDeviceTimeout);
  EXPECT_FALSE(device_lost_called);

  // Wait for the device to time out. Verify that it is reported lost.
  task_environment_.FastForwardBy(1.5 * kStaleDeviceTimeout);
  EXPECT_TRUE(device_lost_called);

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest,
       TestDiscovery_DeviceDiscovered_BleAdvertisement) {
  StartDiscovery();

  on_device_discovered_callback_ = base::BindOnce([]() { FAIL(); });

  // Do not set |name|. This reflects Chrome's usual representation of a BLE
  // advertisement.
  auto device_info = bluetooth::mojom::DeviceInfo::New();
  device_info->address = kDeviceAddress1;
  device_info->name_for_display = kDeviceAddress1;

  fake_adapter_->NotifyDeviceAdded(std::move(device_info));

  EXPECT_FALSE(last_device_discovered_);

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest, TestDiscovery_DeviceNameChanged) {
  StartDiscovery();

  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  EXPECT_EQ(kDeviceName1, last_device_discovered_->GetName());

  NotifyDeviceChanged(kDeviceAddress1, kDeviceName2);
  EXPECT_EQ(kDeviceName2, last_device_name_changed_->GetName());

  EXPECT_EQ(last_device_name_changed_, last_device_discovered_);

  // It is possible for DeviceChanged to trigger without a name change. This
  // previously caused a name change event. Here we verify if the name
  // does not change then we do not see the name change event.
  last_device_name_changed_ = nullptr;
  // We have to call NotifyDeviceChanged directly since we don't expect the
  // callback to be invoked.
  base::RunLoop run_loop;
  fake_adapter_->NotifyDeviceChanged(
      CreateDeviceInfo(kDeviceAddress1, kDeviceName2));
  run_loop.RunUntilIdle();
  EXPECT_EQ(nullptr, last_device_name_changed_.get());

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest, TestDiscovery_DeviceLost) {
  StartDiscovery();

  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  EXPECT_EQ(kDeviceName1, last_device_discovered_->GetName());

  expected_last_device_lost_ = last_device_discovered_;
  NotifyDeviceRemoved(kDeviceAddress1, kDeviceName1);

  StopDiscovery();
}

TEST_F(BluetoothClassicMediumTest, TestConnectToService_Success) {
  StartDiscovery();
  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  StopDiscovery();

  fake_adapter_->AllowConnectionForAddressAndUuidPair(
      kDeviceAddress1, kNearbySharingServiceUuid);

  auto cancellation_flag = std::make_unique<CancellationFlag>();
  auto bluetooth_socket = bluetooth_classic_medium_->ConnectToService(
      *last_device_discovered_, kNearbySharingServiceUuid.value(),
      cancellation_flag.get());
  EXPECT_EQ(last_device_discovered_, bluetooth_socket->GetRemoteDevice());

  EXPECT_TRUE(bluetooth_socket->Close().Ok());
}

TEST_F(BluetoothClassicMediumTest,
       TestConnectToService_CancelledByCancellationFlag) {
  StartDiscovery();
  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  StopDiscovery();

  fake_adapter_->AllowConnectionForAddressAndUuidPair(
      kDeviceAddress1, kNearbySharingServiceUuid);

  auto cancellation_flag = std::make_unique<CancellationFlag>();
  cancellation_flag->Cancel();

  EXPECT_FALSE(bluetooth_classic_medium_->ConnectToService(
      *last_device_discovered_, kNearbySharingServiceUuid.value(),
      cancellation_flag.get()));
}

TEST_F(BluetoothClassicMediumTest, TestConnectToService_Failure) {
  StartDiscovery();
  NotifyDeviceAdded(kDeviceAddress1, kDeviceName1);
  NotifyDeviceAdded(kDeviceAddress2, kDeviceName2);
  StopDiscovery();

  // Do not allow "Device 2".
  fake_adapter_->AllowConnectionForAddressAndUuidPair(
      kDeviceAddress1, kNearbySharingServiceUuid);

  EXPECT_FALSE(bluetooth_classic_medium_->ConnectToService(
      *last_device_discovered_, kNearbySharingServiceUuid.value(), nullptr));
}

TEST_F(BluetoothClassicMediumTest, TestListenForService_Success) {
  fake_adapter_->AllowIncomingConnectionForServiceNameAndUuidPair(
      kNearbySharingServiceName, kNearbySharingServiceUuid);

  EXPECT_TRUE(bluetooth_classic_medium_->ListenForService(
      kNearbySharingServiceName, kNearbySharingServiceUuid.value()));
}

TEST_F(BluetoothClassicMediumTest, TestListenForService_Failure) {
  fake_adapter_->AllowIncomingConnectionForServiceNameAndUuidPair(
      "DifferentServiceName", kNearbySharingServiceUuid);
  fake_adapter_->AllowIncomingConnectionForServiceNameAndUuidPair(
      kNearbySharingServiceName, device::BluetoothUUID("DifferentServiceId"));

  EXPECT_FALSE(bluetooth_classic_medium_->ListenForService(
      kNearbySharingServiceName, kNearbySharingServiceUuid.value()));
}

}  // namespace chrome
}  // namespace nearby