chromium/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"

#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>

#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_config.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/metrics.h"
#include "chromeos/dbus/common/dbus_library_error.h"
#include "chromeos/dbus/common/pipe_reader.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h"

namespace ash {

namespace {

const char kCrOSTracingAgentName[] = "cros";
const char kCrOSTraceLabel[] = "systemTraceEvents";

// Because the cheets logs are very huge, we set the D-Bus timeout to 2 minutes.
const int kBigLogsDBusTimeoutMS = 120 * 1000;

// crash_sender could take a while to run if the network connection is slow, so
// wait up to 20 seconds for it.
const int kCrashSenderTimeoutMS = 20 * 1000;

// NOTE: This does not use the typical pattern of a single `g_instance` variable
// due to browser_tests that need to temporarily override the existing instance
// with a specialized subclass.
DebugDaemonClient* g_instance = nullptr;
DebugDaemonClient* g_instance_for_test = nullptr;

// A self-deleting object that wraps the pipe reader operations for reading the
// big feedback logs. It will delete itself once the pipe stream has been
// terminated. Once the data has been completely read from the pipe, it invokes
// the GetLogsCallback |callback| passing the deserialized logs data back to
// the requester.
class PipeReaderWrapper final {
 public:
  explicit PipeReaderWrapper(DebugDaemonClient::GetLogsCallback callback)
      : pipe_reader_(base::ThreadPool::CreateTaskRunner(
            {base::MayBlock(),
             base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
        callback_(std::move(callback)) {}

  PipeReaderWrapper(const PipeReaderWrapper&) = delete;
  PipeReaderWrapper& operator=(const PipeReaderWrapper&) = delete;

  base::ScopedFD Initialize() {
    return pipe_reader_.StartIO(base::BindOnce(&PipeReaderWrapper::OnIOComplete,
                                               weak_ptr_factory_.GetWeakPtr()));
  }

  void OnIOComplete(std::optional<std::string> result) {
    if (!result.has_value()) {
      VLOG(1) << "Failed to read data.";
      RecordGetFeedbackLogsV2DbusResult(
          GetFeedbackLogsV2DbusResult::kErrorReadingData);
      RunCallbackAndDestroy(std::nullopt);
      return;
    }

    JSONStringValueDeserializer json_reader(result.value());
    std::unique_ptr<base::Value> logs(
        json_reader.Deserialize(nullptr, nullptr));
    if (!logs.get() || !logs->is_dict()) {
      VLOG(1) << "Failed to deserialize the JSON logs.";
      RecordGetFeedbackLogsV2DbusResult(
          GetFeedbackLogsV2DbusResult::kErrorDeserializingJSonLogs);
      RunCallbackAndDestroy(std::nullopt);
      return;
    }
    std::map<std::string, std::string> data;
    for (const auto [dict_key, dict_value] : logs->GetDict()) {
      data[dict_key] = dict_value.GetString();
    }
    RunCallbackAndDestroy(std::move(data));
  }

  void TerminateStream() {
    VLOG(1) << "Terminated";
    RunCallbackAndDestroy(std::nullopt);
  }

  base::WeakPtr<PipeReaderWrapper> AsWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  void RunCallbackAndDestroy(
      std::optional<std::map<std::string, std::string>> result) {
    if (result.has_value()) {
      std::move(callback_).Run(true, std::move(result.value()));
    } else {
      std::move(callback_).Run(false, std::map<std::string, std::string>());
    }
    delete this;
  }

  chromeos::PipeReader pipe_reader_;
  DebugDaemonClient::GetLogsCallback callback_;
  base::WeakPtrFactory<PipeReaderWrapper> weak_ptr_factory_{this};
};

// The DebugDaemonClient implementation used in production.
class DebugDaemonClientImpl : public DebugDaemonClient {
 public:
  DebugDaemonClientImpl() : debugdaemon_proxy_(nullptr) {}

  DebugDaemonClientImpl(const DebugDaemonClientImpl&) = delete;
  DebugDaemonClientImpl& operator=(const DebugDaemonClientImpl&) = delete;

  ~DebugDaemonClientImpl() override = default;

  // DebugDaemonClient override.
  void DumpDebugLogs(bool is_compressed,
                     int file_descriptor,
                     chromeos::VoidDBusMethodCallback callback) override {
    // Issue the dbus request to get debug logs.
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kDumpDebugLogs);
    dbus::MessageWriter writer(&method_call);
    writer.AppendBool(is_compressed);
    writer.AppendFileDescriptor(file_descriptor);
    debugdaemon_proxy_->CallMethod(
        &method_call, kBigLogsDBusTimeoutMS,
        base::BindOnce(&DebugDaemonClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetDebugMode(const std::string& subsystem,
                    chromeos::VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSetDebugMode);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(subsystem);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetRoutes(bool numeric,
                 bool ipv6,
                 bool all_tables,
                 chromeos::DBusMethodCallback<std::vector<std::string>>
                     callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface, debugd::kGetRoutes);
    dbus::MessageWriter writer(&method_call);
    dbus::MessageWriter sub_writer(NULL);
    writer.OpenArray("{sv}", &sub_writer);
    dbus::MessageWriter elem_writer(NULL);
    sub_writer.OpenDictEntry(&elem_writer);
    elem_writer.AppendString("numeric");
    elem_writer.AppendVariantOfBool(numeric);
    sub_writer.CloseContainer(&elem_writer);
    sub_writer.OpenDictEntry(&elem_writer);
    elem_writer.AppendString("v6");
    elem_writer.AppendVariantOfBool(ipv6);
    sub_writer.CloseContainer(&elem_writer);
    sub_writer.OpenDictEntry(&elem_writer);
    elem_writer.AppendString("all");
    elem_writer.AppendVariantOfBool(all_tables);
    sub_writer.CloseContainer(&elem_writer);
    writer.CloseContainer(&sub_writer);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnGetRoutes,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetNetworkStatus(
      chromeos::DBusMethodCallback<std::string> callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetNetworkStatus);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStringMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetNetworkInterfaces(
      chromeos::DBusMethodCallback<std::string> callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetInterfaces);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStringMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetPerfOutput(const std::vector<std::string>& quipper_args,
                     bool disable_cpu_idle,
                     int file_descriptor,
                     chromeos::DBusMethodCallback<uint64_t> callback) override {
    DCHECK(file_descriptor);
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetPerfOutputV2);
    dbus::MessageWriter writer(&method_call);
    writer.AppendArrayOfStrings(quipper_args);
    writer.AppendBool(disable_cpu_idle);
    writer.AppendFileDescriptor(file_descriptor);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnUint64Method,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void StopPerf(uint64_t session_id,
                chromeos::VoidDBusMethodCallback callback) override {
    DCHECK(session_id);
    dbus::MethodCall method_call(debugd::kDebugdInterface, debugd::kStopPerf);
    dbus::MessageWriter writer(&method_call);
    writer.AppendUint64(session_id);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetFeedbackLogs(
      const cryptohome::AccountIdentifier& id,
      const std::vector<debugd::FeedbackLogType>& requested_logs,
      GetLogsCallback callback) override {
    // The PipeReaderWrapper is a self-deleting object; we don't have to worry
    // about ownership or lifetime. We need to create a new one for each Big
    // Logs requests in order to queue these requests. One request can take a
    // long time to be processed and a new request should never be ignored nor
    // cancels the on-going one.
    PipeReaderWrapper* pipe_reader = new PipeReaderWrapper(std::move(callback));
    base::ScopedFD pipe_write_end = pipe_reader->Initialize();

    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetFeedbackLogsV3);
    dbus::MessageWriter writer(&method_call);
    writer.AppendFileDescriptor(pipe_write_end.get());
    writer.AppendString(id.account_id());
    // Write |requested_logs|.
    dbus::MessageWriter sub_writer(nullptr);
    writer.OpenArray("i", &sub_writer);
    for (auto log_type : requested_logs) {
      sub_writer.AppendInt32(log_type);
    }
    writer.CloseContainer(&sub_writer);

    DVLOG(1) << "Requesting feedback logs";
    debugdaemon_proxy_->CallMethodWithErrorResponse(
        &method_call, kBigLogsDBusTimeoutMS,
        base::BindOnce(&DebugDaemonClientImpl::OnFeedbackLogsResponse,
                       weak_ptr_factory_.GetWeakPtr(),
                       pipe_reader->AsWeakPtr()));
  }

  void GetFeedbackBinaryLogs(
      const cryptohome::AccountIdentifier& id,
      const std::map<debugd::FeedbackBinaryLogType, base::ScopedFD>&
          log_type_fds,
      chromeos::VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetFeedbackBinaryLogs);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(id.account_id());

    dbus::MessageWriter array_writer(nullptr);
    // Write map of log_type and fd.
    writer.OpenArray("{ih}", &array_writer);
    for (const auto& log_type : log_type_fds) {
      dbus::MessageWriter dict_entry_writer(nullptr);
      array_writer.OpenDictEntry(&dict_entry_writer);
      dict_entry_writer.AppendInt32(log_type.first);
      dict_entry_writer.AppendFileDescriptor(log_type.second.get());
      array_writer.CloseContainer(&dict_entry_writer);
    }
    writer.CloseContainer(&array_writer);

    DVLOG(1) << "Requesting feedback binary logs";
    debugdaemon_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnFeedbackBinaryLogsResponse,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void BackupArcBugReport(const cryptohome::AccountIdentifier& id,
                          chromeos::VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kBackupArcBugReport);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(id.account_id());

    DVLOG(1) << "Backing up ARC bug report";
    debugdaemon_proxy_->CallMethod(
        &method_call, kBigLogsDBusTimeoutMS,
        base::BindOnce(&DebugDaemonClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetAllLogs(GetLogsCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface, debugd::kGetAllLogs);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnGetAllLogs,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetLog(const std::string& log_name,
              chromeos::DBusMethodCallback<std::string> callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface, debugd::kGetLog);
    dbus::MessageWriter(&method_call).AppendString(log_name);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStringMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  // base::trace_event::TracingAgent implementation.
  std::string GetTracingAgentName() override { return kCrOSTracingAgentName; }

  std::string GetTraceEventLabel() override { return kCrOSTraceLabel; }

  void StartAgentTracing(const base::trace_event::TraceConfig& trace_config,
                         StartAgentTracingCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSystraceStart);
    dbus::MessageWriter writer(&method_call);
    if (trace_config.systrace_events().empty()) {
      writer.AppendString("all");  // TODO(sleffler) parameterize category list
    } else {
      std::string events;
      for (const std::string& event : trace_config.systrace_events()) {
        if (!events.empty())
          events += " ";
        events += event;
      }
      writer.AppendString(events);
    }

    DVLOG(1) << "Requesting a systrace start";
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStartMethod,
                       weak_ptr_factory_.GetWeakPtr()));

    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), GetTracingAgentName(),
                                  true /* success */));
  }

  void StopAgentTracing(StopAgentTracingCallback callback) override {
    DCHECK(stop_agent_tracing_task_runner_);
    if (pipe_reader_ != NULL) {
      LOG(ERROR) << "Busy doing StopSystemTracing";
      return;
    }

    pipe_reader_ =
        std::make_unique<chromeos::PipeReader>(stop_agent_tracing_task_runner_);
    callback_ = std::move(callback);
    base::ScopedFD pipe_write_end = pipe_reader_->StartIO(base::BindOnce(
        &DebugDaemonClientImpl::OnIOComplete, weak_ptr_factory_.GetWeakPtr()));

    DCHECK(pipe_write_end.is_valid());
    // Issue the dbus request to stop system tracing
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSystraceStop);
    dbus::MessageWriter writer(&method_call);
    writer.AppendFileDescriptor(pipe_write_end.get());

    DVLOG(1) << "Requesting a systrace stop";
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStopAgentTracing,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  void SetStopAgentTracingTaskRunner(
      scoped_refptr<base::TaskRunner> task_runner) override {
    stop_agent_tracing_task_runner_ = task_runner;
  }

  void TestICMP(const std::string& ip_address,
                TestICMPCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface, debugd::kTestICMP);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(ip_address);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnTestICMP,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void TestICMPWithOptions(const std::string& ip_address,
                           const std::map<std::string, std::string>& options,
                           TestICMPCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kTestICMPWithOptions);
    dbus::MessageWriter writer(&method_call);
    dbus::MessageWriter sub_writer(NULL);
    dbus::MessageWriter elem_writer(NULL);

    // Write the host.
    writer.AppendString(ip_address);

    // Write the options.
    writer.OpenArray("{ss}", &sub_writer);
    std::map<std::string, std::string>::const_iterator it;
    for (it = options.begin(); it != options.end(); ++it) {
      sub_writer.OpenDictEntry(&elem_writer);
      elem_writer.AppendString(it->first);
      elem_writer.AppendString(it->second);
      sub_writer.CloseContainer(&elem_writer);
    }
    writer.CloseContainer(&sub_writer);

    // Call the function.
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnTestICMP,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void UploadCrashes(UploadCrashesCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kUploadCrashes);
    debugdaemon_proxy_->CallMethod(
        &method_call, kCrashSenderTimeoutMS,
        base::BindOnce(&DebugDaemonClientImpl::OnUploadCrashes,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void OnUploadCrashes(UploadCrashesCallback callback,
                       dbus::Response* response) {
    if (callback.is_null()) {
      return;
    }

    std::move(callback).Run(response != nullptr);
  }

  void EnableDebuggingFeatures(const std::string& password,
                               EnableDebuggingCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kEnableChromeDevFeatures);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(password);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnEnableDebuggingFeatures,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void QueryDebuggingFeatures(QueryDevFeaturesCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kQueryDevFeatures);
    dbus::MessageWriter writer(&method_call);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnQueryDebuggingFeatures,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void RemoveRootfsVerification(EnableDebuggingCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kRemoveRootfsVerification);
    dbus::MessageWriter writer(&method_call);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnRemoveRootfsVerification,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void WaitForServiceToBeAvailable(
      chromeos::WaitForServiceToBeAvailableCallback callback) override {
    debugdaemon_proxy_->WaitForServiceToBeAvailable(std::move(callback));
  }

  void SetOomScoreAdj(const std::map<pid_t, int32_t>& pid_to_oom_score_adj,
                      SetOomScoreAdjCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSetOomScoreAdj);
    dbus::MessageWriter writer(&method_call);

    dbus::MessageWriter sub_writer(nullptr);
    writer.OpenArray("{ii}", &sub_writer);

    dbus::MessageWriter elem_writer(nullptr);
    for (const auto& entry : pid_to_oom_score_adj) {
      sub_writer.OpenDictEntry(&elem_writer);
      elem_writer.AppendInt32(entry.first);
      elem_writer.AppendInt32(entry.second);
      sub_writer.CloseContainer(&elem_writer);
    }
    writer.CloseContainer(&sub_writer);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnSetOomScoreAdj,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void CupsAddManuallyConfiguredPrinter(
      const std::string& name,
      const std::string& uri,
      const std::string& language,
      const std::string& ppd_contents,
      DebugDaemonClient::CupsAddPrinterCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kCupsAddManuallyConfiguredPrinterV2);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(name);
    writer.AppendString(uri);
    writer.AppendString(language);
    writer.AppendArrayOfBytes(base::as_byte_span(ppd_contents));

    debugdaemon_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnPrinterAdded,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void CupsAddAutoConfiguredPrinter(
      const std::string& name,
      const std::string& uri,
      const std::string& language,
      DebugDaemonClient::CupsAddPrinterCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kCupsAddAutoConfiguredPrinterV2);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(name);
    writer.AppendString(uri);
    writer.AppendString(language);

    debugdaemon_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnPrinterAdded,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void CupsRemovePrinter(const std::string& name,
                         DebugDaemonClient::CupsRemovePrinterCallback callback,
                         base::OnceClosure error_callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kCupsRemovePrinter);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(name);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnPrinterRemoved,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                       std::move(error_callback)));
  }

