chromium/ash/system/diagnostics/networking_log_unittest.cc

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

#include "ash/system/diagnostics/networking_log.h"

#include <vector>

#include "ash/system/diagnostics/log_test_helpers.h"
#include "ash/webui/diagnostics_ui/mojom/network_health_provider.mojom.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace diagnostics {
namespace {

constexpr char kNetworkInfoHeader[] = "--- Network Info ---";

mojom::NetworkPtr CreateWiFiNetworkPtr(uint32_t signal_strength,
                                       uint32_t frequency,
                                       const std::string& ssid,
                                       const std::string& bssid,
                                       uint32_t routing_prefix,
                                       const std::string& gateway,
                                       const std::string& ip_address,
                                       std::vector<std::string>&& name_servers,
                                       const std::string& guid,
                                       const std::string& name,
                                       const std::string& mac_address,
                                       mojom::SecurityType security) {
  auto wifi_props = mojom::WiFiStateProperties::New(signal_strength, frequency,
                                                    ssid, bssid, security);
  auto type_props =
      mojom::NetworkTypeProperties::NewWifi(std::move(wifi_props));
  auto ip_config = mojom::IPConfigProperties::New(
      std::move(name_servers), routing_prefix, gateway, ip_address);
  return mojom::Network::New(mojom::NetworkState::kOnline,
                             mojom::NetworkType::kWiFi, std::move(type_props),
                             guid, name, mac_address, std::move(ip_config));
}

mojom::NetworkPtr CreateEthernetNetworkPtr(
    const std::string& guid,
    const std::string& name,
    const std::string& mac_address,
    const mojom::AuthenticationType& authentication) {
  auto ethernet_props = mojom::EthernetStateProperties::New(authentication);
  auto type_props =
      mojom::NetworkTypeProperties::NewEthernet(std::move(ethernet_props));
  return mojom::Network::New(mojom::NetworkState::kOnline,
                             mojom::NetworkType::kEthernet,
                             std::move(type_props), guid, name, mac_address,
                             mojom::IPConfigProperties::New());
}

mojom::NetworkPtr CreateCellularNetworkPtr(
    const std::string& guid,
    const std::string& name,
    const std::string& mac_address,
    const std::string& iccid,
    const std::string& eid,
    const std::string& network_technology,
    bool roaming,
    mojom::RoamingState roaming_state,
    const uint32_t signal_strength,
    bool sim_locked,
    mojom::LockType lock_type) {
  auto cellular_props = mojom::CellularStateProperties::New(
      iccid, eid, network_technology, roaming, roaming_state, signal_strength,
      sim_locked, lock_type);
  auto type_props =
      mojom::NetworkTypeProperties::NewCellular(std::move(cellular_props));
  return mojom::Network::New(mojom::NetworkState::kOnline,
                             mojom::NetworkType::kCellular,
                             std::move(type_props), guid, name, mac_address,
                             mojom::IPConfigProperties::New());
}

// Splits `line` on '-' ignoring the first part which is the date, and verifies
// that the second half of the line equals `expected_message`.
void ExpectCorrectLogLine(const std::string& expected_message,
                          const std::string& line) {
  const std::vector<std::string> event_parts = GetLogLineContents(line);
  EXPECT_EQ(2u, event_parts.size());
  EXPECT_EQ(expected_message, event_parts[1]);
}

}  // namespace

class NetworkingLogTest : public testing::Test {
 public:
  NetworkingLogTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }

  ~NetworkingLogTest() override = default;

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

  base::ScopedTempDir temp_dir_;
};

