blob: 2307bc2aa53c175762ef248b80ccd9d3ed73cd38 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_BLUETOOTH_FLOSS_EXPORTED_CALLBACK_MANAGER_H_
#define DEVICE_BLUETOOTH_FLOSS_EXPORTED_CALLBACK_MANAGER_H_
#include <memory>
#include <sstream>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"
#include "dbus/object_path.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
namespace {
template <typename T>
using MethodDelegate =
base::RepeatingCallback<void(dbus::MethodCall*,
base::WeakPtr<T>,
dbus::ExportedObject::ResponseSender)>;
// A wrapper that runs the barrier closure |callback|.
void OnMethodExported(base::RepeatingClosure callback,
const std::string& interface,
const std::string& method,
bool success) {
callback.Run();
}
}
// Private class helper.
template <typename T, typename... Args>
class CallbackForwarder {
private:
// Holds a parameter pack C++ has problem with inferring multiple variadic
// template parameters. So this is one way to group each template parameter
// pack to disambiguate them.
template <typename...>
struct TypeList;
// Converts parameters, forwards to callback object, and reply the D-Bus
// sender. This is actually a function but is written as a struct because C++
// does not allow partial function template specialization.
template <typename T1, typename T2>
struct ParseParamsAndForward {};
// Base case. There is no more parameters to parse and we have the complete
// built parameters to forward to the callback function and then reply the
// sender.
template <typename... BuiltArgs>
struct ParseParamsAndForward<TypeList<>, TypeList<BuiltArgs...>> {
static void Do(dbus::MessageReader* reader,
dbus::MethodCall* method_call,
base::OnceCallback<void(Args...)> delegate,
dbus::ExportedObject::ResponseSender response_sender,
BuiltArgs... params) {
std::move(delegate).Run(std::forward<BuiltArgs>(params)...);
std::move(response_sender)
.Run(dbus::Response::FromMethodCall(method_call));
}
};
// Recursively (at compile-time) parse parameters and builds parameter list.
// At the end of the recursion we will have a list of parameters already
// parsed at |BuiltArgs... params| and ready to forward to the callback
// function.
template <typename FirstType,
typename... RemainingArgs,
typename... BuiltArgs>
struct ParseParamsAndForward<TypeList<FirstType, RemainingArgs...>,
TypeList<BuiltArgs...>> {
static void Do(dbus::MessageReader* reader,
dbus::MethodCall* method_call,
base::OnceCallback<void(Args...)> delegate,
dbus::ExportedObject::ResponseSender response_sender,
BuiltArgs... params) {
std::decay_t<FirstType> data;
if (!floss::FlossDBusClient::ReadDBusParam(reader, &data)) {
std::stringstream message;
floss::DBusTypeInfo type_info = floss::GetDBusTypeInfo(&data);
std::string next_data_type =
reader->HasMoreData() ? ("'" + reader->GetDataSignature() + "'")
: "none";
message << "Cannot parse the " << (sizeof...(BuiltArgs) + 1)
<< "th parameter, expected type signature '"
<< type_info.dbus_signature << "' "
<< "(" << type_info.type_name << ")"
<< ", got " << next_data_type;
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, floss::FlossDBusClient::kErrorInvalidParameters,
message.str()));
return;
}
ParseParamsAndForward<TypeList<RemainingArgs...>,
TypeList<BuiltArgs..., FirstType>>::
Do(reader, method_call, std::move(delegate),
std::move(response_sender), std::forward<BuiltArgs>(params)...,
data);
}
};
// The start of the recursive ParseParamsAndForward.
static void Forward(dbus::MethodCall* method_call,
base::OnceCallback<void(Args...)> delegate,
dbus::ExportedObject::ResponseSender response_sender) {
dbus::MessageReader reader(method_call);
ParseParamsAndForward<TypeList<Args...>, TypeList<>>::Do(
&reader, method_call, std::move(delegate), std::move(response_sender));
}
public:
// Returns a RepeatingCallback with captured |func| that parses D-Bus
// parameters and forwards it to |func|.
//
// Being a RepeatingCallback has the benefit that the invoker does not need to
// know the signature of |func| at compile time.
static MethodDelegate<T> CreateForwarder(void (T::*func)(Args...)) {
return base::BindRepeating(
[](void (T::*func)(Args...), dbus::MethodCall* method_call,
base::WeakPtr<T> target,
dbus::ExportedObject::ResponseSender response_sender) {
Forward(method_call, base::BindOnce(func, target),
std::move(response_sender));
},
func);
}
};
namespace floss {
// Utility to manage callbacks. This simplifies:
// * Exporting and unexporting a callback object to/from D-Bus.
// * Forwarding received D-Bus method calls to C++ callback objects,
// including parsing the method parameters according to the defined types.
//
// Example usage:
//
// // Create the manager and specify that this is for type ISomeCallback
// // and the D-Bus interface name is "org.some.interface".
// ExportedCallbackManager<ISomeCallback> manager("org.some.interface");
//
// // Must be called first before usage.
// manager.Init(bus);
//
// // Define forwarding for method "OnSomethingHappened" to function call of
// // ISomeCallback::OnSomethingHappened. The manager handles the parsing of
// // parameters and forwarding to the function according to the types.
// manager.AddMethod(
// "OnSomethingHappened", &ISomeCallback::OnSomethingHappened);
// // Define all other methods.
// manager.AddMethod("SomeMethod", &ISomeCallback::SomeMethod);
//
// auto some_callback = std::make_unique<CreateSomeCallbackImpl>();
// // After defining the methods, it's ready to export callback objects.
// manager.ExportCallback(
// dbus::ObjectPath("/path/to/callback"), some_callback->GetWeakPtr());
template <typename T>
class ExportedCallbackManager {
public:
// |interface_name| specifies the D-Bus interface name of the exported
// callbacks managed by this utility.
explicit ExportedCallbackManager(std::string interface_name)
: interface_name_(std::move(interface_name)) {}
// Initializes the manager with a |bus|. Must be called before any usage.
void Init(scoped_refptr<dbus::Bus> bus) { bus_ = bus; }
// Adds a method to be forwarded, following the specified method name and
// the pointer to a member function of T.
template <typename... Args>
void AddMethod(std::string name, void (T::*func)(Args...)) {
auto forwarder = CallbackForwarder<T, Args...>::CreateForwarder(func);
methods_[name] = forwarder;
}
// Exports the callback object to D-Bus. This object will receive method calls
// that are defined via AddMethod.
//
// |exported_callback| weak pointer has to be valid at time of invocation.
bool ExportCallback(const dbus::ObjectPath& callback_path,
base::WeakPtr<T> exported_callback,
base::OnceCallback<void()> on_exported_callback) {
CHECK(exported_callback) << "Callback ptr is not valid";
CHECK(bus_) << "Called without Init";
VLOG(1) << "Exporting callback at " << callback_path.value();
if (base::Contains(exported_callbacks_, callback_path.value())) {
LOG(ERROR) << "Cannot export existing object path";
return false;
}
exported_callbacks_[callback_path.value()] = exported_callback;
dbus::ExportedObject* exported_object =
bus_->GetExportedObject(callback_path);
if (!exported_object) {
LOG(ERROR) << "Could not export client callback "
<< callback_path.value();
return false;
}
auto export_complete =
base::BarrierClosure(methods_.size(), std::move(on_exported_callback));
// Catch all registered methods with OnMethodCall and it will handle the
// forwarding to the callback.
for (auto const& [name, method] : methods_) {
VLOG(1) << "Exporting method " << interface_name_ << "." << name;
exported_object->ExportMethod(
interface_name_, name,
base::BindRepeating(&ExportedCallbackManager::OnMethodCall,
weak_ptr_factory_.GetWeakPtr(), name, method,
exported_callback),
base::BindOnce(&OnMethodExported, export_complete));
}
return true;
}
// Removes the D-Bus object from being exported.
void UnexportCallback(const dbus::ObjectPath& callback_path) {
if (!base::Contains(exported_callbacks_, callback_path.value())) {
LOG(WARNING) << "Not yet exported: " << callback_path.value();
return;
}
bus_->UnregisterExportedObject(callback_path);
exported_callbacks_.erase(callback_path.value());
}
private:
void OnMethodCall(std::string method_name,
MethodDelegate<T> delegate,
base::WeakPtr<T> exported_callback,
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
if (!exported_callback) {
LOG(WARNING) << "Callback no longer exists for method " << method_name;
std::move(response_sender)
.Run(dbus::ErrorResponse::FromMethodCall(
method_call, floss::FlossDBusClient::kErrorDoesNotExist,
"Callback does not exist"));
return;
}
DCHECK(method_name == method_call->GetMember())
<< "Method name from D-Bus does not match with the registered name";
delegate.Run(method_call, exported_callback, std::move(response_sender));
}
scoped_refptr<dbus::Bus> bus_;
std::string interface_name_;
std::unordered_map<std::string, base::WeakPtr<T>> exported_callbacks_;
std::unordered_map<std::string, MethodDelegate<T>> methods_;
// WeakPtrFactory must be last.
base::WeakPtrFactory<ExportedCallbackManager> weak_ptr_factory_{this};
};
} // namespace floss
#endif // DEVICE_BLUETOOTH_FLOSS_EXPORTED_CALLBACK_MANAGER_H_