chromium/chromeos/ash/services/wifi_direct/wifi_direct_manager_unittest.cc

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

#include "chromeos/ash/services/wifi_direct/wifi_direct_manager.h"

#include "ash/constants/ash_features.h"
#include "base/sync_socket.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/patchpanel/fake_patchpanel_client.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/wifi_p2p/wifi_p2p_controller.h"
#include "chromeos/ash/components/wifi_p2p/wifi_p2p_metrics_logger.h"
#include "chromeos/ash/services/wifi_direct/public/mojom/wifi_direct_manager.mojom-test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::wifi_direct {

using mojom::WifiCredentialsPtr;
using mojom::WifiDirectOperationResult;
using mojom::WifiP2PCapabilitiesPtr;

namespace {

constexpr char kIpv4Address[] = "100.0.0.1";
constexpr char kDefaultSSID[] = "DIRECT-A0";
constexpr char kDefaultPassphrase[] = "direct-passphrase";
constexpr char kAssignedSSID[] = "DIRECT-1a";
constexpr char kAssignedPassphrase[] = "test_passphrase";
const int kTestShillId = 0;
const base::TimeDelta kDurationTime = base::Seconds(123);
constexpr char kWifiP2PConnectionDurationHistogram[] =
    "Network.Ash.WiFiDirect.Connection.Duration";
constexpr char kGroupOwnerDisconnectReasonHistogram[] =
    "Network.Ash.WiFiDirect.GroupOwner.DisconnectReason";
constexpr char kGroupClientDisconnectReasonHistogram[] =
    "Network.Ash.WiFiDirect.GroupClient.DisconnectReason";

}  // namespace

class WifiDirectManagerTest : public testing::Test {
 public:
  struct WifiP2POperationTestResult {
    WifiDirectOperationResult result;
    mojo::PendingRemote<mojom::WifiDirectConnection> wifi_direct_connection;
  };

  WifiDirectManagerTest() = default;
  WifiDirectManagerTest(const WifiDirectManagerTest&) = delete;
  WifiDirectManagerTest& operator=(const WifiDirectManagerTest&) = delete;
  ~WifiDirectManagerTest() override = default;

  void SetUp() override {
    shill_clients::InitializeFakes();
    PatchPanelClient::InitializeFake();
    feature_list_.InitAndEnableFeature(features::kWifiDirect);
    WifiP2PController::Initialize();
    wifi_direct_manager_ = std::make_unique<WifiDirectManager>();
  }

  void TearDown() override {
    wifi_direct_manager_.reset();
    WifiP2PController::Shutdown();
    PatchPanelClient::Shutdown();
    shill_clients::Shutdown();
  }

  WifiP2POperationTestResult CreateWifiDirectGroup(
      WifiCredentialsPtr credentials) {
    auto wifi_direct_manager_async_waiter =
        mojom::WifiDirectManagerAsyncWaiter(wifi_direct_manager_.get());
    WifiP2POperationTestResult test_result;
    wifi_direct_manager_async_waiter.CreateWifiDirectGroup(
        std::move(credentials), &test_result.result,
        &test_result.wifi_direct_connection);
    return test_result;
  }

  WifiP2POperationTestResult ConnectToWifiDirectGroup(
      WifiCredentialsPtr credentials,
      std::optional<uint32_t> frequency) {
    auto wifi_direct_manager_async_waiter =
        mojom::WifiDirectManagerAsyncWaiter(wifi_direct_manager_.get());
    WifiP2POperationTestResult test_result;
    wifi_direct_manager_async_waiter.ConnectToWifiDirectGroup(
        std::move(credentials), frequency, &test_result.result,
        &test_result.wifi_direct_connection);
    return test_result;
  }

  WifiP2PCapabilitiesPtr GetWifiP2PCapabilities() {
    auto wifi_direct_manager_async_waiter =
        mojom::WifiDirectManagerAsyncWaiter(wifi_direct_manager_.get());
    WifiP2PCapabilitiesPtr result;
    wifi_direct_manager_async_waiter.GetWifiP2PCapabilities(&result);
    return result;
  }

  mojom::WifiDirectConnectionPropertiesPtr GetProperties(
      const mojo::Remote<mojom::WifiDirectConnection>& wifi_direct_connection) {
    mojom::WifiDirectConnectionProperties properties;
    auto wifi_direct_connection_async_waiter =
        mojom::WifiDirectConnectionAsyncWaiter(wifi_direct_connection.get());
    return wifi_direct_connection_async_waiter.GetProperties();
  }

