chromium/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_browsertest.cc

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

#include <memory>
#include <utility>

#include "base/callback_list.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/login/test/cryptohome_mixin.h"
#include "chrome/browser/ash/policy/affiliation/affiliation_mixin.h"
#include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "chromeos/dbus/missive/missive_client_test_observer.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/reporting/proto/synced/metric_data.pb.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/mock_clock.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

using enterprise_management::DeviceReportingProto;
using enterprise_management::PolicyData;
using testing::Eq;
using testing::SizeIs;
using testing::StrEq;

namespace reporting {
namespace {

constexpr char kWifiDevicePath[] = "/device/wlan";
constexpr char kWifiDeviceName[] = "reporting-wifi0";
constexpr char kAccessPointAddress[] = "00:00:5e:00:53:af";
constexpr bool kEncryptionOn = true;
constexpr bool kPowerManagementOn = true;
constexpr int64_t kTxBitRateMbps = 8;
constexpr int64_t kRxBitRateMbps = 4;
constexpr int64_t kTxPowerDbm = 2;
constexpr int64_t kLinkQuality = 1;
constexpr char kWifiGuid[] = "wifi-guid";
constexpr int kSignalStrength = 80;
constexpr int kSignalStrengthRssi = -50;
constexpr char kIpAddress[] = "192.168.1.240";
constexpr char kGateway[] = "192.168.1.1";
constexpr char kIPConfigPath[] = "ip/config/path";

bool IsNetworkTelemetry(const Record& record) {
  MetricData record_data;
  return record.destination() == Destination::TELEMETRY_METRIC &&
         record_data.ParseFromString(record.data()) &&
         !record_data.telemetry_data()
              .networks_telemetry()
              .network_telemetry()
              .empty();
}

void VerifyNetworkTelemetryData(const MetricData& metric_data) {
  ASSERT_THAT(
      metric_data.telemetry_data().networks_telemetry().network_telemetry(),
      SizeIs(1));
  const auto& network_telemetry =
      metric_data.telemetry_data().networks_telemetry().network_telemetry(0);
  EXPECT_THAT(network_telemetry.guid(), StrEq(kWifiGuid));
  EXPECT_THAT(network_telemetry.type(), Eq(NetworkType::WIFI));
  EXPECT_THAT(network_telemetry.connection_state(),
              Eq(NetworkConnectionState::ONLINE));
  EXPECT_THAT(network_telemetry.ip_address(), StrEq(kIpAddress));
  EXPECT_THAT(network_telemetry.gateway(), StrEq(kGateway));
  EXPECT_THAT(network_telemetry.signal_strength(), Eq(kSignalStrength));
  EXPECT_THAT(network_telemetry.signal_strength_dbm(), Eq(kSignalStrengthRssi));
  EXPECT_THAT(network_telemetry.tx_bit_rate_mbps(), Eq(kTxBitRateMbps));
  EXPECT_THAT(network_telemetry.rx_bit_rate_mbps(), Eq(kRxBitRateMbps));
  EXPECT_THAT(network_telemetry.link_quality(), Eq(kLinkQuality));
  EXPECT_TRUE(network_telemetry.power_management_enabled());
  EXPECT_TRUE(network_telemetry.encryption_on());
}

class DeviceSettingsServiceWaiter
    : public ::ash::DeviceSettingsService::Observer {
 public:
  DeviceSettingsServiceWaiter() {
    CHECK(::ash::DeviceSettingsService::IsInitialized());
    device_settings_observation_.Observe(::ash::DeviceSettingsService::Get());
  }

  DeviceSettingsServiceWaiter(const DeviceSettingsServiceWaiter&) = delete;
  DeviceSettingsServiceWaiter& operator=(const DeviceSettingsServiceWaiter&) =
      delete;

  ~DeviceSettingsServiceWaiter() override = default;

  void Wait() { run_loop.Run(); }

 private:
  // ::ash::DeviceSettingsService::Observer:
  void DeviceSettingsUpdated() override { run_loop.Quit(); }

  base::RunLoop run_loop;
  base::ScopedObservation<::ash::DeviceSettingsService,
                          ::ash::DeviceSettingsService::Observer>
      device_settings_observation_{this};
};

class NetworkTelemetrySamplerBrowserTest
    : public ::policy::DevicePolicyCrosBrowserTest {
 protected:
  NetworkTelemetrySamplerBrowserTest() {
    test::MockClock::Get();
    crypto_home_mixin_.MarkUserAsExisting(affiliation_mixin_.account_id());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ::policy::AffiliationTestHelper::AppendCommandLineSwitchesForLoginManager(
        command_line);
    ::policy::DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    ::policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
    if (::content::IsPreTest()) {
      // Preliminary setup - set up affiliated user.
      ::policy::AffiliationTestHelper::PreLoginUser(
          affiliation_mixin_.account_id());
      return;
    }
    SetupNetworks();
    ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
  }

  void SetupNetworks() {
    network_handler_test_helper_ =
        std::make_unique<::ash::NetworkHandlerTestHelper>();
    network_handler_test_helper_->AddDefaultProfiles();
    network_handler_test_helper_->ResetDevicesAndServices();

    network_handler_test_helper_->device_test()->AddDevice(
        kWifiDevicePath, shill::kTypeWifi, kWifiDeviceName);
    network_handler_test_helper_->device_test()->SetDeviceProperty(
        kWifiDevicePath, shill::kInterfaceProperty,
        base::Value(kWifiDeviceName),
        /*notify_changed=*/true);
    base::RunLoop().RunUntilIdle();

    auto ip_config_properties = base::Value::Dict()
                                    .Set(shill::kAddressProperty, kIpAddress)
                                    .Set(shill::kGatewayProperty, kGateway);
    network_handler_test_helper_->ip_config_test()->AddIPConfig(
        kIPConfigPath, std::move(ip_config_properties));

    std::string service_config = base::StringPrintf(
        R"({"GUID": "%s", "Type": "%s", "State": "%s", "Strength": %d,
        "WiFi.SignalStrengthRssi": %d, "IPConfig": "%s"})",
        kWifiGuid, shill::kTypeWifi, shill::kStateOnline, kSignalStrength,
        kSignalStrengthRssi, kIPConfigPath);
    const std::string service_path =
        network_handler_test_helper_->ConfigureService(service_config);
    ASSERT_FALSE(service_path.empty());
    network_handler_test_helper_->service_test()->SetServiceProperty(
        service_path, shill::kDeviceProperty, base::Value(kWifiDevicePath));
    network_handler_test_helper_->service_test()->SetServiceProperty(
        service_path, shill::kProfileProperty,
        base::Value(network_handler_test_helper_->ProfilePathUser()));