TEST_F(NetworkingLogTest, DetailedLogContentsWiFi) {
  const uint32_t expected_signal_strength = 99;
  const uint16_t expected_frequency = 5785;
  const std::string expected_ssid = "ssid";
  const std::string expected_bssid = "bssid";
  const std::string expected_subnet_mask = "128.0.0.0";
  const std::string expected_gateway = "192.0.0.1";
  const std::string expected_ip_address = "192.168.1.1";
  const std::string expected_security_type = "WEP";
  const std::string name_server1 = "192.168.1.100";
  const std::string name_server2 = "192.168.1.101";

  std::vector<std::string> expected_name_servers = {name_server1, name_server2};
  const std::string expected_guid = "guid";
  const std::string expected_name = "name";
  const std::string expected_mac_address = "84:C5:A6:30:3F:31";

  mojom::NetworkPtr test_info = CreateWiFiNetworkPtr(
      expected_signal_strength, expected_frequency, expected_ssid,
      expected_bssid, /*routing_prefix=*/1, expected_gateway,
      expected_ip_address, std::move(expected_name_servers), expected_guid,
      expected_name, expected_mac_address, mojom::SecurityType::kWepPsk);

  NetworkingLog log(temp_dir_.GetPath());

  log.UpdateNetworkList({expected_guid}, expected_guid);
  log.UpdateNetworkState(test_info.Clone());
  task_environment_.RunUntilIdle();

  const std::string log_as_string = log.GetNetworkInfo();
  const std::vector<std::string> log_lines = GetLogLines(log_as_string);

  // Expect one title line and 14 content lines.
  EXPECT_EQ(15u, log_lines.size());
  EXPECT_EQ(kNetworkInfoHeader, log_lines[0]);
  EXPECT_EQ("Name: " + expected_name, log_lines[1]);
  EXPECT_EQ("Type: WiFi", log_lines[2]);
  EXPECT_EQ("State: Online", log_lines[3]);
  EXPECT_EQ("Active: True", log_lines[4]);
  EXPECT_EQ("MAC Address: " + expected_mac_address, log_lines[5]);
  EXPECT_EQ(
      "Signal Strength: " + base::NumberToString(expected_signal_strength),
      log_lines[6]);
  EXPECT_EQ("Frequency: " + base::NumberToString(expected_frequency),
            log_lines[7]);
  EXPECT_EQ("SSID: " + expected_ssid, log_lines[8]);
  EXPECT_EQ("BSSID: " + expected_bssid, log_lines[9]);
  EXPECT_EQ("Security: " + expected_security_type, log_lines[10]);
  EXPECT_EQ("Gateway: " + expected_gateway, log_lines[11]);
  EXPECT_EQ("IP Address: " + expected_ip_address, log_lines[12]);
  EXPECT_EQ("Name Servers: " + name_server1 + ", " + name_server2,
            log_lines[13]);
  EXPECT_EQ("Subnet Mask: " + expected_subnet_mask, log_lines[14]);

  // Expect one title and one event for adding the network.
  const std::string events_log = log.GetNetworkEvents();
  const std::vector<std::string> events_lines = GetLogLines(events_log);
  EXPECT_EQ(2u, events_lines.size());
  EXPECT_EQ("--- Network Events ---", events_lines[0]);

  const std::string expected_line =
      "WiFi network [" + expected_mac_address + "] started in state Online";
  ExpectCorrectLogLine(expected_line, events_lines[1]);
}