  bool AssociateSocket(
      const mojo::Remote<mojom::WifiDirectConnection>& wifi_direct_connection) {
    auto wifi_direct_connection_async_waiter =
        mojom::WifiDirectConnectionAsyncWaiter(wifi_direct_connection.get());
    bool success;
    base::SyncSocket socket1, socket2;
    base::SyncSocket::CreatePair(&socket1, &socket2);
    wifi_direct_connection_async_waiter.AssociateSocket(
        mojo::PlatformHandle(socket1.Take()), &success);
    return success;
  }

  void ExpectConnectionsCount(size_t expected_connections_count) {
    wifi_direct_manager_->FlushForTesting();
    EXPECT_EQ(expected_connections_count,
              wifi_direct_manager_->GetConnectionsCountForTesting());
  }

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<WifiDirectManager> wifi_direct_manager_;
  base::HistogramTester histogram_tester_;
};

TEST_F(WifiDirectManagerTest, CreateWifiDirectGroupWithCredentials_Success) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                        shill::kCreateP2PGroupResultSuccess);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = kAssignedSSID;
  credentials->passphrase = kAssignedPassphrase;
  WifiP2POperationTestResult result_arguments =
      CreateWifiDirectGroup(std::move(credentials));
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.wifi_direct_connection.is_valid());

  mojo::Remote<mojom::WifiDirectConnection> wifi_direct_connection(
      std::move(result_arguments.wifi_direct_connection));
  ExpectConnectionsCount(1);
  auto properties = GetProperties(wifi_direct_connection);
  EXPECT_EQ(1000u, properties->frequency);
  EXPECT_EQ(kIpv4Address, properties->ipv4_address);
  EXPECT_EQ(kAssignedSSID, properties->credentials->ssid);
  EXPECT_EQ(kAssignedPassphrase, properties->credentials->passphrase);
  EXPECT_TRUE(AssociateSocket(wifi_direct_connection));

  FakePatchPanelClient::Get()->set_tag_socket_success_for_testing(
      /*success=*/false);
  EXPECT_FALSE(AssociateSocket(wifi_direct_connection));

  task_environment_.FastForwardBy(kDurationTime);
  // Request disconnection from client side.
  wifi_direct_connection.reset();
  ExpectConnectionsCount(0);
  EXPECT_EQ(kTestShillId, ShillManagerClient::Get()
                              ->GetTestInterface()
                              ->GetRecentlyDestroyedP2PGroupId());
  histogram_tester_.ExpectTotalCount(kGroupOwnerDisconnectReasonHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      kGroupOwnerDisconnectReasonHistogram,
      WifiP2PMetricsLogger::DisconnectReason::kClientInitiated, 1);

  histogram_tester_.ExpectTotalCount(kWifiP2PConnectionDurationHistogram, 1);
  histogram_tester_.ExpectTimeBucketCount(kWifiP2PConnectionDurationHistogram,
                                          kDurationTime, 1);
}

TEST_F(WifiDirectManagerTest, CreateWifiDirectGroupNoCredentials_Success) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                        shill::kCreateP2PGroupResultSuccess);
  WifiP2POperationTestResult result_arguments = CreateWifiDirectGroup(nullptr);
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.wifi_direct_connection.is_valid());

  mojo::Remote<mojom::WifiDirectConnection> wifi_direct_connection(
      std::move(result_arguments.wifi_direct_connection));
  ExpectConnectionsCount(1);
  auto properties = GetProperties(wifi_direct_connection);
  EXPECT_EQ(1000u, properties->frequency);
  EXPECT_EQ(kIpv4Address, properties->ipv4_address);
  EXPECT_EQ(kDefaultSSID, properties->credentials->ssid);
  EXPECT_EQ(kDefaultPassphrase, properties->credentials->passphrase);
  EXPECT_TRUE(AssociateSocket(wifi_direct_connection));

  task_environment_.FastForwardBy(kDurationTime);
  // Request disconnection from client side.
  wifi_direct_connection.reset();
  ExpectConnectionsCount(0);
  histogram_tester_.ExpectTotalCount(kGroupOwnerDisconnectReasonHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      kGroupOwnerDisconnectReasonHistogram,
      WifiP2PMetricsLogger::DisconnectReason::kClientInitiated, 1);
  histogram_tester_.ExpectTotalCount(kWifiP2PConnectionDurationHistogram, 1);
  histogram_tester_.ExpectTimeBucketCount(kWifiP2PConnectionDurationHistogram,
                                          kDurationTime, 1);
}

