| // Copyright 2020 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "hermes/profile.h" | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <base/check.h> | |
| #include <base/functional/callback_helpers.h> | |
| #include <base/logging.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <chromeos/dbus/service_constants.h> | |
| #include <metrics/structured_events.h> | |
| #include "hermes/executor.h" | |
| #include "hermes/hermes_common.h" | |
| #include "hermes/lpa_util.h" | |
| #include "hermes/type_traits.h" | |
| namespace hermes { | |
| namespace { | |
| const char kBasePath[] = "/org/chromium/Hermes/profile/"; | |
| std::optional<profile::State> LpaProfileStateToHermes( | |
| lpa::proto::ProfileState state) { | |
| switch (state) { | |
| case lpa::proto::DISABLED: | |
| return profile::kInactive; | |
| case lpa::proto::ENABLED: | |
| return profile::kActive; | |
| default: | |
| LOG(ERROR) << "Unrecognized lpa ProfileState: " << state; | |
| return std::nullopt; | |
| } | |
| } | |
| std::optional<profile::ProfileClass> LpaProfileClassToHermes( | |
| lpa::proto::ProfileClass cls) { | |
| switch (cls) { | |
| case lpa::proto::TESTING: | |
| return profile::kTesting; | |
| case lpa::proto::PROVISIONING: | |
| return profile::kProvisioning; | |
| case lpa::proto::OPERATIONAL: | |
| return profile::kOperational; | |
| default: | |
| LOG(ERROR) << "Unrecognized lpa ProfileClass: " << cls; | |
| return std::nullopt; | |
| } | |
| } | |
| } // namespace | |
| // static | |
| std::unique_ptr<Profile> Profile::Create( | |
| const lpa::proto::ProfileInfo& profile_info, | |
| const uint32_t physical_slot, | |
| const std::string& eid, | |
| bool is_pending, | |
| base::RepeatingCallback<void(const std::string&)> on_profile_enabled_cb) { | |
| CHECK(profile_info.has_iccid()); | |
| auto path = dbus::ObjectPath(kBasePath + (eid == "" ? "unknown" : eid) + "/" + | |
| profile_info.iccid()); | |
| auto profile = | |
| std::unique_ptr<Profile>(new Profile(std::move(path), physical_slot)); | |
| LOG(INFO) << __func__ << " Slot:" << physical_slot << " " | |
| << GetObjectPathForLog(profile->object_path_); | |
| // Initialize properties. | |
| profile->SetIccid(profile_info.iccid()); | |
| profile->SetServiceProvider(profile_info.service_provider_name()); | |
| if (profile_info.has_profile_owner()) { | |
| profile->SetMccMnc(profile_info.profile_owner().mcc() + | |
| profile_info.profile_owner().mnc()); | |
| } | |
| profile->SetActivationCode(profile_info.activation_code()); | |
| std::optional<profile::State> state; | |
| state = is_pending ? profile::kPending | |
| : LpaProfileStateToHermes(profile_info.profile_state()); | |
| if (!state.has_value()) { | |
| LOG(ERROR) << "Failed to create Profile for iccid " << profile_info.iccid() | |
| << "; invalid ProfileState " << profile_info.profile_state(); | |
| return nullptr; | |
| } | |
| profile->SetState(state.value()); | |
| auto cls = LpaProfileClassToHermes(profile_info.profile_class()); | |
| if (!cls.has_value()) { | |
| LOG(ERROR) << "Failed to create Profile for iccid " << profile_info.iccid() | |
| << "; invalid ProfileClass " << profile_info.profile_class(); | |
| return nullptr; | |
| } | |
| profile->SetProfileClass(cls.value()); | |
| profile->SetName(profile_info.profile_name()); | |
| profile->SetNickname(profile_info.profile_nickname()); | |
| profile->RegisterWithDBusObject(&profile->dbus_object_); | |
| profile->dbus_object_.RegisterAndBlock(); | |
| profile->on_profile_enabled_cb_ = std::move(on_profile_enabled_cb); | |
| LOG(INFO) << "Successfuly created Profile"; | |
| VLOG(2) << profile_info.DebugString(); | |
| return profile; | |
| } | |
| Profile::Profile(dbus::ObjectPath object_path, const uint32_t physical_slot) | |
| : org::chromium::Hermes::ProfileAdaptor(this), | |
| context_(Context::Get()), | |
| object_path_(std::move(object_path)), | |
| dbus_object_(nullptr, context_->bus(), object_path_), | |
| physical_slot_(physical_slot), | |
| weak_factory_(this) {} | |
| void Profile::Enable(std::unique_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| if (context_->dbus_ongoing_) { | |
| context_->executor()->PostDelayedTask( | |
| FROM_HERE, | |
| base::BindOnce(&Profile::Enable, weak_factory_.GetWeakPtr(), | |
| std::move(response)), | |
| kLpaRetryDelay); | |
| return; | |
| } | |
| if (GetState() == profile::kPending) { | |
| response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain, | |
| kErrorPendingProfile, | |
| "Cannot enable a pending Profile object"); | |
| return; | |
| } | |
| context_->dbus_ongoing_ = true; | |
| LOG(INFO) << "Enabling profile: " << GetObjectPathForLog(object_path_); | |
| auto enable_profile = | |
| base::BindOnce(&Profile::EnableProfile, weak_factory_.GetWeakPtr()); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::START, EuiccOp::ENABLE}, | |
| base::BindOnce(&Profile::RunOnSuccess<std::unique_ptr<DBusResponse<>>>, | |
| weak_factory_.GetWeakPtr(), EuiccOp::ENABLE, | |
| std::move(enable_profile), std::move(response))); | |
| } | |
| void Profile::EnableProfile(std::unique_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| context_->lpa()->EnableProfile( | |
| GetIccid(), context_->executor(), | |
| [response{std::shared_ptr<DBusResponse<>>(std::move(response))}, | |
| weak{weak_factory_.GetWeakPtr()}](int error) mutable { | |
| if (!weak) { | |
| return; | |
| } | |
| weak->OnEnabled(error, std::move(response)); | |
| }); | |
| } | |
| void Profile::Disable(std::unique_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| if (context_->dbus_ongoing_) { | |
| context_->executor()->PostDelayedTask( | |
| FROM_HERE, | |
| base::BindOnce(&Profile::Disable, weak_factory_.GetWeakPtr(), | |
| std::move(response)), | |
| kLpaRetryDelay); | |
| return; | |
| } | |
| if (GetState() == profile::kPending) { | |
| response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain, | |
| kErrorPendingProfile, | |
| "Cannot disable a pending Profile object"); | |
| return; | |
| } | |
| context_->dbus_ongoing_ = true; | |
| LOG(INFO) << "Disabling profile: " << GetObjectPathForLog(object_path_); | |
| auto disable_profile = | |
| base::BindOnce(&Profile::DisableProfile, weak_factory_.GetWeakPtr()); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::START, EuiccOp::DISABLE}, | |
| base::BindOnce(&Profile::RunOnSuccess<std::unique_ptr<DBusResponse<>>>, | |
| weak_factory_.GetWeakPtr(), EuiccOp::DISABLE, | |
| std::move(disable_profile), std::move(response))); | |
| } | |
| void Profile::DisableProfile(std::unique_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| context_->lpa()->DisableProfile( | |
| GetIccid(), context_->executor(), | |
| [response{std::shared_ptr<DBusResponse<>>(std::move(response))}, | |
| weak{weak_factory_.GetWeakPtr()}](int error) mutable { | |
| if (!weak) { | |
| return; | |
| } | |
| weak->OnDisabled(error, std::move(response)); | |
| }); | |
| } | |
| void Profile::OnEnabled(int error, std::shared_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| if (error) { | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::END}, | |
| base::BindOnce(&Profile::SendDBusError, weak_factory_.GetWeakPtr(), | |
| EuiccOp::ENABLE, std::move(response), error)); | |
| return; | |
| } | |
| on_profile_enabled_cb_.Run(GetIccid()); | |
| VLOG(2) << "Enabled profile: " << object_path_.value(); | |
| auto send_notifs = | |
| base::BindOnce(&Profile::FinishProfileOpCb, weak_factory_.GetWeakPtr(), | |
| EuiccOp::ENABLE, std::move(response)); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::PENDING_NOTIFICATIONS}, | |
| std::move(send_notifs)); | |
| } | |
| void Profile::OnDisabled(int error, std::shared_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " " << GetObjectPathForLog(object_path_); | |
| if (error) { | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::END, EuiccOp::DISABLE}, | |
| base::BindOnce(&Profile::SendDBusError, weak_factory_.GetWeakPtr(), | |
| EuiccOp::DISABLE, std::move(response), error)); | |
| return; | |
| } | |
| VLOG(2) << "Disabled profile: " << object_path_.value(); | |
| SetState(profile::kInactive); | |
| auto send_notifs = | |
| base::BindOnce(&Profile::FinishProfileOpCb, weak_factory_.GetWeakPtr(), | |
| EuiccOp::DISABLE, std::move(response)); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::PENDING_NOTIFICATIONS, EuiccOp::DISABLE}, | |
| std::move(send_notifs)); | |
| } | |
| void Profile::FinishProfileOpCb(EuiccOp euicc_op, | |
| std::shared_ptr<DBusResponse<>> response, | |
| int err) { | |
| LOG(INFO) << __func__; | |
| if (err) { | |
| LOG(WARNING) << "Could not finish profile op: " << object_path_.value(); | |
| // Notifications are optional by the standard. Since FinishProfileOp failed, | |
| // it means notifications cannot be sent, but our enable/disable succeeded. | |
| // return success on DBus anyway since only notifications cannot be sent. | |
| SendDBusSuccess(euicc_op, response); | |
| return; | |
| } | |
| context_->lpa()->SendNotifications( | |
| context_->executor(), | |
| [this, response{std::move(response)}](int /*error*/) { | |
| VLOG(2) << "FinishProfileOpCb: sent all notifications"; | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::END}, | |
| base::BindOnce( | |
| [](Context* context, std::shared_ptr<DBusResponse<>> response, | |
| int error) { | |
| response->Return(); | |
| context->dbus_ongoing_ = false; | |
| LOG(INFO) | |
| << "FinishProfileOpCb: completed with err = " << error; | |
| }, | |
| context_, response)); | |
| }); | |
| } | |
| void Profile::Rename(std::unique_ptr<DBusResponse<>> response, | |
| const std::string& nickname) { | |
| LOG(INFO) << __func__ << " Nickname: " << nickname << " " | |
| << GetObjectPathForLog(object_path_); | |
| if (context_->dbus_ongoing_) { | |
| context_->executor()->PostDelayedTask( | |
| FROM_HERE, | |
| base::BindOnce(&Profile::Rename, weak_factory_.GetWeakPtr(), | |
| std::move(response), nickname), | |
| kLpaRetryDelay); | |
| return; | |
| } | |
| context_->dbus_ongoing_ = true; | |
| auto set_nickname = | |
| base::BindOnce(&Profile::SetNicknameMethod, weak_factory_.GetWeakPtr(), | |
| std::move(nickname)); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::START, EuiccOp::RENAME}, | |
| base::BindOnce(&Profile::RunOnSuccess<std::unique_ptr<DBusResponse<>>>, | |
| weak_factory_.GetWeakPtr(), EuiccOp::RENAME, | |
| std::move(set_nickname), std::move(response))); | |
| } | |
| void Profile::SetNicknameMethod(std::string nickname, | |
| std::unique_ptr<DBusResponse<>> response) { | |
| LOG(INFO) << __func__ << " Nickname: " << nickname << " " | |
| << GetObjectPathForLog(object_path_); | |
| context_->lpa()->SetProfileNickname( | |
| GetIccid(), nickname, context_->executor(), | |
| [this, nickname, | |
| response{std::shared_ptr<DBusResponse<>>(std::move(response))}]( | |
| int error) mutable { | |
| if (error) { | |
| SendDBusError( | |
| EuiccOp::RENAME, response, error /* lpa_error */, | |
| kSuccess /* modem_error */); // kSuccess indicates that the modem | |
| // did not return an error but the | |
| // LPA did. | |
| return; | |
| } | |
| this->SetNickname(nickname); | |
| context_->modem_control()->RestoreActiveSlot( | |
| base::BindOnce(&Profile::OnRestoreActiveSlot, | |
| weak_factory_.GetWeakPtr(), std::move(response))); | |
| }); | |
| } | |
| void Profile::OnRestoreActiveSlot(std::shared_ptr<DBusResponse<>> response, | |
| int error) { | |
| if (error) { | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::END}, | |
| base::BindOnce(&Profile::SendDBusError, weak_factory_.GetWeakPtr(), | |
| EuiccOp::RENAME, std::move(response), error)); | |
| return; | |
| } | |
| auto return_dbus_success = base::BindOnce( | |
| &Profile::SendDBusSuccess, weak_factory_.GetWeakPtr(), EuiccOp::RENAME); | |
| context_->modem_control()->ProcessEuiccEvent( | |
| {physical_slot_, EuiccStep::END}, | |
| base::BindOnce(&Profile::RunOnSuccess<std::shared_ptr<DBusResponse<>>>, | |
| weak_factory_.GetWeakPtr(), EuiccOp::RENAME, | |
| std::move(return_dbus_success), std::move(response))); | |
| } | |
| void Profile::SendDBusError(EuiccOp euicc_op, | |
| std::shared_ptr<Profile::DBusResponse<>> response, | |
| int lpa_error, | |
| int modem_error) { | |
| if (modem_error != kSuccess) { | |
| LOG(ERROR) << "Modem finished with error code: " << modem_error; | |
| } | |
| ::metrics::structured::events::cellular::HermesOp() | |
| .SetOperation(to_underlying(euicc_op)) | |
| .SetResult(lpa_error) | |
| .Sethome_mccmnc(GetMCCMNCAsInt()) | |
| .Record(); | |
| auto decoded_error = LpaErrorToBrillo(FROM_HERE, lpa_error); | |
| LOG(ERROR) << euicc_op << " failed: " << decoded_error; | |
| response->ReplyWithError(decoded_error.get()); | |
| context_->dbus_ongoing_ = false; | |
| } | |
| void Profile::SendDBusSuccess( | |
| EuiccOp euicc_op, std::shared_ptr<Profile::DBusResponse<>> response) { | |
| ::metrics::structured::events::cellular::HermesOp() | |
| .SetOperation(to_underlying(euicc_op)) | |
| .Sethome_mccmnc(GetMCCMNCAsInt()) | |
| .Record(); | |
| response->Return(); | |
| context_->dbus_ongoing_ = false; | |
| } | |
| template <typename T> | |
| void Profile::RunOnSuccess(EuiccOp euicc_op, | |
| base::OnceCallback<void(T)> cb, | |
| T response, | |
| int err) { | |
| if (err) { | |
| LOG(ERROR) << "Received modem error: " << err; | |
| ::metrics::structured::events::cellular::HermesOp() | |
| .SetOperation(to_underlying(euicc_op)) | |
| .SetResult(err) | |
| .Sethome_mccmnc(GetMCCMNCAsInt()) | |
| .Record(); | |
| response->ReplyWithError( | |
| FROM_HERE, brillo::errors::dbus::kDomain, GetDBusError(err), | |
| "QMI/MBIM operation failed with code: " + std::to_string(err)); | |
| context_->dbus_ongoing_ = false; | |
| return; | |
| } | |
| std::move(cb).Run(std::move(response)); | |
| } | |
| int Profile::GetMCCMNCAsInt() { | |
| int res; | |
| std::string mccmnc = GetMccMnc(); | |
| base::StringToInt(mccmnc, &res); | |
| return res; | |
| } | |
| Profile::~Profile() { | |
| dbus_object_.UnregisterAndBlock(); | |
| } | |
| } // namespace hermes |