chromium/chromeos/ash/components/dbus/hermes/hermes_profile_client.cc

// Copyright 2020 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/dbus/hermes/hermes_profile_client.h"

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/dbus/hermes/constants.h"
#include "chromeos/ash/components/dbus/hermes/fake_hermes_profile_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_response_status.h"
#include "components/device_event_log/device_event_log.h"
#include "dbus/bus.h"
#include "dbus/object_manager.h"
#include "dbus/property.h"
#include "third_party/cros_system_api/dbus/hermes/dbus-constants.h"

namespace dbus {

// dbus::Property specialization to read and write
// hermes::profile::State enum.
template <>
Property<hermes::profile::State>::Property()
    : value_(hermes::profile::State::kPending) {}

template <>
bool Property<hermes::profile::State>::PopValueFromReader(
    MessageReader* reader) {
  int32_t int_value;
  if (!reader->PopVariantOfInt32(&int_value)) {
    NET_LOG(ERROR) << "Unable to pop value for eSIM profile state.";
    return false;
  }
  switch (int_value) {
    case hermes::profile::State::kActive:
    case hermes::profile::State::kInactive:
    case hermes::profile::State::kPending:
      value_ = static_cast<hermes::profile::State>(int_value);
      return true;
  }
  NOTREACHED_IN_MIGRATION()
      << "Received invalid hermes profile state " << int_value;
  return false;
}

template <>
void Property<hermes::profile::State>::AppendSetValueToWriter(
    MessageWriter* writer) {
  writer->AppendVariantOfInt32(set_value_);
}

// dbus::Property specialization to read and write
// hermes::profile::ProfileClass enum.
template <>
Property<hermes::profile::ProfileClass>::Property()
    : value_(hermes::profile::ProfileClass::kOperational) {}

template <>
bool Property<hermes::profile::ProfileClass>::PopValueFromReader(
    MessageReader* reader) {
  int32_t int_value;
  if (!reader->PopVariantOfInt32(&int_value)) {
    NET_LOG(ERROR) << "Unable to pop value for eSIM profile class";
    return false;
  }
  switch (int_value) {
    case hermes::profile::ProfileClass::kTesting:
    case hermes::profile::ProfileClass::kProvisioning:
    case hermes::profile::ProfileClass::kOperational:
      value_ = static_cast<hermes::profile::ProfileClass>(int_value);
      return true;
  }
  NOTREACHED_IN_MIGRATION()
      << "Received invalid hermes profile class " << int_value;
  return false;
}

template <>
void Property<hermes::profile::ProfileClass>::AppendSetValueToWriter(
    MessageWriter* writer) {
  writer->AppendVariantOfInt32(set_value_);
}

}  // namespace dbus

namespace ash {

namespace {
HermesProfileClient* g_instance = nullptr;
}  // namespace

HermesProfileClient::Properties::Properties(
    dbus::ObjectProxy* object_proxy,
    const PropertyChangedCallback& callback)
    : dbus::PropertySet(object_proxy,
                        hermes::kHermesProfileInterface,
                        callback) {
  RegisterProperty(hermes::profile::kIccidProperty, &iccid_);
  RegisterProperty(hermes::profile::kServiceProviderProperty,
                   &service_provider_);
  RegisterProperty(hermes::profile::kMccMncProperty, &mcc_mnc_);
  RegisterProperty(hermes::profile::kActivationCodeProperty, &activation_code_);
  RegisterProperty(hermes::profile::kNameProperty, &name_);
  RegisterProperty(hermes::profile::kNicknameProperty, &nick_name_);
  RegisterProperty(hermes::profile::kStateProperty, &state_);
  RegisterProperty(hermes::profile::kProfileClassProperty, &profile_class_);
}

HermesProfileClient::Properties::~Properties() = default;

class HermesProfileClientImpl : public HermesProfileClient {
 public:
  explicit HermesProfileClientImpl(dbus::Bus* bus) : bus_(bus) {}
  explicit HermesProfileClientImpl(const HermesProfileClient&) = delete;
  HermesProfileClient& operator=(const HermesProfileClient&) = delete;
  ~HermesProfileClientImpl() override = default;

  using Object = std::pair<dbus::ObjectProxy*, std::unique_ptr<Properties>>;
  using ObjectMap = std::map<dbus::ObjectPath, Object>;