TEST_F(WifiDirectManagerTest, CreateWifiDirectGroupFailure_InvalidCredentials) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kCreateP2PGroupResultNotSupported);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = "invalid-ssid";
  credentials->passphrase = "test_passphrase";
  WifiP2POperationTestResult result_arguments =
      CreateWifiDirectGroup(std::move(credentials));
  EXPECT_EQ(result_arguments.result,
            WifiDirectOperationResult::kInvalidArguments);
  EXPECT_FALSE(result_arguments.wifi_direct_connection.is_valid());
}

TEST_F(WifiDirectManagerTest, CreateWifiDirectGroupFailure_NotSupported) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kCreateP2PGroupResultNotSupported);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = "DIRECT-1a";
  credentials->passphrase = "test_passphrase";
  WifiP2POperationTestResult result_arguments =
      CreateWifiDirectGroup(std::move(credentials));
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kNotSupported);
  EXPECT_FALSE(result_arguments.wifi_direct_connection.is_valid());
}

TEST_F(WifiDirectManagerTest, ConnectToWifiDirectGroupSuccess) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateConnectToP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kConnectToP2PGroupResultSuccess);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = kAssignedSSID;
  credentials->passphrase = kAssignedPassphrase;
  WifiP2POperationTestResult result_arguments =
      ConnectToWifiDirectGroup(std::move(credentials), 5200u);
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.wifi_direct_connection.is_valid());

  mojo::Remote<mojom::WifiDirectConnection> wifi_direct_connection(
      std::move(result_arguments.wifi_direct_connection));
  ExpectConnectionsCount(1);
  auto properties = GetProperties(wifi_direct_connection);
  EXPECT_EQ(5200u, properties->frequency);
  EXPECT_EQ(kIpv4Address, properties->ipv4_address);
  EXPECT_EQ(kAssignedSSID, properties->credentials->ssid);
  EXPECT_EQ(kAssignedPassphrase, properties->credentials->passphrase);
  EXPECT_TRUE(AssociateSocket(wifi_direct_connection));

  FakePatchPanelClient::Get()->set_tag_socket_success_for_testing(
      /*success=*/false);
  EXPECT_FALSE(AssociateSocket(wifi_direct_connection));

  task_environment_.FastForwardBy(kDurationTime);
  // Request disconnection from client side.
  wifi_direct_connection.reset();
  ExpectConnectionsCount(0);
  EXPECT_EQ(kTestShillId, ShillManagerClient::Get()
                              ->GetTestInterface()
                              ->GetRecentlyDisconnectedP2PGroupId());
  histogram_tester_.ExpectTotalCount(kGroupClientDisconnectReasonHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      kGroupClientDisconnectReasonHistogram,
      WifiP2PMetricsLogger::DisconnectReason::kClientInitiated, 1);
  histogram_tester_.ExpectTotalCount(kWifiP2PConnectionDurationHistogram, 1);
  histogram_tester_.ExpectTimeBucketCount(kWifiP2PConnectionDurationHistogram,
                                          kDurationTime, 1);
}

TEST_F(WifiDirectManagerTest, GroupClientEvents) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateConnectToP2PGroupResult(
          FakeShillSimulatedResult::kSuccess,
          shill::kConnectToP2PGroupResultSuccess);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = kAssignedSSID;
  credentials->passphrase = kAssignedPassphrase;
  WifiP2POperationTestResult result_arguments =
      ConnectToWifiDirectGroup(std::move(credentials), 5200u);
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.wifi_direct_connection.is_valid());

  mojo::Remote<mojom::WifiDirectConnection> wifi_direct_connection(
      std::move(result_arguments.wifi_direct_connection));
  ExpectConnectionsCount(1);

  task_environment_.FastForwardBy(kDurationTime);
  auto p2pclient_dict =
      base::Value::Dict().Set(shill::kP2PClientInfoShillIDProperty, 0);
  p2pclient_dict.Set(shill::kP2PClientInfoStateProperty,
                     shill::kP2PClientInfoStateIdle);

  base::Value::List p2pclient_list;
  p2pclient_list.Append(std::move(p2pclient_dict));

  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PClientInfosProperty, base::Value(p2pclient_list.Clone()));

  ExpectConnectionsCount(0);
  histogram_tester_.ExpectTotalCount(kGroupClientDisconnectReasonHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      kGroupClientDisconnectReasonHistogram,
      WifiP2PMetricsLogger::DisconnectReason::kInternalError, 1);
  histogram_tester_.ExpectTotalCount(kWifiP2PConnectionDurationHistogram, 1);
  histogram_tester_.ExpectTimeBucketCount(kWifiP2PConnectionDurationHistogram,
                                          kDurationTime, 1);
}