TEST_F(NetworkingLogTest, DetailedLogContentsEthernet) {
  const std::string expected_guid = "guid";
  const std::string expected_name = "name";
  const std::string expected_mac_address = "84:C5:A6:30:3F:31";
  const std::string expected_authentication = "EAP";

  mojom::NetworkPtr test_info = CreateEthernetNetworkPtr(
      expected_guid, expected_name, expected_mac_address,
      mojom::AuthenticationType::k8021x);

  NetworkingLog log(temp_dir_.GetPath());

  log.UpdateNetworkList({expected_guid}, expected_guid);
  log.UpdateNetworkState(test_info.Clone());
  task_environment_.RunUntilIdle();

  const std::string log_as_string = log.GetNetworkInfo();
  const std::vector<std::string> log_lines = GetLogLines(log_as_string);

  // Expect one title line and 10 content lines.
  EXPECT_EQ(11u, log_lines.size());
  EXPECT_EQ(kNetworkInfoHeader, log_lines[0]);
  EXPECT_EQ("Name: " + expected_name, log_lines[1]);
  EXPECT_EQ("Type: Ethernet", log_lines[2]);
  EXPECT_EQ("State: Online", log_lines[3]);
  EXPECT_EQ("Active: True", log_lines[4]);
  EXPECT_EQ("MAC Address: " + expected_mac_address, log_lines[5]);
  EXPECT_EQ("Authentication: " + expected_authentication, log_lines[6]);

  // Expect one title and one event for adding the network.
  const std::string events_log = log.GetNetworkEvents();
  const std::vector<std::string> events_lines = GetLogLines(events_log);
  EXPECT_EQ(2u, events_lines.size());
  EXPECT_EQ("--- Network Events ---", events_lines[0]);

  const std::string expected_line =
      "Ethernet network [" + expected_mac_address + "] started in state Online";
  ExpectCorrectLogLine(expected_line, events_lines[1]);
}

TEST_F(NetworkingLogTest, DetailedLogContentsCellular) {
  const std::string expected_guid = "guid";
  const std::string expected_name = "name";
  const std::string expected_mac_address = "84:C5:A6:30:3F:31";
  const std::string expected_iccid = "83948080007483825411";
  const std::string expected_eid = "82099038007008862600508229159883";
  const std::string expected_network_technology = "LTE";
  const std::string expected_roaming = "False";
  const std::string expected_roaming_state = "None";
  const uint32_t expected_signal_strength = 89;
  const std::string expected_sim_locked = "True";
  const std::string expected_lock_type = "sim-pin";

  mojom::NetworkPtr test_info = CreateCellularNetworkPtr(
      expected_guid, expected_name, expected_mac_address, expected_iccid,
      expected_eid, expected_network_technology, false,
      mojom::RoamingState::kNone, expected_signal_strength, true,
      mojom::LockType::kSimPin);

  NetworkingLog log(temp_dir_.GetPath());

  log.UpdateNetworkList({expected_guid}, expected_guid);
  log.UpdateNetworkState(test_info.Clone());
  task_environment_.RunUntilIdle();

  const std::string log_as_string = log.GetNetworkInfo();
  const std::vector<std::string> log_lines = GetLogLines(log_as_string);

  // Expect one title line and 17 content lines.
  EXPECT_EQ(18u, log_lines.size());
  EXPECT_EQ(kNetworkInfoHeader, log_lines[0]);
  EXPECT_EQ("Name: " + expected_name, log_lines[1]);
  EXPECT_EQ("Type: Cellular", log_lines[2]);
  EXPECT_EQ("State: Online", log_lines[3]);
  EXPECT_EQ("Active: True", log_lines[4]);
  EXPECT_EQ("MAC Address: " + expected_mac_address, log_lines[5]);
  EXPECT_EQ("ICCID: " + expected_iccid, log_lines[6]);
  EXPECT_EQ("EID: " + expected_eid, log_lines[7]);
  EXPECT_EQ("Technology: " + expected_network_technology, log_lines[8]);
  EXPECT_EQ("Roaming: " + expected_roaming, log_lines[9]);
  EXPECT_EQ("Roaming State: " + expected_roaming_state, log_lines[10]);
  EXPECT_EQ(
      "Signal Strength: " + base::NumberToString(expected_signal_strength),
      log_lines[11]);
  EXPECT_EQ("SIM Locked: " + expected_sim_locked, log_lines[12]);
  EXPECT_EQ("SIM Lock Type: " + expected_lock_type, log_lines[13]);

  // Expect one title and one event for adding the network.
  const std::string events_log = log.GetNetworkEvents();
  const std::vector<std::string> events_lines = GetLogLines(events_log);
  EXPECT_EQ(2u, events_lines.size());
  EXPECT_EQ("--- Network Events ---", events_lines[0]);

  const std::string expected_line =
      "Cellular network [" + expected_mac_address + "] started in state Online";
  ExpectCorrectLogLine(expected_line, events_lines[1]);
}

