blob: ab29c1c6aaf474c5a97db70a57c1fb4a99fc100b [file] [log] [blame]
// 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_