TEST_F(WifiDirectManagerTest, ConnectToWifiDirectGroupFailure_InvalidResult) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateConnectToP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                           "invalid_result");
  auto credentials = mojom::WifiCredentials::New();
  WifiP2POperationTestResult result_arguments =
      ConnectToWifiDirectGroup(std::move(credentials), 5200u);
  EXPECT_EQ(result_arguments.result,
            WifiDirectOperationResult::kInvalidResultCode);
  EXPECT_FALSE(result_arguments.wifi_direct_connection.is_valid());
}

TEST_F(WifiDirectManagerTest, GroupOwnerEvents) {
  ShillManagerClient::Get()
      ->GetTestInterface()
      ->SetSimulateCreateP2PGroupResult(FakeShillSimulatedResult::kSuccess,
                                        shill::kCreateP2PGroupResultSuccess);
  auto credentials = mojom::WifiCredentials::New();
  credentials->ssid = kAssignedSSID;
  credentials->passphrase = kAssignedPassphrase;
  WifiP2POperationTestResult result_arguments =
      CreateWifiDirectGroup(std::move(credentials));
  EXPECT_EQ(result_arguments.result, WifiDirectOperationResult::kSuccess);
  ASSERT_TRUE(result_arguments.wifi_direct_connection.is_valid());

  mojo::Remote<mojom::WifiDirectConnection> wifi_direct_connection(
      std::move(result_arguments.wifi_direct_connection));
  ExpectConnectionsCount(1);

  task_environment_.FastForwardBy(kDurationTime);
  auto p2pgroup_dict =
      base::Value::Dict().Set(shill::kP2PGroupInfoShillIDProperty, 0);
  p2pgroup_dict.Set(shill::kP2PGroupInfoStateProperty,
                    shill::kP2PGroupInfoStateIdle);

  base::Value::List p2pgroup_list;
  p2pgroup_list.Append(std::move(p2pgroup_dict));

  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PGroupInfosProperty, base::Value(p2pgroup_list.Clone()));

  ExpectConnectionsCount(0);
  histogram_tester_.ExpectTotalCount(kGroupOwnerDisconnectReasonHistogram, 1);
  histogram_tester_.ExpectBucketCount(
      kGroupOwnerDisconnectReasonHistogram,
      WifiP2PMetricsLogger::DisconnectReason::kInternalError, 1);
  histogram_tester_.ExpectTotalCount(kWifiP2PConnectionDurationHistogram, 1);
  histogram_tester_.ExpectTimeBucketCount(kWifiP2PConnectionDurationHistogram,
                                          kDurationTime, 1);
}

TEST_F(WifiDirectManagerTest, GetWifiP2PCapabilities) {
  auto capabilities_dict =
      base::Value::Dict().Set(shill::kP2PCapabilitiesGroupReadinessProperty,
                              shill::kP2PCapabilitiesGroupReadinessReady);
  capabilities_dict.Set(shill::kP2PCapabilitiesClientReadinessProperty,
                        shill::kP2PCapabilitiesClientReadinessReady);
  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PCapabilitiesProperty, base::Value(capabilities_dict.Clone()));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(GetWifiP2PCapabilities()->is_client_ready);
  EXPECT_TRUE(GetWifiP2PCapabilities()->is_owner_ready);

  capabilities_dict.Set(shill::kP2PCapabilitiesClientReadinessProperty,
                        shill::kP2PCapabilitiesClientReadinessNotReady);
  ShillManagerClient::Get()->GetTestInterface()->SetManagerProperty(
      shill::kP2PCapabilitiesProperty, base::Value(capabilities_dict.Clone()));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(GetWifiP2PCapabilities()->is_client_ready);
  EXPECT_TRUE(GetWifiP2PCapabilities()->is_owner_ready);
}

}  // namespace ash::wifi_direct