TEST_F(NetworkingLogTest, NetworkEvents) {
  const std::string expected_guid = "guid";
  const std::string expected_name = "name";
  const std::string expected_mac_address = "84:C5:A6:30:3F:31";

  mojom::NetworkPtr test_info = CreateEthernetNetworkPtr(
      expected_guid, expected_name, expected_mac_address,
      mojom::AuthenticationType::k8021x);

  NetworkingLog log(temp_dir_.GetPath());

  // Add the network.
  log.UpdateNetworkList({expected_guid}, expected_guid);
  log.UpdateNetworkState(test_info.Clone());

  // Change the state of the network from Online to Disabled.
  mojom::NetworkPtr new_state = test_info.Clone();
  new_state->state = mojom::NetworkState::kDisabled;
  log.UpdateNetworkState(std::move(new_state));

  // Remove the network.
  log.UpdateNetworkList({}, "expected_guid");

  // Make sure all the updates are committed.
  task_environment_.RunUntilIdle();

  // Split the log for verification.
  const std::string events_log = log.GetNetworkEvents();
  const std::vector<std::string> events_lines = GetLogLines(events_log);
  EXPECT_EQ(4u, events_lines.size());

  // Verify section header.
  size_t upto_line = 0;
  EXPECT_EQ("--- Network Events ---", events_lines[upto_line++]);

  // Verify add event.
  std::string expected_line =
      "Ethernet network [" + expected_mac_address + "] started in state Online";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify state change event.
  expected_line = "Ethernet network [" + expected_mac_address +
                  "] changed state from Online to Disabled";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify remove event.
  expected_line = "Ethernet network [" + expected_mac_address + "] removed";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);
}