  void CupsRetrievePrinterPpd(
      const std::string& name,
      DebugDaemonClient::CupsRetrievePrinterPpdCallback callback,
      base::OnceClosure error_callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kCupsRetrievePpd);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(name);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnRetrievedPrinterPpd,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                       std::move(error_callback)));
  }

  void StartPluginVmDispatcher(const std::string& owner_id,
                               const std::string& lang,
                               PluginVmDispatcherCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kStartVmPluginDispatcher);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(owner_id);
    writer.AppendString(lang);
    debugdaemon_proxy_->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStartPluginVmDispatcher,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void StopPluginVmDispatcher(PluginVmDispatcherCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kStopVmPluginDispatcher);
    dbus::MessageWriter writer(&method_call);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStopPluginVmDispatcher,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetRlzPingSent(SetRlzPingSentCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSetRlzPingSent);
    dbus::MessageWriter writer(&method_call);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnSetRlzPingSent,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetKstaledRatio(uint8_t val, KstaledRatioCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kKstaledSetRatio);
    dbus::MessageWriter writer(&method_call);
    writer.AppendByte(val);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnSetKstaledRatio,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetSchedulerConfigurationV2(
      const std::string& config_name,
      bool lock_policy,
      SetSchedulerConfigurationV2Callback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSetSchedulerConfigurationV2);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(config_name);
    writer.AppendBool(lock_policy);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnSetSchedulerConfigurationV2,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void SetU2fFlags(const std::set<std::string>& flags,
                   chromeos::VoidDBusMethodCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kSetU2fFlags);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(base::JoinString(
        std::vector<std::string>(flags.begin(), flags.end()), ","));
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void GetU2fFlags(
      chromeos::DBusMethodCallback<std::set<std::string>> callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kGetU2fFlags);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnGetU2fFlags,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void BluetoothStartBtsnoop(BluetoothBtsnoopCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kBluetoothStartBtsnoop);
    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnBluetoothStartBtsnoop,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void BluetoothStopBtsnoop(int fd,
                            BluetoothBtsnoopCallback callback) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kBluetoothStopBtsnoop);
    dbus::MessageWriter writer(&method_call);
    writer.AppendFileDescriptor(fd);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnBluetoothStopBtsnoop,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void StopPacketCapture(const std::string& handle) override {
    dbus::MethodCall method_call(debugd::kDebugdInterface,
                                 debugd::kPacketCaptureStop);
    dbus::MessageWriter writer(&method_call);
    writer.AppendString(handle);

    debugdaemon_proxy_->CallMethod(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        base::BindOnce(&DebugDaemonClientImpl::OnStopMethod,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  // DebugDaemonClient Observer overrides.
  void AddObserver(Observer* observer) override {
    DCHECK(observer);
    observers_.AddObserver(observer);
  }

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

  void Init(dbus::Bus* bus) override {
    debugdaemon_proxy_ =
        bus->GetObjectProxy(debugd::kDebugdServiceName,
                            dbus::ObjectPath(debugd::kDebugdServicePath));
    // Listen to D-Bus signals emitted by debugd.
    auto on_connected_callback =
        base::BindRepeating(&DebugDaemonClientImpl::SignalConnected,
                            weak_ptr_factory_.GetWeakPtr());
    debugdaemon_proxy_->ConnectToSignal(
        debugd::kDebugdInterface, debugd::kPacketCaptureStartSignal,
        base::BindRepeating(
            &DebugDaemonClientImpl::PacketCaptureStartSignalReceived,
            weak_ptr_factory_.GetWeakPtr()),
        on_connected_callback);
    debugdaemon_proxy_->ConnectToSignal(
        debugd::kDebugdInterface, debugd::kPacketCaptureStopSignal,
        base::BindRepeating(
            &DebugDaemonClientImpl::PacketCaptureStopSignalReceived,
            weak_ptr_factory_.GetWeakPtr()),
        on_connected_callback);
  }

 private:
  void OnGetRoutes(
      chromeos::DBusMethodCallback<std::vector<std::string>> callback,
      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::vector<std::string> routes;
    dbus::MessageReader reader(response);
    if (!reader.PopArrayOfStrings(&routes)) {
      LOG(ERROR) << "Got non-array response from GetRoutes";
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::move(callback).Run(std::move(routes));
  }

  void OnGetAllLogs(GetLogsCallback callback, dbus::Response* response) {
    std::map<std::string, std::string> logs;
    bool broken = false;  // did we see a broken (k,v) pair?
    dbus::MessageReader sub_reader(NULL);
    if (!response || !dbus::MessageReader(response).PopArray(&sub_reader)) {
      std::move(callback).Run(false, logs);
      return;
    }
    while (sub_reader.HasMoreData()) {
      dbus::MessageReader sub_sub_reader(NULL);
      std::string key, value;
      if (!sub_reader.PopDictEntry(&sub_sub_reader) ||
          !sub_sub_reader.PopString(&key) ||
          !sub_sub_reader.PopString(&value)) {
        broken = true;
        break;
      }
      logs[key] = value;
    }
    std::move(callback).Run(!sub_reader.HasMoreData() && !broken, logs);
  }

  void OnFeedbackLogsResponse(base::WeakPtr<PipeReaderWrapper> pipe_reader,
                              dbus::Response* response,
                              dbus::ErrorResponse* err_response) {
    RecordGetFeedbackLogsV2DbusError(err_response);
    if (!response && pipe_reader.get()) {
      // We need to terminate the data stream if an error occurred while the
      // pipe reader is still waiting on read.
      pipe_reader->TerminateStream();
    }
  }

  void OnFeedbackBinaryLogsResponse(chromeos::VoidDBusMethodCallback callback,
                                    dbus::Response* response,
                                    dbus::ErrorResponse* err_response) {
    bool succeeded = !err_response;
    if (!succeeded) {
      LOG(ERROR) << "Failed to GetFeedbackBinaryLogs. Error: "
                 << err_response->GetErrorName();
    }
    std::move(callback).Run(succeeded);
  }

  // Called when a response for a simple start is received.
  void OnStartMethod(dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to request start";
      return;
    }
  }

  // Called when a response for a simple stop is received.
  void OnStopMethod(dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to request stop method through D-Bus";
      return;
    }
  }

  // Called when D-Bus method call which does not return the result is
  // completed or on its error.
  void OnVoidMethod(chromeos::VoidDBusMethodCallback callback,
                    dbus::Response* response) {
    std::move(callback).Run(response);
  }

  void OnUint64Method(chromeos::DBusMethodCallback<uint64_t> callback,
                      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    uint64_t result;
    if (!reader.PopUint64(&result)) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::move(callback).Run(std::move(result));
  }

  // Called when D-Bus method call which returns a string is completed or on
  // its error.
  void OnStringMethod(chromeos::DBusMethodCallback<std::string> callback,
                      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    dbus::MessageReader reader(response);
    std::string result;
    if (!reader.PopString(&result)) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::move(callback).Run(std::move(result));
  }

  void OnEnableDebuggingFeatures(EnableDebuggingCallback callback,
                                 dbus::Response* response) {
    if (callback.is_null())
      return;

    std::move(callback).Run(response != nullptr);
  }

  void OnQueryDebuggingFeatures(QueryDevFeaturesCallback callback,
                                dbus::Response* response) {
    if (callback.is_null())
      return;

    int32_t feature_mask = DEV_FEATURE_NONE;
    if (!response || !dbus::MessageReader(response).PopInt32(&feature_mask)) {
      std::move(callback).Run(false,
                              debugd::DevFeatureFlag::DEV_FEATURES_DISABLED);
      return;
    }

    std::move(callback).Run(true, feature_mask);
  }

  void OnRemoveRootfsVerification(EnableDebuggingCallback callback,
                                  dbus::Response* response) {
    if (callback.is_null())
      return;

    std::move(callback).Run(response != nullptr);
  }

  // Called when a response for StopAgentTracing() is received.
  void OnStopAgentTracing(dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to request systrace stop";
      // If debugd crashes or completes I/O before this message is processed
      // then pipe_reader_ can be NULL, see OnIOComplete().
      if (pipe_reader_.get()) {
        pipe_reader_.reset();
        std::move(callback_).Run(GetTracingAgentName(), GetTraceEventLabel(),
                                 scoped_refptr<base::RefCountedString>(
                                     new base::RefCountedString()));
      }
    }
    // NB: requester is signaled when i/o completes
  }

  void OnTestICMP(TestICMPCallback callback, dbus::Response* response) {
    std::string status;
    if (!response || !dbus::MessageReader(response).PopString(&status)) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::move(callback).Run(status);
  }

  // Called when pipe i/o completes; pass data on and delete the instance.
  void OnIOComplete(std::optional<std::string> result) {
    pipe_reader_.reset();
    std::string pipe_data =
        result.has_value() ? std::move(result).value() : std::string();
    std::move(callback_).Run(
        GetTracingAgentName(), GetTraceEventLabel(),
        base::MakeRefCounted<base::RefCountedString>(std::move(pipe_data)));
  }

  void OnSetOomScoreAdj(SetOomScoreAdjCallback callback,
                        dbus::Response* response) {
    std::string output;
    if (response && dbus::MessageReader(response).PopString(&output))
      std::move(callback).Run(true, output);
    else
      std::move(callback).Run(false, "");
  }

  void OnPrinterAdded(CupsAddPrinterCallback callback,
                      dbus::Response* response,
                      dbus::ErrorResponse* err_response) {
    int32_t result;

    // If we get a normal response, we need not examine the error response.
    if (response && dbus::MessageReader(response).PopInt32(&result)) {
      DCHECK_GE(result, 0);
      std::move(callback).Run(result);
      return;
    }

    // Without a normal response, we communicate the D-Bus error response
    // to the callback.
    std::string err_str;
    if (err_response) {
      dbus::MessageReader err_reader(err_response);
      err_str = err_response->GetErrorName();
    }
    chromeos::DBusLibraryError dbus_error =
        chromeos::DBusLibraryErrorFromString(err_str);
    std::move(callback).Run(dbus_error);
  }

  void OnPrinterRemoved(CupsRemovePrinterCallback callback,
                        base::OnceClosure error_callback,
                        dbus::Response* response) {
    bool result = false;
    if (response && dbus::MessageReader(response).PopBool(&result))
      std::move(callback).Run(result);
    else
      std::move(error_callback).Run();
  }

  void OnRetrievedPrinterPpd(CupsRetrievePrinterPpdCallback callback,
                             base::OnceClosure error_callback,
                             dbus::Response* response) {
    size_t length = 0;
    const uint8_t* bytes = nullptr;

    if (!(response &&
          dbus::MessageReader(response).PopArrayOfBytes(&bytes, &length)) ||
        length == 0 || bytes == nullptr) {
      LOG(ERROR) << "Failed to retrieve printer PPD";
      std::move(error_callback).Run();
      return;
    }

    std::vector<uint8_t> data(bytes, bytes + length);
    std::move(callback).Run(data);
  }

  void OnStartPluginVmDispatcher(PluginVmDispatcherCallback callback,
                                 dbus::Response* response,
                                 dbus::ErrorResponse* error) {
    if (error) {
      LOG(ERROR) << "Failed to start dispatcher, DBus error "
                 << error->GetErrorName();
      std::move(callback).Run(false);
      return;
    }

    bool result = false;
    if (response) {
      dbus::MessageReader reader(response);
      reader.PopBool(&result);
    }
    std::move(callback).Run(result);
  }

  void OnStopPluginVmDispatcher(PluginVmDispatcherCallback callback,
                                dbus::Response* response) {
    // Debugd just sends back an empty response, so we just check if
    // the response exists
    std::move(callback).Run(response != nullptr);
  }

  void OnSetRlzPingSent(SetRlzPingSentCallback callback,
                        dbus::Response* response) {
    bool result = false;
    if (response) {
      dbus::MessageReader reader(response);
      reader.PopBool(&result);
    }
    std::move(callback).Run(result);
  }

  void OnSetKstaledRatio(KstaledRatioCallback callback,
                         dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to read debugd response";
      std::move(callback).Run(false);
      return;
    }

    bool result = false;
    dbus::MessageReader reader(response);
    if (!reader.PopBool(&result)) {
      LOG(ERROR) << "Debugd response did not contain a bool";
      std::move(callback).Run(false);
      return;
    }

    std::move(callback).Run(result);
  }

  void OnSetSchedulerConfigurationV2(
      SetSchedulerConfigurationV2Callback callback,
      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(false, 0);
      return;
    }

    bool result = false;
    uint32_t num_cores_disabled = 0;
    dbus::MessageReader reader(response);
    if (!reader.PopBool(&result) || !reader.PopUint32(&num_cores_disabled)) {
      LOG(ERROR) << "Failed to read SetSchedulerConfigurationV2 response";
      std::move(callback).Run(false, 0);
      return;
    }

    std::move(callback).Run(result, num_cores_disabled);
  }

  void OnGetU2fFlags(
      chromeos::DBusMethodCallback<std::set<std::string>> callback,
      dbus::Response* response) {
    if (!response) {
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::string flags_string;
    dbus::MessageReader reader(response);
    if (!reader.PopString(&flags_string)) {
      LOG(ERROR) << "Failed to read GetU2fFlags response";
      std::move(callback).Run(std::nullopt);
      return;
    }

    std::set<std::string> flags;
    for (const auto& flag :
         base::SplitString(flags_string, ",", base::TRIM_WHITESPACE,
                           base::SPLIT_WANT_NONEMPTY)) {
      flags.insert(flag);
    }

    std::move(callback).Run(std::move(flags));
  }

  void OnBluetoothStartBtsnoop(BluetoothBtsnoopCallback callback,
                               dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to read debugd response";
    }
    std::move(callback).Run(response != nullptr);
  }

  void OnBluetoothStopBtsnoop(BluetoothBtsnoopCallback callback,
                              dbus::Response* response) {
    if (!response) {
      LOG(ERROR) << "Failed to read debugd response";
    }
    std::move(callback).Run(response != nullptr);
  }

  // Called when a D-Bus signal is initially connected.
  void SignalConnected(const std::string& interface_name,
                       const std::string& signal_name,
                       bool success) {
    if (!success)
      LOG(ERROR) << "Failed to connect to signal " << signal_name << ".";
  }

  void PacketCaptureStartSignalReceived(dbus::Signal* signal) override {
    for (auto& observer : observers_)
      observer.OnPacketCaptureStarted();
  }

  void PacketCaptureStopSignalReceived(dbus::Signal* signal) override {
    for (auto& observer : observers_)
      observer.OnPacketCaptureStopped();
  }

  raw_ptr<dbus::ObjectProxy> debugdaemon_proxy_;
  std::unique_ptr<chromeos::PipeReader> pipe_reader_;
  StopAgentTracingCallback callback_;
  scoped_refptr<base::TaskRunner> stop_agent_tracing_task_runner_;
  base::ObserverList<Observer> observers_;
  base::WeakPtrFactory<DebugDaemonClientImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
DebugDaemonClient* DebugDaemonClient::Get() {
  if (g_instance_for_test)
    return g_instance_for_test;
  return g_instance;
}

// static
void DebugDaemonClient::Initialize(dbus::Bus* bus) {
  CHECK(bus);
  CHECK(!g_instance);
  g_instance = new DebugDaemonClientImpl();
  g_instance->Init(bus);
}

// static
void DebugDaemonClient::InitializeFake() {
  CHECK(!g_instance);
  g_instance = new FakeDebugDaemonClient();
  g_instance->Init(nullptr);
}

// static
void DebugDaemonClient::SetInstanceForTest(DebugDaemonClient* client) {
  g_instance_for_test = client;
}

// static
void DebugDaemonClient::Shutdown() {
  CHECK(g_instance);
  delete g_instance;
  g_instance = nullptr;
}

DebugDaemonClient::DebugDaemonClient() = default;

DebugDaemonClient::~DebugDaemonClient() = default;

// static
std::unique_ptr<DebugDaemonClient> DebugDaemonClient::CreateInstance() {
  return std::make_unique<DebugDaemonClientImpl>();
}

}  // namespace ash