blob: 33807fb252bf0fd9462fcf312e4049b19c26d0bc [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_profile_handler_impl.h"
#include <sstream>
#include <vector>
#include "ash/constants/ash_pref_names.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/values.h"
#include "chromeos/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/network/cellular_utils.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_type_pattern.h"
#include "components/prefs/pref_registry_simple.h"
namespace chromeos {
namespace {
base::flat_set<std::string> GetEuiccPathsFromHermes() {
base::flat_set<std::string> paths;
for (const dbus::ObjectPath& euicc_path :
HermesManagerClient::Get()->GetAvailableEuiccs()) {
paths.insert(euicc_path.value());
}
return paths;
}
bool ContainsProfileWithoutIccid(
const std::vector<CellularESimProfile>& profiles) {
auto iter = std::find_if(profiles.begin(), profiles.end(),
[](const CellularESimProfile& profile) {
return profile.iccid().empty();
});
return iter != profiles.end();
}
} // namespace
// static
void CellularESimProfileHandlerImpl::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
registry->RegisterListPref(prefs::kESimRefreshedEuiccs);
registry->RegisterListPref(prefs::kESimProfiles);
}
CellularESimProfileHandlerImpl::CellularESimProfileHandlerImpl() = default;
CellularESimProfileHandlerImpl::~CellularESimProfileHandlerImpl() {
network_state_handler()->RemoveObserver(this, FROM_HERE);
}
void CellularESimProfileHandlerImpl::DeviceListChanged() {
if (!device_prefs_)
return;
AutoRefreshEuiccsIfNecessary();
}
void CellularESimProfileHandlerImpl::InitInternal() {
network_state_handler()->AddObserver(this, FROM_HERE);
}
std::vector<CellularESimProfile>
CellularESimProfileHandlerImpl::GetESimProfiles() {
// Profiles are stored in prefs.
if (!device_prefs_)
return std::vector<CellularESimProfile>();
const base::ListValue* profiles_list =
device_prefs_->GetList(prefs::kESimProfiles);
if (!profiles_list) {
NET_LOG(ERROR) << "eSIM profiles pref is not a list";
return std::vector<CellularESimProfile>();
}
std::vector<CellularESimProfile> profiles;
for (const base::Value& value : profiles_list->GetList()) {
const base::DictionaryValue* dict;
if (!value.GetAsDictionary(&dict)) {
NET_LOG(ERROR) << "List item from eSIM profiles pref is not a dictionary";
continue;
}
absl::optional<CellularESimProfile> profile =
CellularESimProfile::FromDictionaryValue(*dict);
if (!profile) {
NET_LOG(ERROR) << "Unable to deserialize eSIM profile: " << *dict;
continue;
}
profiles.push_back(*profile);
}
return profiles;
}
bool CellularESimProfileHandlerImpl::HasRefreshedProfilesForEuicc(
const std::string& eid) {
base::flat_set<std::string> euicc_paths =
GetAutoRefreshedEuiccPathsFromPrefs();
for (const auto& path : euicc_paths) {
HermesEuiccClient::Properties* euicc_properties =
HermesEuiccClient::Get()->GetProperties(dbus::ObjectPath(path));
if (!euicc_properties)
continue;
if (eid == euicc_properties->eid().value())
return true;
}
return false;
}
void CellularESimProfileHandlerImpl::SetDevicePrefs(PrefService* device_prefs) {
device_prefs_ = device_prefs;
OnHermesPropertiesUpdated();
}
void CellularESimProfileHandlerImpl::OnHermesPropertiesUpdated() {
if (!device_prefs_)
return;
AutoRefreshEuiccsIfNecessary();
UpdateProfilesFromHermes();
}
void CellularESimProfileHandlerImpl::AutoRefreshEuiccsIfNecessary() {
if (!CellularDeviceExists())
return;
base::flat_set<std::string> euicc_paths_from_hermes =
GetEuiccPathsFromHermes();
base::flat_set<std::string> auto_refreshed_euicc_paths =
GetAutoRefreshedEuiccPaths();
base::flat_set<std::string> paths_needing_auto_refresh;
for (const auto& euicc_path : euicc_paths_from_hermes) {
if (!base::Contains(auto_refreshed_euicc_paths, euicc_path))
paths_needing_auto_refresh.insert(euicc_path);
}
// We only need to request profiles if we see a new EUICC from Hermes that we
// have not yet seen before. If no such EUICCs exist, return early.
if (paths_needing_auto_refresh.empty())
return;
StartAutoRefresh(paths_needing_auto_refresh);
}
void CellularESimProfileHandlerImpl::StartAutoRefresh(
const base::flat_set<std::string>& euicc_paths) {
paths_pending_auto_refresh_.insert(euicc_paths.begin(), euicc_paths.end());
// If there is more than one EUICC, log an error. This configuration is not
// officially supported, so this log may be helpful in feedback reports.
if (euicc_paths.size() > 1u)
NET_LOG(ERROR) << "Attempting to refresh profiles from multiple EUICCs";
// Refresh profiles from the unknown EUICCs. Note that this will internally
// start an inhibit operation, temporarily blocking the user from changing
// cellular settings. This operation is only expected to occur when the device
// originally boots or after a powerwash.
for (const auto& path : euicc_paths) {
NET_LOG(EVENT) << "Found new EUICC whose profiles have not yet been "
<< "refreshsed. Refreshing profile list for " << path;
RefreshProfileList(
dbus::ObjectPath(path),
base::BindOnce(
&CellularESimProfileHandlerImpl::OnAutoRefreshEuiccComplete,
weak_ptr_factory_.GetWeakPtr(), path));
}
}
base::flat_set<std::string>
CellularESimProfileHandlerImpl::GetAutoRefreshedEuiccPaths() const {
// Add all paths stored in prefs.
base::flat_set<std::string> euicc_paths =
GetAutoRefreshedEuiccPathsFromPrefs();
// Add paths which are currently pending a refresh.
euicc_paths.insert(paths_pending_auto_refresh_.begin(),
paths_pending_auto_refresh_.end());
return euicc_paths;
}
base::flat_set<std::string>
CellularESimProfileHandlerImpl::GetAutoRefreshedEuiccPathsFromPrefs() const {
DCHECK(device_prefs_);
const base::ListValue* euicc_paths_from_prefs =
device_prefs_->GetList(prefs::kESimRefreshedEuiccs);
if (!euicc_paths_from_prefs) {
NET_LOG(ERROR) << "Could not fetch refreshed EUICCs pref.";
return {};
}
base::flat_set<std::string> euicc_paths;
for (const auto& euicc : euicc_paths_from_prefs->GetList()) {
if (!euicc.is_string()) {
NET_LOG(ERROR) << "Non-string EUICC path: " << euicc;
continue;
}
euicc_paths.insert(euicc.GetString());
}
return euicc_paths;
}
void CellularESimProfileHandlerImpl::OnAutoRefreshEuiccComplete(
const std::string& path,
std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
paths_pending_auto_refresh_.erase(path);
if (!inhibit_lock) {
NET_LOG(ERROR) << "Auto-refresh failed due to inhibit error. Path: "
<< path;
return;
}
NET_LOG(EVENT) << "Finished auto-refresh for path: " << path;
AddNewlyRefreshedEuiccPathToPrefs(path);
}
void CellularESimProfileHandlerImpl::AddNewlyRefreshedEuiccPathToPrefs(
const std::string& new_path) {
DCHECK(device_prefs_);
base::flat_set<std::string> auto_refreshed_euicc_paths =
GetAutoRefreshedEuiccPathsFromPrefs();
// Keep all paths which were already in prefs.
base::Value euicc_paths(base::Value::Type::LIST);
for (const auto& path : auto_refreshed_euicc_paths)
euicc_paths.Append(path);
// Add new path.
euicc_paths.Append(new_path);
device_prefs_->Set(prefs::kESimRefreshedEuiccs, std::move(euicc_paths));
}
void CellularESimProfileHandlerImpl::UpdateProfilesFromHermes() {
DCHECK(device_prefs_);
std::vector<CellularESimProfile> profiles_from_hermes =
GenerateProfilesFromHermes();
// Skip updating if there are profiles that haven't received ICCID updates
// yet. This is required because property updates to eSIM profile objects
// occur after the profile list has been updated. This state is temporary.
// This method will be triggered again when ICCID properties are updated.
if (ContainsProfileWithoutIccid(profiles_from_hermes)) {
return;
}
// When the device starts up, Hermes is expected to return an empty list of
// profiles if the profiles have not yet been requested. If this occurs,
// return early and rely on the profiles stored in prefs instead.
if (profiles_from_hermes.empty() &&
!has_completed_successful_profile_refresh()) {
return;
}
std::vector<CellularESimProfile> profiles_before_fetch = GetESimProfiles();
// If nothing has changed since the last update, do not update prefs or notify
// observers of a change.
if (profiles_from_hermes == profiles_before_fetch)
return;
std::stringstream ss;
ss << "New set of eSIM profiles have been fetched from Hermes: ";
// Store the updated list of profiles in prefs.
base::Value list(base::Value::Type::LIST);
for (const auto& profile : profiles_from_hermes) {
list.Append(profile.ToDictionaryValue());
ss << "{iccid: " << profile.iccid() << ", eid: " << profile.eid() << "}, ";
}
device_prefs_->Set(prefs::kESimProfiles, std::move(list));
if (profiles_from_hermes.empty())
ss << "<empty>";
NET_LOG(EVENT) << ss.str();
network_state_handler()->SyncStubCellularNetworks();
NotifyESimProfileListUpdated();
}
bool CellularESimProfileHandlerImpl::CellularDeviceExists() const {
return network_state_handler()->GetDeviceStateByType(
NetworkTypePattern::Cellular()) != nullptr;
}
void CellularESimProfileHandlerImpl::ResetESimProfileCache() {
DCHECK(device_prefs_);
device_prefs_->Set(prefs::kESimProfiles,
base::Value(base::Value::Type::LIST));
device_prefs_->Set(prefs::kESimRefreshedEuiccs,
base::Value(base::Value::Type::LIST));
NET_LOG(EVENT) << "Resetting eSIM profile cache";
OnHermesPropertiesUpdated();
}
} // namespace chromeos