TEST_F(NetworkingLogTest, WiFiNetworkEvents) {
  const uint32_t expected_signal_strength = 99;
  const uint16_t expected_frequency = 5785;
  const std::string expected_ssid = "ssid";
  const std::string expected_bssid = "bssid";
  const std::string expected_bssid_roamed = "bssid_roamed";
  const std::string expected_subnet_mask = "128.0.0.0";
  const std::string expected_gateway = "192.0.0.1";
  const std::string expected_ip_address = "192.168.1.1";
  const std::string expected_security_type = "WEP";
  const std::string name_server1 = "192.168.1.100";
  const std::string name_server2 = "192.168.1.101";

  std::vector<std::string> expected_name_servers = {name_server1, name_server2};
  const std::string expected_guid = "guid";
  const std::string expected_name = "name";
  const std::string expected_mac_address = "84:C5:A6:30:3F:31";

  mojom::NetworkPtr test_info = CreateWiFiNetworkPtr(
      expected_signal_strength, expected_frequency, expected_ssid,
      expected_bssid, /*routing_prefix=*/1, expected_gateway,
      expected_ip_address, std::move(expected_name_servers), expected_guid,
      expected_name, expected_mac_address, mojom::SecurityType::kWepPsk);

  NetworkingLog log(temp_dir_.GetPath());

  // Add the network.
  log.UpdateNetworkList({expected_guid}, expected_guid);
  log.UpdateNetworkState(test_info.Clone());

  // Leave the WiFi network.
  mojom::NetworkPtr new_state = test_info.Clone();
  new_state->state = mojom::NetworkState::kNotConnected;
  new_state->type_properties->get_wifi()->ssid = "";
  log.UpdateNetworkState(std::move(new_state));

  // Rejoin the WiFi network.
  new_state = test_info.Clone();
  new_state->state = mojom::NetworkState::kOnline;
  new_state->type_properties->get_wifi()->ssid = expected_ssid;
  log.UpdateNetworkState(std::move(new_state));

  // Roam to a new access point.
  new_state = test_info.Clone();
  new_state->state = mojom::NetworkState::kOnline;
  new_state->type_properties->get_wifi()->bssid = expected_bssid_roamed;
  log.UpdateNetworkState(std::move(new_state));

  // Remove the network.
  log.UpdateNetworkList({}, "expected_guid");

  // Make sure all the updates are committed.
  task_environment_.RunUntilIdle();

  // Split the log for verification.
  const std::string events_log = log.GetNetworkEvents();
  const std::vector<std::string> events_lines = GetLogLines(events_log);
  EXPECT_EQ(8u, events_lines.size());

  // Verify section header.
  size_t upto_line = 0;
  EXPECT_EQ("--- Network Events ---", events_lines[upto_line++]);

  // Verify add event.
  std::string expected_line =
      "WiFi network [" + expected_mac_address + "] started in state Online";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify network leave event.
  expected_line = "WiFi network [" + expected_mac_address + "] left SSID '" +
                  expected_ssid + "'";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify state change event.
  expected_line = "WiFi network [" + expected_mac_address +
                  "] changed state from Online to Not Connected";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify network join event.
  expected_line = "WiFi network [" + expected_mac_address + "] joined SSID '" +
                  expected_ssid + "' on access point [" + expected_bssid + "]";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify state change event.
  expected_line = "WiFi network [" + expected_mac_address +
                  "] changed state from Not Connected to Online";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify access point roam event.
  expected_line = "WiFi network [" + expected_mac_address + "] on SSID '" +
                  expected_ssid + "' roamed from access point [" +
                  expected_bssid + "] to [" + expected_bssid_roamed + "]";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);

  // Verify remove event.
  expected_line = "WiFi network [" + expected_mac_address + "] removed";
  ExpectCorrectLogLine(expected_line, events_lines[upto_line++]);
}

TEST_F(NetworkingLogTest, NetworkPtrInvalidDoesNotCrash) {
  mojom::NetworkPtr null_network;
  NetworkingLog log(temp_dir_.GetPath());
  const std::vector<std::string> observer_guids;

  EXPECT_NO_FATAL_FAILURE(
      log.UpdateNetworkList(observer_guids, /**active_guid=*/""));
  EXPECT_TRUE(null_network.is_null());
  EXPECT_NO_FATAL_FAILURE(log.UpdateNetworkState(std::move(null_network)));

  // Ensure AsyncLog tasks complete.
  task_environment_.RunUntilIdle();

  // No networks, only header should be logged.
  const std::vector<std::string> logged_network_info_1 =
      ash::diagnostics::GetLogLines(log.GetNetworkInfo());
  EXPECT_EQ(1u, logged_network_info_1.size());
  EXPECT_EQ(kNetworkInfoHeader, logged_network_info_1[0]);

  // LogRemoveNetwork should not crash if NetworkPtr null.
  mojom::NetworkPtr removed_network =
      CreateEthernetNetworkPtr("fake_guid", "eth0", "00:AA:11:BB:22:CC",
                               mojom::AuthenticationType::kNone);
  EXPECT_NO_FATAL_FAILURE(log.UpdateNetworkState(std::move(removed_network)));
  EXPECT_NO_FATAL_FAILURE(
      log.UpdateNetworkList(observer_guids, /**active_guid=*/""));

  // Ensure AsyncLog tasks complete.
  task_environment_.RunUntilIdle();

  // No networks, only header should be logged.
  const std::vector<std::string> logged_network_info_2 =
      ash::diagnostics::GetLogLines(log.GetNetworkInfo());
  EXPECT_EQ(1u, logged_network_info_2.size());
  EXPECT_EQ(kNetworkInfoHeader, logged_network_info_2[0]);
}

}  // namespace diagnostics
}  // namespace ash