#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)>;
void OnMethodExported(base::RepeatingClosure callback,
const std::string& interface,
const std::string& method,
bool success) {
callback.Run();
}
}
template <typename T, typename... Args>
class CallbackForwarder {
private:
template <typename...>
struct TypeList;
template <typename T1, typename T2>
struct ParseParamsAndForward {};
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));
}
};
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);
}
};
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:
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 {
template <typename T>
class ExportedCallbackManager {
public:
explicit ExportedCallbackManager(std::string interface_name)
: interface_name_(std::move(interface_name)) {}
void Init(scoped_refptr<dbus::Bus> bus) { bus_ = bus; }
template <typename... Args>
void AddMethod(std::string name, void (T::*func)(Args...)) {
auto forwarder = CallbackForwarder<T, Args...>::CreateForwarder(func);
methods_[name] = forwarder;
}
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));
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;
}
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_;
base::WeakPtrFactory<ExportedCallbackManager> weak_ptr_factory_{this};
};
}
#endif