910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_DBUS_UTILS_CALL_METHOD_H_
#define COMPONENTS_DBUS_UTILS_CALL_METHOD_H_

#include <dbus/dbus.h>

#include <numeric>
#include <string>
#include <tuple>
#include <utility>

#include "base/component_export.h"
#include "base/functional/callback.h"
#include "base/numerics/clamped_math.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "components/dbus/utils/read_message.h"
#include "components/dbus/utils/read_value.h"
#include "components/dbus/utils/signature.h"
#include "components/dbus/utils/types.h"
#include "components/dbus/utils/variant.h"
#include "components/dbus/utils/write_value.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"

namespace dbus_utils {

enum class CallMethodErrorStatus {
  kErrorResponse = 0,
  kExtraDataInResponse = 1,
  kNoResponse = 2,
  kInvalidResponseFormat = 3,
};

constexpr base::TimeDelta kTimeoutDefault =
    base::Milliseconds(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
constexpr base::TimeDelta kTimeoutMax =
    base::Milliseconds(dbus::ObjectProxy::TIMEOUT_MAX);

class COMPONENT_EXPORT(COMPONENTS_DBUS) CallMethodError {
 public:
  CallMethodError(CallMethodErrorStatus status,
                  dbus::ErrorResponse* error_response);

  const CallMethodErrorStatus status;
  // These members are only set if `status` is `kErrorResponse`.
  const std::string error_name;
  const std::string error_message;
};

template <typename... Rets>
using CallMethodResult = base::expected<std::tuple<Rets...>, CallMethodError>;

// Like CallMethodResult, but the return types are specified via a signature.
template <SignatureLiteral RetsSignature>
using CallMethodResultSig =
    base::expected<internal::ParseDBusSignaturePack<RetsSignature>,
                   CallMethodError>;

namespace internal {

template <typename ArgsTuple, typename Result, std::size_t... Is>
void CallMethodImpl(dbus::ObjectProxy* proxy,
                    const std::string& interface,
                    const std::string& method,
                    base::TimeDelta timeout,
                    base::OnceCallback<void(Result)> callback,
                    std::index_sequence<Is...>,
                    const std::tuple_element_t<Is, ArgsTuple>&... args) {
  dbus::MethodCall dbus_call(interface, method);
  dbus::MessageWriter writer(&dbus_call);
  (WriteValue<std::tuple_element_t<Is, ArgsTuple>>(writer, args), ...);

  base::ClampedNumeric<int32_t> timeout_ms = timeout.InMilliseconds();
  proxy->CallMethodWithErrorResponse(
      &dbus_call, timeout_ms,
      base::BindOnce(
          [](base::OnceCallback<void(Result)> cb, dbus::Response* response,
             dbus::ErrorResponse* error_response) {
            if (response) {
              auto rets = ReadMessage<typename Result::value_type>(*response);
              if (rets.has_value()) {
                std::move(cb).Run(std::move(*rets));
                return;
              }
              switch (rets.error()) {
                case MessageFormatError::kInvalidMessageFormat:
                  std::move(cb).Run(base::unexpected(CallMethodError(
                      CallMethodErrorStatus::kInvalidResponseFormat, nullptr)));
                  return;
                case MessageFormatError::kExtraDataInMessage:
                  std::move(cb).Run(base::unexpected(CallMethodError(
                      CallMethodErrorStatus::kExtraDataInResponse, nullptr)));
                  return;
              }
            } else if (error_response) {
              std::move(cb).Run(base::unexpected(CallMethodError(
                  CallMethodErrorStatus::kErrorResponse, error_response)));
            } else {
              std::move(cb).Run(base::unexpected(CallMethodError(
                  CallMethodErrorStatus::kNoResponse, nullptr)));
            }
          },
          std::move(callback)));
}

}  // namespace internal

// This is similar to dbus::ObjectProxy::CallMethod, except the MethodCall,
// MessageWriter, and MessageReader logic is contained within. The method
// arguments and reply type are specified in `ArgsSignature` and
// `RetsSignature`. This is intended to be used on Linux where a C++ bindings
// generator is not available, to allow a more declarative style of calling
// D-Bus methods. `callback` takes a `CallMethodResult`, which contains either a
// tuple of the return types, or a `CallMethodError`. Two template arguments are
// required: `ArgsSignature` and `RetsSignature`, which are C string literals
// that must match the D-Bus signature of the method arguments and reply.
template <SignatureLiteral ArgsSignature, SignatureLiteral RetsSignature>
void CallMethod(
    dbus::ObjectProxy* proxy,
    const std::string& interface,
    const std::string& method,
    base::TimeDelta timeout,
    base::OnceCallback<void(CallMethodResultSig<RetsSignature>)> callback,
    const auto&... args) {
  using ArgsTupleType = internal::ParseDBusSignaturePack<ArgsSignature>;
  internal::CallMethodImpl<ArgsTupleType, CallMethodResultSig<RetsSignature>>(
      proxy, interface, method, timeout, std::move(callback),
      std::make_index_sequence<std::tuple_size_v<ArgsTupleType>>{}, args...);
}

// This is a convenience overload of CallMethod that uses the default timeout.
template <SignatureLiteral ArgsSignature, SignatureLiteral RetsSignature>
void CallMethod(
    dbus::ObjectProxy* proxy,
    const std::string& interface,
    const std::string& method,
    base::OnceCallback<void(CallMethodResultSig<RetsSignature>)> callback,
    const auto&... args) {
  CallMethod<ArgsSignature, RetsSignature>(
      proxy, interface, method, kTimeoutDefault, std::move(callback), args...);
}

}  // namespace dbus_utils

#endif  // COMPONENTS_DBUS_UTILS_CALL_METHOD_H_