  // HermesProfileClient:
  void EnableCarrierProfile(const dbus::ObjectPath& carrier_profile_path,
                            HermesResponseCallback callback) override {
    dbus::MethodCall method_call(hermes::kHermesProfileInterface,
                                 hermes::profile::kEnable);
    dbus::ObjectProxy* object_proxy = GetObject(carrier_profile_path).first;
    object_proxy->CallMethodWithErrorResponse(
        &method_call, hermes_constants::kHermesNetworkOperationTimeoutMs,
        base::BindOnce(&HermesProfileClientImpl::OnHermesStatusResponse,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void DisableCarrierProfile(const dbus::ObjectPath& carrier_profile_path,
                             HermesResponseCallback callback) override {
    dbus::MethodCall method_call(hermes::kHermesProfileInterface,
                                 hermes::profile::kDisable);
    dbus::ObjectProxy* object_proxy = GetObject(carrier_profile_path).first;
    object_proxy->CallMethodWithErrorResponse(
        &method_call, hermes_constants::kHermesNetworkOperationTimeoutMs,
        base::BindOnce(&HermesProfileClientImpl::OnHermesStatusResponse,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void RenameProfile(const dbus::ObjectPath& carrier_profile_path,
                     const std::string& new_name,
                     HermesResponseCallback callback) override {
    dbus::MethodCall method_call(hermes::kHermesProfileInterface,
                                 hermes::profile::kRename);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(new_name);
    dbus::ObjectProxy* object_proxy = GetObject(carrier_profile_path).first;
    object_proxy->CallMethodWithErrorResponse(
        &method_call, hermes_constants::kHermesNetworkOperationTimeoutMs,
        base::BindOnce(&HermesProfileClientImpl::OnHermesStatusResponse,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  Properties* GetProperties(
      const dbus::ObjectPath& carrier_profile_path) override {
    return GetObject(carrier_profile_path).second.get();
  }

  TestInterface* GetTestInterface() override { return nullptr; }

 private:
  Object& GetObject(const dbus::ObjectPath& object_path) {
    ObjectMap::iterator it = object_map_.find(object_path);
    if (it != object_map_.end())
      return it->second;

    dbus::ObjectProxy* object_proxy =
        bus_->GetObjectProxy(hermes::kHermesServiceName, object_path);

    auto properties = std::make_unique<Properties>(
        object_proxy,
        base::BindRepeating(&HermesProfileClientImpl::OnPropertyChanged,
                            weak_ptr_factory_.GetWeakPtr(), object_path));
    properties->ConnectSignals();
    properties->GetAll();

    Object object = std::make_pair(object_proxy, std::move(properties));
    object_map_[object_path] = std::move(object);
    return object_map_[object_path];
  }

  void OnPropertyChanged(const dbus::ObjectPath& object_path,
                         const std::string& property_name) {
    for (auto& observer : observers()) {
      observer.OnCarrierProfilePropertyChanged(object_path, property_name);
    }
  }

  void OnHermesStatusResponse(HermesResponseCallback callback,
                              dbus::Response* response,
                              dbus::ErrorResponse* error_response) {
    if (error_response) {
      NET_LOG(ERROR) << "Hermes Profile operation failed with error: "
                     << error_response->GetErrorName();
      std::move(callback).Run(
          HermesResponseStatusFromErrorName(error_response->GetErrorName()));
      return;
    }
    std::move(callback).Run(HermesResponseStatus::kSuccess);
  }

  raw_ptr<dbus::Bus> bus_;
  ObjectMap object_map_;
  base::WeakPtrFactory<HermesProfileClientImpl> weak_ptr_factory_{this};
};

HermesProfileClient::HermesProfileClient() {
  DCHECK(!g_instance);
  g_instance = this;
}

HermesProfileClient::~HermesProfileClient() {
  DCHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

void HermesProfileClient::AddObserver(Observer* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void HermesProfileClient::RemoveObserver(Observer* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

// static
void HermesProfileClient::Initialize(dbus::Bus* bus) {
  DCHECK(bus);
  DCHECK(!g_instance);
  new HermesProfileClientImpl(bus);
}

// static
void HermesProfileClient::InitializeFake() {
  new FakeHermesProfileClient();
}

// static
void HermesProfileClient::Shutdown() {
  DCHECK(g_instance);
  delete g_instance;
}

// static
HermesProfileClient* HermesProfileClient::Get() {
  return g_instance;
}

}  // namespace ash