1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
// 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_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 {
namespace internal {
template <typename... Ts>
std::tuple<Ts...> Unwrap(std::tuple<std::optional<Ts>...> input) {
return std::apply(
[](auto&&... args) { return std::tuple(*std::move(args)...); },
std::move(input));
}
} // namespace internal
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>;
// 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 `args` and `callback`,
// respectively. 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. The first argument to `callback` is a status code, and the
// remaining arguments are deserialized from the reply, or default-constructed
// if they could not be deserialized. 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 <internal::StringLiteral ArgsSignature,
internal::StringLiteral RetsSignature,
typename... Args,
typename... Rets>
void CallMethod(dbus::ObjectProxy* proxy,
const std::string& interface,
const std::string& method,
base::TimeDelta timeout,
base::OnceCallback<void(CallMethodResult<Rets...>)> callback,
const Args&... args)
requires((internal::IsSupportedDBusType<Args> && ...) &&
(internal::IsSupportedDBusType<Rets> && ...) &&
(internal::StrJoin(
(internal::DBusSignature<std::decay_t<Args>>::kValue)...) ==
ArgsSignature.value) &&
(internal::StrJoin(
(internal::DBusSignature<std::decay_t<Rets>>::kValue)...) ==
RetsSignature.value))
{
dbus::MethodCall dbus_call(interface, method);
dbus::MessageWriter writer(&dbus_call);
(internal::WriteValue(writer, args), ...);
base::ClampedNumeric<int32_t> timeout_ms = timeout.InMilliseconds();
proxy->CallMethodWithErrorResponse(
&dbus_call, timeout_ms,
base::BindOnce(
[](base::OnceCallback<void(CallMethodResult<Rets...>)> cb,
dbus::Response* response, dbus::ErrorResponse* error_response) {
if (response) {
std::tuple<std::optional<Rets>...> rets;
dbus::MessageReader reader(response);
const bool success = std::apply(
[&](auto&... value) {
auto read_and_assign = [&](auto& member) {
using OptionalType =
std::remove_cvref_t<decltype(member)>;
if (auto result = internal::ReadValue<
typename OptionalType::value_type>(reader)) {
member = std::move(*result);
return true;
}
return false;
};
return (read_and_assign(value) && ...);
},
rets);
if (!success) {
std::move(cb).Run(base::unexpected(CallMethodError(
CallMethodErrorStatus::kInvalidResponseFormat,
nullptr)));
} else if (reader.HasMoreData()) {
std::move(cb).Run(base::unexpected(
CallMethodError(CallMethodErrorStatus::kExtraDataInResponse,
nullptr)));
} else {
std::move(cb).Run(internal::Unwrap(std::move(rets)));
}
} 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)));
}
// This is a convenience overload of CallMethod that uses the default timeout.
template <internal::StringLiteral ArgsSignature,
internal::StringLiteral RetsSignature,
typename... Args,
typename... Rets>
void CallMethod(dbus::ObjectProxy* proxy,
const std::string& interface,
const std::string& method,
base::OnceCallback<void(CallMethodResult<Rets...>)> callback,
const Args&... args)
requires((internal::IsSupportedDBusType<Args> && ...) &&
(internal::IsSupportedDBusType<Rets> && ...) &&
(internal::StrJoin(
(internal::DBusSignature<std::decay_t<Args>>::kValue)...) ==
ArgsSignature.value) &&
(internal::StrJoin(
(internal::DBusSignature<std::decay_t<Rets>>::kValue)...) ==
RetsSignature.value))
{
CallMethod<ArgsSignature, RetsSignature>(
proxy, interface, method, kTimeoutDefault, std::move(callback), args...);
}
} // namespace dbus_utils
#endif // COMPONENTS_DBUS_UTILS_CALL_METHOD_H_
|