blob: 82fe9945825ca4b628ca8af912e853f35d61b04e [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/network/cellular_esim_uninstall_handler.h"
#include "base/containers/flat_set.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/dbus/hermes/hermes_profile_client.h"
#include "chromeos/network/cellular_esim_profile_handler.h"
#include "chromeos/network/cellular_inhibitor.h"
#include "chromeos/network/device_state.h"
#include "chromeos/network/hermes_metrics_util.h"
#include "chromeos/network/network_configuration_handler.h"
#include "chromeos/network/network_connection_handler.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/network/network_state.h"
#include "components/device_event_log/device_event_log.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/cros_system_api/dbus/hermes/dbus-constants.h"
namespace chromeos {
CellularESimUninstallHandler::UninstallRequest::UninstallRequest(
const std::string& iccid,
const absl::optional<dbus::ObjectPath>& esim_profile_path,
const absl::optional<dbus::ObjectPath>& euicc_path,
UninstallRequestCallback callback)
: iccid(iccid),
esim_profile_path(esim_profile_path),
euicc_path(euicc_path),
callback(std::move(callback)) {}
CellularESimUninstallHandler::UninstallRequest::~UninstallRequest() = default;
CellularESimUninstallHandler::CellularESimUninstallHandler() = default;
CellularESimUninstallHandler::~CellularESimUninstallHandler() = default;
void CellularESimUninstallHandler::Init(
CellularInhibitor* cellular_inhibitor,
CellularESimProfileHandler* cellular_esim_profile_handler,
NetworkConfigurationHandler* network_configuration_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkStateHandler* network_state_handler) {
cellular_inhibitor_ = cellular_inhibitor;
cellular_esim_profile_handler_ = cellular_esim_profile_handler;
network_configuration_handler_ = network_configuration_handler;
network_connection_handler_ = network_connection_handler;
network_state_handler_ = network_state_handler;
}
void CellularESimUninstallHandler::UninstallESim(
const std::string& iccid,
const dbus::ObjectPath& esim_profile_path,
const dbus::ObjectPath& euicc_path,
UninstallRequestCallback callback) {
uninstall_requests_.push_back(std::make_unique<UninstallRequest>(
iccid, esim_profile_path, euicc_path, std::move(callback)));
ProcessPendingUninstallRequests();
}
void CellularESimUninstallHandler::ProcessPendingUninstallRequests() {
// No requests to process.
if (uninstall_requests_.empty())
return;
// Another uninstall request is in progress. Do not process a new request
// until the previous one has completed
if (state_ != UninstallState::kIdle)
return;
NET_LOG(EVENT) << "Starting eSIM uninstall. ICCID: "
<< GetIccidForCurrentRequest();
TransitionToUninstallState(UninstallState::kCheckingNetworkState);
CheckNetworkState();
}
void CellularESimUninstallHandler::TransitionToUninstallState(
UninstallState next_state) {
NET_LOG(EVENT) << "CellularESimUninstallHandler state: " << state_ << " => "
<< next_state;
state_ = next_state;
}
void CellularESimUninstallHandler::CompleteCurrentRequest(
UninstallESimResult result) {
DCHECK(state_ != UninstallState::kIdle);
base::UmaHistogramEnumeration(
"Network.Cellular.ESim.UninstallProfile.OperationResult", result);
const bool success = result == UninstallESimResult::kSuccess;
NET_LOG(EVENT) << "Completed uninstall request for ICCID "
<< GetIccidForCurrentRequest() << ". Success = " << success;
std::move(uninstall_requests_.front()->callback).Run(success);
uninstall_requests_.pop_front();
TransitionToUninstallState(UninstallState::kIdle);
ProcessPendingUninstallRequests();
}
const std::string& CellularESimUninstallHandler::GetIccidForCurrentRequest()
const {
return uninstall_requests_.front()->iccid;
}
const NetworkState*
CellularESimUninstallHandler::GetNetworkStateForCurrentRequest() const {
for (auto* const network : GetESimCellularNetworks()) {
if (network->iccid() == GetIccidForCurrentRequest()) {
return network;
}
}
return nullptr;
}
void CellularESimUninstallHandler::CheckNetworkState() {
DCHECK_EQ(state_, UninstallState::kCheckingNetworkState);
const NetworkState* network = GetNetworkStateForCurrentRequest();
if (!network) {
NET_LOG(ERROR) << "Unable to find eSIM network with ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kNetworkNotFound);
return;
}
// If there is no profile path in the request then this is a stale service.
// Skip directly to configuration removal.
if (!uninstall_requests_.front()->esim_profile_path) {
TransitionToUninstallState(UninstallState::kRemovingShillService);
AttemptRemoveShillService();
return;
}
// If the network is connected, disconnect it before we attempt to uninstall
// the associated profile.
if (network->IsConnectedState()) {
TransitionToUninstallState(UninstallState::kDisconnectingNetwork);
AttemptNetworkDisconnect(network);
return;
}
TransitionToUninstallState(UninstallState::kInhibitingShill);
AttemptShillInhibit();
}
void CellularESimUninstallHandler::AttemptNetworkDisconnect(
const NetworkState* network) {
DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
network_connection_handler_->DisconnectNetwork(
network->path(),
base::BindOnce(&CellularESimUninstallHandler::OnDisconnectSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&CellularESimUninstallHandler::OnDisconnectFailure,
weak_ptr_factory_.GetWeakPtr()));
}
void CellularESimUninstallHandler::OnDisconnectSuccess() {
DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
TransitionToUninstallState(UninstallState::kInhibitingShill);
AttemptShillInhibit();
}
void CellularESimUninstallHandler::OnDisconnectFailure(
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
DCHECK_EQ(state_, UninstallState::kDisconnectingNetwork);
NET_LOG(ERROR) << "Failed disconnecting network with ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kDisconnectFailed);
}
void CellularESimUninstallHandler::AttemptShillInhibit() {
DCHECK_EQ(state_, UninstallState::kInhibitingShill);
cellular_inhibitor_->InhibitCellularScanning(
CellularInhibitor::InhibitReason::kRemovingProfile,
base::BindOnce(&CellularESimUninstallHandler::OnShillInhibit,
weak_ptr_factory_.GetWeakPtr()));
}
void CellularESimUninstallHandler::OnShillInhibit(
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
DCHECK_EQ(state_, UninstallState::kInhibitingShill);
if (!inhibit_lock) {
NET_LOG(ERROR) << "Error inhbiting Shill during uninstall for ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kInhibitFailed);
return;
}
// Save lock in the uninstall request so that it will be released when the
// request is popped.
uninstall_requests_.front()->inhibit_lock = std::move(inhibit_lock);
TransitionToUninstallState(UninstallState::kRequestingInstalledProfiles);
AttemptRequestInstalledProfiles();
}
void CellularESimUninstallHandler::AttemptRequestInstalledProfiles() {
DCHECK_EQ(state_, UninstallState::kRequestingInstalledProfiles);
cellular_esim_profile_handler_->RefreshProfileList(
*uninstall_requests_.front()->euicc_path,
base::BindOnce(&CellularESimUninstallHandler::OnRefreshProfileListResult,
weak_ptr_factory_.GetWeakPtr()),
std::move(uninstall_requests_.front()->inhibit_lock));
}
void CellularESimUninstallHandler::OnRefreshProfileListResult(
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
DCHECK_EQ(state_, UninstallState::kRequestingInstalledProfiles);
if (!inhibit_lock) {
NET_LOG(ERROR) << "Error refreshing profile list during uninstall for "
<< "ICCID " << GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kRefreshProfilesFailed);
return;
}
// Save lock back to the uninstall request since we will continue to perform
// additional eSIM operations.
uninstall_requests_.front()->inhibit_lock = std::move(inhibit_lock);
TransitionToUninstallState(UninstallState::kDisablingProfile);
AttemptDisableProfile();
}
void CellularESimUninstallHandler::AttemptDisableProfile() {
DCHECK_EQ(state_, UninstallState::kDisablingProfile);
HermesProfileClient::Get()->DisableCarrierProfile(
*uninstall_requests_.front()->esim_profile_path,
base::BindOnce(&CellularESimUninstallHandler::OnDisableProfile,
weak_ptr_factory_.GetWeakPtr()));
}
void CellularESimUninstallHandler::OnDisableProfile(
HermesResponseStatus status) {
DCHECK_EQ(state_, UninstallState::kDisablingProfile);
hermes_metrics::LogDisableProfileResult(status);
bool success = status == HermesResponseStatus::kSuccess ||
status == HermesResponseStatus::kErrorAlreadyDisabled;
if (!success) {
NET_LOG(ERROR) << "Failed to disable profile for ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kDisableProfileFailed);
return;
}
TransitionToUninstallState(UninstallState::kUninstallingProfile);
AttemptUninstallProfile();
}
void CellularESimUninstallHandler::AttemptUninstallProfile() {
DCHECK_EQ(state_, UninstallState::kUninstallingProfile);
HermesEuiccClient::Get()->UninstallProfile(
*uninstall_requests_.front()->euicc_path,
*uninstall_requests_.front()->esim_profile_path,
base::BindOnce(&CellularESimUninstallHandler::OnUninstallProfile,
weak_ptr_factory_.GetWeakPtr()));
}
void CellularESimUninstallHandler::OnUninstallProfile(
HermesResponseStatus status) {
DCHECK_EQ(state_, UninstallState::kUninstallingProfile);
hermes_metrics::LogUninstallProfileResult(status);
if (status != HermesResponseStatus::kSuccess) {
NET_LOG(ERROR) << "Failed to uninstall profile for ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kUninstallProfileFailed);
return;
}
TransitionToUninstallState(UninstallState::kRemovingShillService);
AttemptRemoveShillService();
}
void CellularESimUninstallHandler::AttemptRemoveShillService() {
DCHECK_EQ(state_, UninstallState::kRemovingShillService);
const NetworkState* network = GetNetworkStateForCurrentRequest();
if (!network) {
NET_LOG(ERROR) << "Unable to find eSIM network with ICCID "
<< GetIccidForCurrentRequest();
CompleteCurrentRequest(UninstallESimResult::kRemoveServiceFailed);
return;
}
// Return success immediately for non-shill eSIM cellular networks since we
// don't know the actual shill service path. This stub non-shill service will
// be removed automatically when the eSIM profile list updates.
if (network->IsNonShillCellularNetwork()) {
CompleteCurrentRequest(UninstallESimResult::kSuccess);
return;
}
network_configuration_handler_->RemoveConfiguration(
network->path(), absl::nullopt,
base::BindOnce(&CellularESimUninstallHandler::OnRemoveServiceSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&CellularESimUninstallHandler::OnRemoveServiceFailure,
weak_ptr_factory_.GetWeakPtr()));
}
void CellularESimUninstallHandler::OnRemoveServiceSuccess() {
DCHECK_EQ(state_, UninstallState::kRemovingShillService);
CompleteCurrentRequest(UninstallESimResult::kSuccess);
}
void CellularESimUninstallHandler::OnRemoveServiceFailure(
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
DCHECK_EQ(state_, UninstallState::kRemovingShillService);
NET_LOG(ERROR) << "Error removing service with ICCID "
<< GetIccidForCurrentRequest() << ". Error: " << error_name;
CompleteCurrentRequest(UninstallESimResult::kRemoveServiceFailed);
}
NetworkStateHandler::NetworkStateList
CellularESimUninstallHandler::GetESimCellularNetworks() const {
NetworkStateHandler::NetworkStateList network_list;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::Cellular(), /*configured_only=*/false,
/*visible_only=*/false, /*limit=*/0, &network_list);
for (auto iter = network_list.begin(); iter != network_list.end();) {
const NetworkState* network_state = *iter;
if (network_state->eid().empty()) {
iter = network_list.erase(iter);
} else {
iter++;
}
}
return network_list;
}
bool CellularESimUninstallHandler::HasQueuedRequest(
const std::string& iccid) const {
const auto iter = std::find_if(
uninstall_requests_.begin(), uninstall_requests_.end(),
[&](const std::unique_ptr<UninstallRequest>& uninstall_request) {
return uninstall_request->iccid == iccid;
});
return iter != uninstall_requests_.end();
}
std::ostream& operator<<(
std::ostream& stream,
const CellularESimUninstallHandler::UninstallState& state) {
switch (state) {
case CellularESimUninstallHandler::UninstallState::kIdle:
stream << "[Idle]";
break;
case CellularESimUninstallHandler::UninstallState::kCheckingNetworkState:
stream << "[Checking network state]";
break;
case CellularESimUninstallHandler::UninstallState::kInhibitingShill:
stream << "[Inhibiting Shill]";
break;
case CellularESimUninstallHandler::UninstallState::
kRequestingInstalledProfiles:
stream << "[Requesting Installed Profiles]";
break;
case CellularESimUninstallHandler::UninstallState::kDisconnectingNetwork:
stream << "[Disconnecting Network]";
break;
case CellularESimUninstallHandler::UninstallState::kDisablingProfile:
stream << "[Disabling Profile]";
break;
case CellularESimUninstallHandler::UninstallState::kUninstallingProfile:
stream << "[Uninstalling Profile]";
break;
case CellularESimUninstallHandler::UninstallState::kRemovingShillService:
stream << "[Removing Shill Service]";
break;
}
return stream;
}
} // namespace chromeos