blob: d0909e4671727d9c8864bba047a72806beb34f31 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/callback.h>
#include <base/check.h>
#include <base/logging.h>
#include <brillo/errors/error_codes.h>
#include <chromeos/dbus/service_constants.h>
#include <google-lpa/lpa/core/lpa.h>
#include <google-lpa/lpa/data/proto/euicc_info_1.pb.h>
#include "hermes/euicc.h"
#include "hermes/executor.h"
#include "hermes/hermes_common.h"
#include "hermes/lpa_util.h"
using lpa::proto::ProfileInfo;
namespace hermes {
namespace {
const char kDefaultProdRootSmds[] = "lpa.ds.gsma.com";
const char kDefaultTestRootSmds[] = "testrootsmds.example.com";
void PrintEuiccEventResult(int err) {
if (err) {
LOG(ERROR) << "ProcessEuiccEvent failed with err=" << err;
return;
}
VLOG(2) << "ProcessEuiccEvent succeeded";
}
} // namespace
Euicc::Euicc(uint8_t physical_slot, EuiccSlotInfo slot_info)
: physical_slot_(physical_slot),
slot_info_(std::move(slot_info)),
is_test_mode_(false),
use_test_certs_(false),
context_(Context::Get()),
dbus_adaptor_(context_->adaptor_factory()->CreateEuiccAdaptor(this)),
weak_factory_(this) {
dbus_adaptor_->SetPendingProfiles({});
dbus_adaptor_->SetPhysicalSlot(physical_slot_);
UpdateSlotInfo(slot_info_);
}
void Euicc::UpdateSlotInfo(EuiccSlotInfo slot_info) {
slot_info_ = std::move(slot_info);
dbus_adaptor_->SetEid(slot_info_.eid());
dbus_adaptor_->SetIsActive(slot_info_.IsActive());
}
void Euicc::UpdateLogicalSlot(base::Optional<uint8_t> logical_slot) {
slot_info_.SetLogicalSlot(std::move(logical_slot));
dbus_adaptor_->SetIsActive(slot_info_.IsActive());
}
void Euicc::InstallProfileFromActivationCode(
std::string activation_code,
std::string confirmation_code,
DbusResult<dbus::ObjectPath> dbus_result) {
LOG(INFO) << __func__;
if (!context_->lpa()->IsLpaIdle()) {
// The LPA performs background tasks even after a dbus call is returned.
// During this period(about 2 seconds), we must not perform any operations
// that could disrupt the state of the transmit queue (slot-switching,
// acquiring a new channel etc.).
context_->executor()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Euicc::InstallProfileFromActivationCode,
weak_factory_.GetWeakPtr(), std::move(activation_code),
std::move(confirmation_code), std::move(dbus_result)),
kLpaRetryDelay);
return;
}
auto download_profile =
base::BindOnce(&Euicc::DownloadProfile, weak_factory_.GetWeakPtr(),
std::move(activation_code), std::move(confirmation_code));
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<dbus::ObjectPath>,
weak_factory_.GetWeakPtr(), std::move(download_profile),
std::move(dbus_result)));
}
void Euicc::DownloadProfile(std::string activation_code,
std::string confirmation_code,
DbusResult<dbus::ObjectPath> dbus_result) {
LOG(INFO) << __func__;
auto profile_cb = [dbus_result{std::move(dbus_result)}, this](
lpa::proto::ProfileInfo& info, int error) mutable {
OnProfileInstalled(info, error, std::move(dbus_result));
};
if (activation_code.empty()) {
context_->lpa()->GetDefaultProfileFromSmdp("", context_->executor(),
std::move(profile_cb));
return;
}
lpa::core::Lpa::DownloadOptions options;
options.enable_profile = false;
options.allow_policy_rules = false;
options.confirmation_code = confirmation_code;
context_->lpa()->DownloadProfile(activation_code, std::move(options),
context_->executor(), std::move(profile_cb));
}
void Euicc::InstallPendingProfile(dbus::ObjectPath profile_path,
std::string confirmation_code,
DbusResult<dbus::ObjectPath> dbus_result) {
LOG(INFO) << __func__ << " " << GetObjectPathForLog(profile_path);
if (!context_->lpa()->IsLpaIdle()) {
context_->executor()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Euicc::InstallPendingProfile,
weak_factory_.GetWeakPtr(), std::move(profile_path),
std::move(confirmation_code), std::move(dbus_result)),
kLpaRetryDelay);
return;
}
auto iter = find_if(pending_profiles_.begin(), pending_profiles_.end(),
[&profile_path](const std::unique_ptr<Profile>& profile) {
return profile->object_path() == profile_path;
});
if (iter == pending_profiles_.end()) {
dbus_result.Error(brillo::Error::Create(
FROM_HERE, brillo::errors::dbus::kDomain, kErrorInvalidParameter,
"Could not find Profile " + profile_path.value()));
return;
}
std::string activation_code = iter->get()->GetActivationCode();
InstallProfileFromActivationCode(std::move(activation_code),
std::move(confirmation_code),
std::move(dbus_result));
}
void Euicc::UninstallProfile(dbus::ObjectPath profile_path,
DbusResult<> dbus_result) {
LOG(INFO) << __func__ << " " << GetObjectPathForLog(profile_path);
if (!context_->lpa()->IsLpaIdle()) {
context_->executor()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Euicc::UninstallProfile, weak_factory_.GetWeakPtr(),
std::move(profile_path), std::move(dbus_result)),
kLpaRetryDelay);
return;
}
const Profile* matching_profile = nullptr;
for (auto& profile : installed_profiles_) {
if (profile->object_path() == profile_path) {
matching_profile = profile.get();
break;
}
}
if (!matching_profile) {
dbus_result.Error(brillo::Error::Create(
FROM_HERE, brillo::errors::dbus::kDomain, kErrorInvalidParameter,
"Could not find Profile " + profile_path.value()));
return;
}
auto delete_profile =
base::BindOnce(&Euicc::DeleteProfile, weak_factory_.GetWeakPtr(),
std::move(profile_path), matching_profile->GetIccid());
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<>, weak_factory_.GetWeakPtr(),
std::move(delete_profile), std::move(dbus_result)));
}
void Euicc::DeleteProfile(dbus::ObjectPath profile_path,
std::string iccid,
DbusResult<> dbus_result) {
context_->lpa()->DeleteProfile(std::move(iccid), context_->executor(),
[dbus_result{std::move(dbus_result)},
profile_path, this](int error) mutable {
OnProfileUninstalled(profile_path, error,
std::move(dbus_result));
});
}
void Euicc::UpdateInstalledProfilesProperty() {
std::vector<dbus::ObjectPath> profile_paths;
LOG(INFO) << __func__;
for (auto& profile : installed_profiles_) {
profile_paths.push_back(profile->object_path());
}
dbus_adaptor_->SetInstalledProfiles(profile_paths);
}
void Euicc::UpdatePendingProfilesProperty() {
LOG(INFO) << __func__;
std::vector<dbus::ObjectPath> profile_paths;
for (auto& profile : pending_profiles_) {
profile_paths.push_back(profile->object_path());
}
dbus_adaptor_->SetPendingProfiles(profile_paths);
}
void Euicc::OnProfileInstalled(const lpa::proto::ProfileInfo& profile_info,
int error,
DbusResult<dbus::ObjectPath> dbus_result) {
LOG(INFO) << __func__;
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
auto iter = find_if(pending_profiles_.begin(), pending_profiles_.end(),
[&profile_info](const std::unique_ptr<Profile>& profile) {
return profile->GetIccid() == profile_info.iccid();
});
std::unique_ptr<Profile> profile;
// Call UpdatePendingProfilesProperty() after UpdateInstalledProfilesProperty,
// else Chrome assumes the pending profile was deleted forever.
bool update_pending_profiles_property = false;
if (iter != pending_profiles_.end()) {
// Remove the profile from pending_profiles_ so that it can become an
// installed profile
profile = std::move(*iter);
pending_profiles_.erase(iter);
update_pending_profiles_property = true;
} else {
profile = Profile::Create(profile_info, physical_slot_, slot_info_.eid(),
/*is_pending*/ false,
base::BindRepeating(&Euicc::OnProfileEnabled,
weak_factory_.GetWeakPtr()));
}
if (!profile) {
auto profile_error = brillo::Error::Create(
FROM_HERE, brillo::errors::dbus::kDomain, kErrorInternalLpaFailure,
"Failed to create Profile object");
EndEuiccOp(dbus_result, std::move(profile_error));
return;
}
installed_profiles_.push_back(std::move(profile));
UpdateInstalledProfilesProperty();
if (update_pending_profiles_property) {
UpdatePendingProfilesProperty();
installed_profiles_.back()->SetState(profile::kInactive);
}
// Refresh LPA profile cache
// Send notifications and refresh LPA profile cache. No errors will be raised
// if these operations fail since the profile installation already succeeded.
context_->lpa()->SendNotifications(
context_->executor(),
[this, dbus_result{std::move(dbus_result)},
profile_path{installed_profiles_.back()->object_path()}](int /*err*/) {
// Send notifications has completed, refresh the profile cache.
context_->lpa()->GetInstalledProfiles(
context_->executor(),
[this, dbus_result{std::move(dbus_result)}, profile_path](
std::vector<lpa::proto::ProfileInfo>& profile_infos,
int /*error*/) { EndEuiccOp(dbus_result, profile_path); });
});
}
void Euicc::OnProfileEnabled(const std::string& iccid) {
for (auto& installed_profile : installed_profiles_) {
installed_profile->SetState(installed_profile->GetIccid() == iccid
? profile::kActive
: profile::kInactive);
}
}
void Euicc::OnProfileUninstalled(const dbus::ObjectPath& profile_path,
int error,
DbusResult<> dbus_result) {
LOG(INFO) << __func__;
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
auto iter = installed_profiles_.begin();
for (; iter != installed_profiles_.end(); ++iter) {
if ((*iter)->object_path() == profile_path) {
break;
}
}
CHECK(iter != installed_profiles_.end());
installed_profiles_.erase(iter);
UpdateInstalledProfilesProperty();
SendNotifications(std::move(dbus_result));
}
void Euicc::SendNotifications(
DbusResult<> dbus_result) { // Send notifications and refresh LPA profile
// cache. No errors will be raised
// if these operations fail since the profile operation already succeeded.
context_->lpa()->SendNotifications(
context_->executor(),
[this, dbus_result{std::move(dbus_result)}](int /*err*/) {
context_->lpa()->GetInstalledProfiles(
context_->executor(),
[this, dbus_result{std::move(dbus_result)}](
std::vector<lpa::proto::ProfileInfo>& profile_infos,
int /*error*/) { EndEuiccOp(dbus_result); });
});
}
void Euicc::RequestInstalledProfiles(DbusResult<> dbus_result) {
LOG(INFO) << __func__;
if (!context_->lpa()->IsLpaIdle()) {
context_->executor()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Euicc::RequestInstalledProfiles,
weak_factory_.GetWeakPtr(), std::move(dbus_result)),
kLpaRetryDelay);
return;
}
auto get_installed_profiles =
base::BindOnce(&Euicc::GetInstalledProfiles, weak_factory_.GetWeakPtr());
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<>, weak_factory_.GetWeakPtr(),
std::move(get_installed_profiles),
std::move(dbus_result)));
}
void Euicc::GetInstalledProfiles(DbusResult<> dbus_result) {
context_->lpa()->GetInstalledProfiles(
context_->executor(),
[dbus_result{std::move(dbus_result)}, this](
std::vector<lpa::proto::ProfileInfo>& profile_infos,
int error) mutable {
OnInstalledProfilesReceived(profile_infos, error,
std::move(dbus_result));
});
}
void Euicc::OnInstalledProfilesReceived(
const std::vector<lpa::proto::ProfileInfo>& profile_infos,
int error,
DbusResult<> dbus_result) {
LOG(INFO) << __func__;
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
LOG(ERROR) << "Failed to retrieve installed profiles";
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
installed_profiles_.clear();
for (const auto& info : profile_infos) {
if (!is_test_mode_ &&
info.profile_class() == lpa::proto::ProfileClass::TESTING)
continue;
auto profile =
Profile::Create(info, physical_slot_, slot_info_.eid(),
/*is_pending*/ false,
base::BindRepeating(&Euicc::OnProfileEnabled,
weak_factory_.GetWeakPtr()));
if (profile) {
installed_profiles_.push_back(std::move(profile));
}
}
UpdateInstalledProfilesProperty();
EndEuiccOp(dbus_result);
}
void Euicc::RequestPendingProfiles(DbusResult<> dbus_result,
std::string root_smds) {
LOG(INFO) << __func__;
if (!context_->lpa()->IsLpaIdle()) {
context_->executor()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Euicc::RequestPendingProfiles,
weak_factory_.GetWeakPtr(), std::move(dbus_result),
std::move(root_smds)),
kLpaRetryDelay);
return;
}
auto get_pending_profiles_from_smds =
base::BindOnce(&Euicc::GetPendingProfilesFromSmds,
weak_factory_.GetWeakPtr(), std::move(root_smds));
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<>, weak_factory_.GetWeakPtr(),
std::move(get_pending_profiles_from_smds),
std::move(dbus_result)));
}
void Euicc::GetPendingProfilesFromSmds(std::string root_smds,
DbusResult<> dbus_result) {
auto default_smds =
use_test_certs_ ? kDefaultTestRootSmds : kDefaultProdRootSmds;
auto smds = root_smds.empty() ? default_smds : root_smds;
context_->lpa()->GetPendingProfilesFromSmds(
smds, context_->executor(),
[dbus_result{std::move(dbus_result)}, this](
std::vector<lpa::proto::ProfileInfo>& profile_infos,
int error) mutable {
OnPendingProfilesReceived(profile_infos, error, std::move(dbus_result));
});
}
void Euicc::OnPendingProfilesReceived(
const std::vector<lpa::proto::ProfileInfo>& profile_infos,
int error,
DbusResult<> dbus_result) {
LOG(INFO) << __func__;
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
LOG(ERROR) << "Failed to retrieve pending profiles";
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
pending_profiles_.clear();
for (const auto& info : profile_infos) {
auto profile =
Profile::Create(info, physical_slot_, slot_info_.eid(),
/*is_pending*/ true,
base::BindRepeating(&Euicc::OnProfileEnabled,
weak_factory_.GetWeakPtr()));
if (profile) {
pending_profiles_.push_back(std::move(profile));
}
}
UpdatePendingProfilesProperty();
EndEuiccOp(dbus_result);
}
void Euicc::SetTestModeHelper(bool is_test_mode, DbusResult<> dbus_result) {
VLOG(2) << __func__ << " : is_test_mode" << is_test_mode;
is_test_mode_ = is_test_mode;
auto set_test_mode_internal = base::BindOnce(
&Euicc::SetTestMode, weak_factory_.GetWeakPtr(), is_test_mode);
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<>, weak_factory_.GetWeakPtr(),
std::move(set_test_mode_internal),
std::move(dbus_result)));
}
void Euicc::SetTestMode(bool is_test_mode, DbusResult<> dbus_result) {
VLOG(2) << __func__ << " : is_test_mode" << is_test_mode;
context_->lpa()->SetTestMode(
is_test_mode, context_->executor(),
[this, dbus_result{std::move(dbus_result)}](int error) {
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
EndEuiccOp(dbus_result);
});
}
void Euicc::UseTestCerts(bool use_test_certs) {
const std::string kPath("/usr/share/hermes-ca-certificates/");
// TODO(pholla): b/180422014 - all euicc's share the same LPA. Setting a euicc
// to use test certs will make other euiccs use test certs too.
context_->lpa()->SetTlsCertsDir(kPath + (use_test_certs ? "test/" : "prod/"));
use_test_certs_ = use_test_certs;
}
void Euicc::ResetMemoryHelper(DbusResult<> dbus_result, int reset_options) {
VLOG(2) << __func__ << " : reset_options: " << reset_options;
if (reset_options != lpa::data::reset_options::kDeleteOperationalProfiles &&
reset_options !=
lpa::data::reset_options::kDeleteFieldLoadedTestProfiles) {
dbus_result.Error(brillo::Error::Create(
FROM_HERE, brillo::errors::dbus::kDomain, kErrorInvalidParameter,
"Illegal value for reset_options."));
return;
}
auto reset_memory_internal = base::BindOnce(
&Euicc::ResetMemory, weak_factory_.GetWeakPtr(), reset_options);
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<>, weak_factory_.GetWeakPtr(),
std::move(reset_memory_internal), std::move(dbus_result)));
}
void Euicc::ResetMemory(int reset_options, DbusResult<> dbus_result) {
bool reset_uicc = false; // Ignored by the lpa.
context_->lpa()->ResetMemory(
reset_options, reset_uicc, context_->executor(),
[this, dbus_result{std::move(dbus_result)}](int error) {
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
installed_profiles_.clear();
UpdateInstalledProfilesProperty();
SendNotifications(std::move(dbus_result));
});
}
void Euicc::IsTestEuicc(DbusResult<bool> dbus_result) {
LOG(INFO) << __func__;
auto get_euicc_info_1 =
base::BindOnce(&Euicc::GetEuiccInfo1, weak_factory_.GetWeakPtr());
context_->modem_control()->ProcessEuiccEvent(
{physical_slot_, EuiccStep::START},
base::BindOnce(&Euicc::RunOnSuccess<bool>, weak_factory_.GetWeakPtr(),
std::move(get_euicc_info_1), std::move(dbus_result)));
}
void Euicc::GetEuiccInfo1(DbusResult<bool> dbus_result) {
LOG(INFO) << __func__;
context_->lpa()->GetEuiccInfo1(
context_->executor(),
[this, dbus_result{std::move(dbus_result)}](
lpa::proto::EuiccInfo1& euicc_info_1, int error) {
LOG(INFO) << "euicc_info_1:" << euicc_info_1.DebugString();
auto decoded_error = LpaErrorToBrillo(FROM_HERE, error);
if (decoded_error) {
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
constexpr char kTestEuiccInfo1[] =
"665A1433D67C1A2C5DB8B52C967F10A057BA5CB2";
for (const auto& pkid : euicc_info_1.pkid_for_verif()) {
if (pkid == kTestEuiccInfo1) {
EndEuiccOp(dbus_result, true);
return;
}
}
EndEuiccOp(dbus_result, false);
});
}
template <typename... T>
void Euicc::EndEuiccOp(DbusResult<T...> dbus_result, T... object) {
auto send_dbus_response = base::BindOnce(
[](const DbusResult<T...>& dbus_result, const T&... object, int err) {
PrintEuiccEventResult(err);
dbus_result.Success(object...);
},
dbus_result, object...);
context_->modem_control()->ProcessEuiccEvent({physical_slot_, EuiccStep::END},
std::move(send_dbus_response));
}
template <typename... T>
void Euicc::EndEuiccOp(DbusResult<T...> dbus_result, brillo::ErrorPtr error) {
auto send_dbus_response = base::BindOnce(
[](const DbusResult<T...>& dbus_result, brillo::ErrorPtr error, int err) {
PrintEuiccEventResult(err);
dbus_result.Error(error);
},
dbus_result, std::move(error));
context_->modem_control()->ProcessEuiccEvent({physical_slot_, EuiccStep::END},
std::move(send_dbus_response));
}
template <typename... T>
void Euicc::RunOnSuccess(base::OnceCallback<void(DbusResult<T...>)> cb,
DbusResult<T...> dbus_result,
int err) {
if (err) {
LOG(ERROR) << "Received modem error: " << err;
auto decoded_error = brillo::Error::Create(
FROM_HERE, brillo::errors::dbus::kDomain, kErrorUnknown,
"QMI/MBIM operation failed with code: " + std::to_string(err));
EndEuiccOp(dbus_result, std::move(decoded_error));
return;
}
std::move(cb).Run(std::move(dbus_result));
}
} // namespace hermes