chromium/chromeos/ash/components/sync_wifi/synced_network_updater_impl.cc

// Copyright 2019 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/components/sync_wifi/synced_network_updater_impl.h"

#include "base/functional/bind.h"
#include "base/values.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_profile_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/sync_wifi/network_type_conversions.h"
#include "chromeos/ash/components/timer_factory/timer_factory.h"
#include "components/device_event_log/device_event_log.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::network_config {
namespace mojom = ::chromeos::network_config::mojom;
}

namespace ash::sync_wifi {

namespace {

const int kMaxRetries = 3;
constexpr base::TimeDelta kTimeout = base::Minutes(1);

}  // namespace

SyncedNetworkUpdaterImpl::SyncedNetworkUpdaterImpl(
    std::unique_ptr<PendingNetworkConfigurationTracker> tracker,
    network_config::mojom::CrosNetworkConfig* cros_network_config,
    ash::timer_factory::TimerFactory* timer_factory,
    SyncedNetworkMetricsLogger* metrics_logger)
    : tracker_(std::move(tracker)),
      cros_network_config_(cros_network_config),
      timer_factory_(timer_factory),
      metrics_logger_(metrics_logger) {
  cros_network_config_->AddObserver(
      cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());
  // Load the current list of networks.
  OnNetworkStateListChanged();
  std::vector<PendingNetworkConfigurationUpdate> updates =
      tracker_->GetPendingUpdates();
  for (const PendingNetworkConfigurationUpdate& update : updates)
    Retry(update);
}

SyncedNetworkUpdaterImpl::~SyncedNetworkUpdaterImpl() = default;

void SyncedNetworkUpdaterImpl::AddOrUpdateNetwork(
    const sync_pb::WifiConfigurationSpecifics& specifics) {
  auto id = NetworkIdentifier::FromProto(specifics);
  std::string change_guid = tracker_->TrackPendingUpdate(id, specifics);
  StartAddOrUpdateOperation(change_guid, id, specifics);
}

void SyncedNetworkUpdaterImpl::StartAddOrUpdateOperation(
    const std::string& change_guid,
    const NetworkIdentifier& id,
    const sync_pb::WifiConfigurationSpecifics& specifics) {
  network_config::mojom::NetworkStatePropertiesPtr existing_network =
      FindMojoNetwork(id);
  network_config::mojom::ConfigPropertiesPtr config =
      MojoNetworkConfigFromProto(specifics);

  // We can get into this state when the SSID's are improperly encoded hex
  // strings, which causes the proto conversion above to return a nullptr.
  //
  // TODO(b/270151177) : Dig into to understand why we are getting improperly
  // encoded SSIDs.
  if (!config) {
    NET_LOG(ERROR) << "Failed to generate local network config";
    metrics_logger_->RecordApplyGenerateLocalNetworkConfig(/*success=*/false);
    return;
  }

  metrics_logger_->RecordApplyGenerateLocalNetworkConfig(/*success=*/true);
  StartTimer(change_guid, id);

  if (existing_network) {
    NET_LOG(EVENT) << "Updating existing network "
                   << NetworkGuidId(existing_network->guid);
    if (network_guid_to_updates_counter_.contains(existing_network->guid))
      network_guid_to_updates_counter_[existing_network->guid]++;
    else
      network_guid_to_updates_counter_[existing_network->guid] = 1;
    cros_network_config_->SetProperties(
        existing_network->guid, std::move(config),
        base::BindOnce(&SyncedNetworkUpdaterImpl::OnSetPropertiesResult,
                       weak_ptr_factory_.GetWeakPtr(), change_guid,
                       existing_network->guid, specifics));
    return;
  }

  NET_LOG(EVENT) << "Adding new network configuration.";
  cros_network_config_->ConfigureNetwork(
      std::move(config), /*shared=*/false,
      base::BindOnce(&SyncedNetworkUpdaterImpl::OnConfigureNetworkResult,
                     weak_ptr_factory_.GetWeakPtr(), change_guid, specifics));
}

void SyncedNetworkUpdaterImpl::RemoveNetwork(const NetworkIdentifier& id) {
  network_config::mojom::NetworkStatePropertiesPtr network =
      FindMojoNetwork(id);
  if (!network) {
    NET_LOG(EVENT) << "Network not found, nothing to remove.";
    return;
  }

  NET_LOG(EVENT) << "Removing network " << NetworkGuidId(network->guid);
  std::string change_guid =
      tracker_->TrackPendingUpdate(id, /*specifics=*/std::nullopt);
  StartDeleteOperation(change_guid, id, network->guid);
}

void SyncedNetworkUpdaterImpl::StartDeleteOperation(
    const std::string& change_guid,
    const NetworkIdentifier& id,
    std::string guid) {
  StartTimer(change_guid, id);
  cros_network_config_->ForgetNetwork(
      guid, base::BindOnce(&SyncedNetworkUpdaterImpl::OnForgetNetworkResult,
                           weak_ptr_factory_.GetWeakPtr(), change_guid, id));
}

bool SyncedNetworkUpdaterImpl::IsUpdateInProgress(
    const std::string& network_guid) {
  return network_guid_to_updates_counter_.contains(network_guid) &&
         network_guid_to_updates_counter_[network_guid] > 0;
}

network_config::mojom::NetworkStatePropertiesPtr
SyncedNetworkUpdaterImpl::FindMojoNetwork(const NetworkIdentifier& id) {
  for (const network_config::mojom::NetworkStatePropertiesPtr& network :
       networks_) {
    if (id == NetworkIdentifier::FromMojoNetwork(network))
      return network.Clone();
  }
  return nullptr;
}

void SyncedNetworkUpdaterImpl::OnNetworkStateListChanged() {
  cros_network_config_->GetNetworkStateList(
      network_config::mojom::NetworkFilter::New(
          network_config::mojom::FilterType::kConfigured,
          network_config::mojom::NetworkType::kWiFi,
          /*limit=*/0),
      base::BindOnce(&SyncedNetworkUpdaterImpl::OnGetNetworkList,
                     base::Unretained(this)));
}

void SyncedNetworkUpdaterImpl::OnGetNetworkList(
    std::vector<network_config::mojom::NetworkStatePropertiesPtr> networks) {
  networks_ = std::move(networks);
}

void SyncedNetworkUpdaterImpl::OnConfigureNetworkResult(
    const std::string& change_guid,
    const sync_pb::WifiConfigurationSpecifics& proto,
    const std::optional<std::string>& network_guid,
    const std::string& error_message) {
  auto id = NetworkIdentifier::FromProto(proto);
  if (network_guid) {
    NET_LOG(EVENT) << "Successfully configured network "
                   << NetworkGuidId(*network_guid);
    NetworkMetadataStore* metadata_store =
        NetworkHandler::Get()->network_metadata_store();
    metadata_store->SetIsConfiguredBySync(*network_guid);
    metadata_store->SetLastConnectedTimestamp(
        *network_guid, base::Milliseconds(proto.last_connected_timestamp()));
  } else {
    NET_LOG(ERROR) << "Failed to configure network "
                   << NetworkId(NetworkStateFromNetworkIdentifier(id))
                   << " because: " << error_message;
    metrics_logger_->RecordApplyNetworkFailureReason(
        ApplyNetworkFailureReason::kFailedToAdd, error_message);
  }
  HandleShillResult(change_guid, id, network_guid.has_value());
}

void SyncedNetworkUpdaterImpl::OnSetPropertiesResult(
    const std::string& change_guid,
    const std::string& network_guid,
    const sync_pb::WifiConfigurationSpecifics& proto,
    bool is_success,
    const std::string& error_message) {
  if (is_success) {
    NET_LOG(EVENT) << "Successfully updated network  "
                   << NetworkGuidId(network_guid);
    NetworkMetadataStore* metadata_store =
        NetworkHandler::Get()->network_metadata_store();
    metadata_store->SetIsConfiguredBySync(network_guid);
    metadata_store->SetLastConnectedTimestamp(
        network_guid, base::Milliseconds(proto.last_connected_timestamp()));
  } else {
    NET_LOG(ERROR) << "Failed to update network "
                   << NetworkGuidId(network_guid);
    metrics_logger_->RecordApplyNetworkFailureReason(
        ApplyNetworkFailureReason::kFailedToUpdate, error_message);
  }
  if (network_guid_to_updates_counter_.contains(network_guid))
    network_guid_to_updates_counter_[network_guid]--;
  HandleShillResult(change_guid, NetworkIdentifier::FromProto(proto),
                    is_success);
}

void SyncedNetworkUpdaterImpl::OnForgetNetworkResult(
    const std::string& change_guid,
    const NetworkIdentifier& id,
    bool is_success) {
  if (is_success) {
    NET_LOG(EVENT) << "Successfully deleted network for change " << change_guid;
  } else {
    NET_LOG(ERROR) << "Failed to remove network for change " << change_guid;
    metrics_logger_->RecordApplyNetworkFailureReason(
        ApplyNetworkFailureReason::kFailedToRemove, "");
  }

  HandleShillResult(change_guid, id, is_success);
}

void SyncedNetworkUpdaterImpl::HandleShillResult(const std::string& change_guid,
                                                 const NetworkIdentifier& id,
                                                 bool is_success) {
  change_guid_to_timer_map_.erase(change_guid);

  if (!tracker_->GetPendingUpdate(change_guid, id)) {
    NET_LOG(EVENT)
        << "Update to network with change_guid " << change_guid
        << " is no longer pending.  This is likely because the change was"
           " preempted by another update to the same network.";
    return;
  }

  if (is_success) {
    tracker_->MarkComplete(change_guid, id);
    metrics_logger_->RecordApplyNetworkSuccess();
    return;
  }

  tracker_->IncrementCompletedAttempts(change_guid, id);
  std::optional<PendingNetworkConfigurationUpdate> update =
      tracker_->GetPendingUpdate(change_guid, id);

  if (update->completed_attempts() >= kMaxRetries) {
    NET_LOG(ERROR) << "Ran out of retries for change " << change_guid
                   << " to network "
                   << NetworkId(NetworkStateFromNetworkIdentifier(id));
    tracker_->MarkComplete(change_guid, id);
    metrics_logger_->RecordApplyNetworkFailed();
    return;
  }

  Retry(*update);
}

void SyncedNetworkUpdaterImpl::CleanupUpdate(const std::string& change_guid,
                                             const NetworkIdentifier& id) {
  tracker_->MarkComplete(change_guid, id);
}

void SyncedNetworkUpdaterImpl::Retry(
    const PendingNetworkConfigurationUpdate& update) {
  if (update.IsDeleteOperation()) {
    network_config::mojom::NetworkStatePropertiesPtr network =
        FindMojoNetwork(update.id());
    if (!network) {
      tracker_->MarkComplete(update.change_guid(), update.id());
      return;
    }

    StartDeleteOperation(update.change_guid(), update.id(), network->guid);
    return;
  }

  StartAddOrUpdateOperation(update.change_guid(), update.id(),
                            update.specifics().value());
}

void SyncedNetworkUpdaterImpl::StartTimer(const std::string& change_guid,
                                          const NetworkIdentifier& id) {
  change_guid_to_timer_map_[change_guid] = timer_factory_->CreateOneShotTimer();
  change_guid_to_timer_map_[change_guid]->Start(
      FROM_HERE, kTimeout,
      base::BindOnce(&SyncedNetworkUpdaterImpl::OnTimeout,
                     base::Unretained(this), change_guid, id));
}

void SyncedNetworkUpdaterImpl::OnTimeout(const std::string& change_guid,
                                         const NetworkIdentifier& id) {
  NET_LOG(ERROR) << "Failed to update network, operation timed out.";
  metrics_logger_->RecordApplyNetworkFailureReason(
      ApplyNetworkFailureReason::kTimedout, "");
  HandleShillResult(change_guid, id, /*is_success=*/false);
}

}  // namespace ash::sync_wifi