blob: 75a4bd7f99d666f82e5cfecee3108e9fe6dc6db3 [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_EXPORT_METHOD_H_
#define COMPONENTS_DBUS_UTILS_EXPORT_METHOD_H_
#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "components/dbus/utils/read_message.h"
#include "components/dbus/utils/signature.h"
#include "components/dbus/utils/write_value.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
namespace dbus_utils {
struct ExportMethodError {
std::string name;
std::string message;
};
// The result of an exported method callback.
template <typename... Rets>
using ExportMethodResult =
base::expected<std::tuple<Rets...>, ExportMethodError>;
// The result of an exported method callback, with the signature specified as a
// literal.
template <SignatureLiteral RetsSignature>
using ExportMethodResultSig =
base::expected<internal::ParseDBusSignaturePack<RetsSignature>,
ExportMethodError>;
namespace internal {
template <typename ArgsTuple, typename RetsTuple, typename Callback>
void OnExportedMethod(Callback callback,
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender sender) {
auto args_result = internal::ReadMessage<ArgsTuple>(*method_call);
if (!args_result.has_value()) {
std::move(sender).Run(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"));
return;
}
auto result = std::apply(
[&](auto&&... args) {
return callback.Run(std::forward<decltype(args)>(args)...);
},
std::move(*args_result));
if (result.has_value()) {
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
std::apply([&](const auto&... rets) { (WriteValue(writer, rets), ...); },
*result);
std::move(sender).Run(std::move(response));
} else {
std::move(sender).Run(dbus::ErrorResponse::FromMethodCall(
method_call, result.error().name, result.error().message));
}
}
template <typename T>
struct CallbackTraits;
template <typename... Rets, typename... Args>
struct CallbackTraits<base::RepeatingCallback<
base::expected<std::tuple<Rets...>, ExportMethodError>(Args...)>> {
using ArgsTuple = std::tuple<Args...>;
using RetsTuple = std::tuple<Rets...>;
};
} // namespace internal
// Exports a method with a strongly-typed callback. The `callback` receives the
// method arguments and returns a `base::expected` containing a tuple of return
// values on success, or a `MethodReplyError` on failure. If a peer calls the
// method with invalid arguments, a D-Bus error response with name
// "org.freedesktop.DBus.Error.InvalidArgs" will be sent.
template <SignatureLiteral ArgsSignature,
SignatureLiteral RetsSignature,
typename Callback>
void ExportMethod(dbus::ExportedObject* exported_object,
const std::string& interface_name,
const std::string& method_name,
Callback callback,
dbus::ExportedObject::OnExportedCallback on_exported) {
using ArgsTuple = internal::ParseDBusSignaturePack<ArgsSignature>;
using RetsTuple = internal::ParseDBusSignaturePack<RetsSignature>;
using CallbackArgsTuple =
typename internal::CallbackTraits<Callback>::ArgsTuple;
using CallbackRetsTuple =
typename internal::CallbackTraits<Callback>::RetsTuple;
static_assert(std::is_same_v<ArgsTuple, CallbackArgsTuple>,
"Callback arguments do not match signature");
static_assert(std::is_same_v<RetsTuple, CallbackRetsTuple>,
"Callback return values do not match signature");
exported_object->ExportMethod(
interface_name, method_name,
base::BindRepeating(
&internal::OnExportedMethod<ArgsTuple, RetsTuple, Callback>,
callback),
std::move(on_exported));
}
} // namespace dbus_utils
#endif // COMPONENTS_DBUS_UTILS_EXPORT_METHOD_H_