    SetWifiInterfaceData();
  }

  void SetWifiInterfaceData() {
    auto telemetry_info = ::ash::cros_healthd::mojom::TelemetryInfo::New();
    std::vector<::ash::cros_healthd::mojom::NetworkInterfaceInfoPtr>
        network_interfaces;

    auto wireless_link_info = ::ash::cros_healthd::mojom::WirelessLinkInfo::New(
        kAccessPointAddress, kTxBitRateMbps, kRxBitRateMbps, kTxPowerDbm,
        kEncryptionOn, kLinkQuality, kSignalStrengthRssi);
    auto wireless_interface_info =
        ::ash::cros_healthd::mojom::WirelessInterfaceInfo::New(
            kWifiDeviceName, kPowerManagementOn, std::move(wireless_link_info));
    network_interfaces.push_back(
        ::ash::cros_healthd::mojom::NetworkInterfaceInfo::
            NewWirelessInterfaceInfo(std::move(wireless_interface_info)));
    auto network_interface_result =
        ::ash::cros_healthd::mojom::NetworkInterfaceResult::
            NewNetworkInterfaceInfo(std::move(network_interfaces));

    telemetry_info->network_interface_result =
        std::move(network_interface_result);
    ::ash::cros_healthd::FakeCrosHealthd::Get()
        ->SetProbeTelemetryInfoResponseForTesting(telemetry_info);
  }

  void SetReportNetworkStatusPolicy(bool enabled) {
    bool network_status_enabled;
    base::test::TestFuture<void> test_future;
    base::CallbackListSubscription subscription =
        ::ash::CrosSettings::Get()->AddSettingsObserver(
            ::ash::kReportDeviceNetworkStatus,
            test_future.GetRepeatingCallback());
    device_reporting()->set_report_network_status(enabled);
    policy_helper_.RefreshDevicePolicy();

    ASSERT_TRUE(test_future.Wait());
    ASSERT_TRUE(::ash::CrosSettings::Get()->GetBoolean(
        ::ash::kReportDeviceNetworkStatus, &network_status_enabled));
    ASSERT_THAT(network_status_enabled, Eq(enabled));
  }

  void SetReportNetworkTelemetryCollectionRateMs(int64_t rate) {
    int collection_rate;
    base::test::TestFuture<void> test_future;
    base::CallbackListSubscription subscription =
        ::ash::CrosSettings::Get()->AddSettingsObserver(
            ::ash::kReportDeviceNetworkTelemetryCollectionRateMs,
            test_future.GetRepeatingCallback());
    device_reporting()->set_report_network_telemetry_collection_rate_ms(rate);
    policy_helper_.RefreshDevicePolicy();

    ASSERT_TRUE(test_future.Wait());
    ASSERT_TRUE(::ash::CrosSettings::Get()->GetInteger(
        ::ash::kReportDeviceNetworkTelemetryCollectionRateMs,
        &collection_rate));
    ASSERT_THAT(collection_rate, Eq(rate));
  }

  void Deprovision() {
    DeviceSettingsServiceWaiter waiter;
    policy_helper_.device_policy()->policy_data().set_state(
        PolicyData::DEPROVISIONED);
    policy_helper_.RefreshDevicePolicy();
    waiter.Wait();

    ASSERT_THAT(::ash::DeviceSettingsService::Get()->policy_data()->state(),
                Eq(PolicyData::DEPROVISIONED));
  }

  DeviceReportingProto* device_reporting() {
    return policy_helper_.device_policy()->payload().mutable_device_reporting();
  }

  std::unique_ptr<::ash::NetworkHandlerTestHelper> network_handler_test_helper_;
  ::policy::DevicePolicyCrosTestHelper policy_helper_;
  ::policy::AffiliationMixin affiliation_mixin_{&mixin_host_, &policy_helper_};
  ::ash::CryptohomeMixin crypto_home_mixin_{&mixin_host_};
};

IN_PROC_BROWSER_TEST_F(NetworkTelemetrySamplerBrowserTest, PRE_Default) {
  // Simple case that sets up the affiliated user through SetUpOnMainThread
  // PRE-condition.
}

// TODO(crbug.com/40939150): Test is flaky on multiple CrOS builders.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_Default DISABLED_Default
#else
#define MAYBE_Default Default
#endif
IN_PROC_BROWSER_TEST_F(NetworkTelemetrySamplerBrowserTest, MAYBE_Default) {
  ::chromeos::MissiveClientTestObserver missive_observer(
      base::BindRepeating(&IsNetworkTelemetry));

  {
    // Initial collection, policy not set but default is true.
    MetricData record_data;
    test::MockClock::Get().Advance(metrics::kInitialCollectionDelay);
    const auto [priority, record] = missive_observer.GetNextEnqueuedRecord();

    EXPECT_THAT(priority, Eq(Priority::MANUAL_BATCH));
    ASSERT_TRUE(record.has_source_info());
    EXPECT_THAT(record.source_info().source(), Eq(SourceInfo::ASH));
    ASSERT_TRUE(record_data.ParseFromString(record.data()));
    VerifyNetworkTelemetryData(record_data);
    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }

  {
    // Collection rate policy not set, collect on default collection rate.
    MetricData record_data;
    test::MockClock::Get().Advance(
        metrics::kDefaultNetworkTelemetryCollectionRate);
    const auto [priority, record] = missive_observer.GetNextEnqueuedRecord();

    EXPECT_THAT(priority, Eq(Priority::MANUAL_BATCH));
    ASSERT_TRUE(record.has_source_info());
    EXPECT_THAT(record.source_info().source(), Eq(SourceInfo::ASH));
    ASSERT_TRUE(record_data.ParseFromString(record.data()));
    VerifyNetworkTelemetryData(record_data);
    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }

  SetReportNetworkStatusPolicy(false);
  {
    // Reporting is disabled, no network telemetry data should be collected.
    MetricData record_data;
    test::MockClock::Get().Advance(
        metrics::kDefaultNetworkTelemetryCollectionRate);
    base::RunLoop().RunUntilIdle();

    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }

  // Set collection rate policy to double the default rate.
  base::TimeDelta collection_rate =
      metrics::kDefaultNetworkTelemetryCollectionRate * 2;
  SetReportNetworkTelemetryCollectionRateMs(collection_rate.InMilliseconds());

  // Advance time by few hours before re-enabling the policy.
  test::MockClock::Get().Advance(base::Hours(5));
  SetReportNetworkStatusPolicy(true);
  {
    // Policy re-enabled, one immediate collection should take place.
    MetricData record_data;
    const auto [priority, record] = missive_observer.GetNextEnqueuedRecord();

    EXPECT_THAT(priority, Eq(Priority::MANUAL_BATCH));
    ASSERT_TRUE(record.has_source_info());
    EXPECT_THAT(record.source_info().source(), Eq(SourceInfo::ASH));
    ASSERT_TRUE(record_data.ParseFromString(record.data()));
    VerifyNetworkTelemetryData(record_data);
    base::RunLoop().RunUntilIdle();
    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }

  {
    // Collection rate policy set to double the default rate.
    MetricData record_data;
    test::MockClock::Get().Advance(
        metrics::kDefaultNetworkTelemetryCollectionRate);
    base::RunLoop().RunUntilIdle();

    // No data collected, only half of time elapsed.
    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());

    // Advance the remaining time.
    test::MockClock::Get().Advance(
        collection_rate - metrics::kDefaultNetworkTelemetryCollectionRate);
    const auto [priority, record] = missive_observer.GetNextEnqueuedRecord();

    EXPECT_THAT(priority, Eq(Priority::MANUAL_BATCH));
    ASSERT_TRUE(record.has_source_info());
    EXPECT_THAT(record.source_info().source(), Eq(SourceInfo::ASH));
    ASSERT_TRUE(record_data.ParseFromString(record.data()));
    VerifyNetworkTelemetryData(record_data);
    base::RunLoop().RunUntilIdle();
    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }

  Deprovision();
  {
    // Device is deprovisioned, no network telemetry data should be collected.
    MetricData record_data;
    test::MockClock::Get().Advance(collection_rate);
    base::RunLoop().RunUntilIdle();

    ASSERT_FALSE(missive_observer.HasNewEnqueuedRecord());
  }
}

}  // namespace
}  // namespace reporting