blob: 39df8f8809c5238beacf95b1fd3f3ec1de3e7a33 [file] [log] [blame]
// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// DBusObject is a special helper class that simplifies the implementation of
// D-Bus objects in C++. It provides an easy way to define interfaces with
// methods and properties and offloads a lot of work to register the object and
// all of its interfaces, to marshal method calls (by converting D-Bus method
// parameters to native C++ types and invoking native method handlers), etc.
// The basic usage pattern of this class is as follows:
/*
class MyDbusObject {
public:
MyDbusObject(const ExportedObjectManager_WeakPtr& object_manager)
: dbus_object_(object_manager, dbus::ObjectPath("/org/chromium/my_obj")) {
}
void Init(const AsyncEventSequencer::CompletionAction& callback) {
DBusInterface* my_interface =
dbus_object_.AddOrGetInterface("org.chromium.MyInterface");
my_interface->AddMethodHandler("Method1", this, &MyDbusObject::Method1);
my_interface->AddMethodHandler("Method2", this, &MyDbusObject::Method2);
my_interface->AddProperty("Property1", &prop1_);
my_interface->AddProperty("Property2", &prop2_);
prop1_.SetValue("prop1_value");
prop2_.SetValue(50);
// Register the object by exporting its methods and properties and
// exposing them to D-Bus clients.
dbus_object_.RegisterAsync(callback);
}
private:
DBusObject dbus_object_;
// Make sure the properties outlive the DBusObject they are registered with.
chromeos::dbus_utils::ExportedProperty<std::string> prop1_;
chromeos::dbus_utils::ExportedProperty<int> prop2_;
int Method1(chromeos::ErrorPtr* error);
void Method2(chromeos::ErrorPtr* error, const std::string& message);
DISALLOW_COPY_AND_ASSIGN(MyDbusObject);
};
*/
#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_
#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_
#include <map>
#include <string>
#include <base/bind.h>
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
#include <chromeos/chromeos_export.h>
#include <chromeos/dbus/async_event_sequencer.h>
#include <chromeos/dbus/dbus_object_internal_impl.h>
#include <chromeos/dbus/dbus_signal.h>
#include <chromeos/dbus/exported_property_set.h>
#include <chromeos/errors/error.h>
#include <dbus/bus.h>
#include <dbus/exported_object.h>
#include <dbus/message.h>
#include <dbus/object_path.h>
namespace chromeos {
namespace dbus_utils {
class ExportedObjectManager;
class ExportedPropertyBase;
// This is an abstract base class to allow dispatching a native C++ callback
// method when a corresponding D-Bus method is called.
class DBusInterfaceMethodHandler {
public:
virtual ~DBusInterfaceMethodHandler() = default;
virtual std::unique_ptr<dbus::Response> HandleMethod(
dbus::MethodCall* method_call) = 0;
};
// A template overload of DBusInterfaceMethodHandler that is specialized
// for particular method handler type signature. The handler is expected
// to take an arbitrary number of arguments of type |Args...| and return
// a value of type |R| (which could be 'void' as well).
template<typename R, typename... Args>
class TypedDBusInterfaceMethodHandler : public DBusInterfaceMethodHandler {
public:
// A constructor that takes a |handler| to be called when HandleMethod()
// virtual function is invoked.
TypedDBusInterfaceMethodHandler(
const base::Callback<R(chromeos::ErrorPtr*, Args...)>& handler)
: handler_(handler) {
}
// This method forwards the call to |handler_| and extracts the required
// arguments from the D-Bus message buffer specified in |method_call|.
// The return value of |handler_| (if any) is sent back via the returned
// dbus::Response object, which could also include error details if the
// handler call has failed.
std::unique_ptr<dbus::Response> HandleMethod(
dbus::MethodCall* method_call) override {
dbus::MessageReader reader(method_call);
using Handler = TypedReturnDBusMethodHandler<R, Args...>;
return TypedReturnDBusInvoker<R, Handler, Args...>::Invoke(handler_,
method_call,
&reader);
}
private:
// C++ callback to be called when a D-Bus method is dispatched.
base::Callback<R(chromeos::ErrorPtr*, Args...)> handler_;
DISALLOW_COPY_AND_ASSIGN(TypedDBusInterfaceMethodHandler);
};
// A specialization of TypedDBusInterfaceMethodHandler for returning
// dbus::Response object instead of arbitrary value. This specialization is
// used when C++ callback expects parsed input parameters but its return
// value is custom and it's up to the callback's implementer to return a valid
// D-Bus response object. Also note that the callback does not take ErrorPtr*
// as a first parameter, since the error information should be returned through
// the D-Bus error response object.
template<typename... Args>
class TypedDBusInterfaceMethodHandler<std::unique_ptr<dbus::Response>, Args...>
: public DBusInterfaceMethodHandler {
public:
// A constructor that takes a |handler| to be called when HandleMethod()
// virtual function is invoked.
TypedDBusInterfaceMethodHandler(
const base::Callback<std::unique_ptr<dbus::Response>(
dbus::MethodCall* method_call, Args...)>& handler)
: handler_(handler) {
}
// This method forwards the call to |handler_| and extracts the required
// arguments from the D-Bus message buffer specified in |method_call|.
// The dbus::Response return value of |handler_| is passed on to the caller.
std::unique_ptr<dbus::Response> HandleMethod(
dbus::MethodCall* method_call) override {
dbus::MessageReader reader(method_call);
using Handler = RawReturnDBusMethodHandler<Args...>;
return RawReturnDBusInvoker<Handler, Args...>::Invoke(handler_,
method_call,
&reader);
}
private:
// C++ callback to be called when a D-Bus method is dispatched.
base::Callback<std::unique_ptr<dbus::Response>(dbus::MethodCall* method_call,
Args...)> handler_;
DISALLOW_COPY_AND_ASSIGN(TypedDBusInterfaceMethodHandler);
};
// An implementation of DBusInterfaceMethodHandler that has custom processing
// of both input and output parameters. This class is used by
// DBusObject::AddRawMethodHandler and expects the callback to be of the
// following signature:
// std::unique_ptr<dbus::Response>(dbus::MethodCall* method_call)
// It will be up to the callback to parse the input parameters from the
// message buffer and construct the D-Bus Response object.
class RawDBusInterfaceMethodHandler : public DBusInterfaceMethodHandler {
public:
// A constructor that takes a |handler| to be called when HandleMethod()
// virtual function is invoked.
RawDBusInterfaceMethodHandler(
const base::Callback<std::unique_ptr<dbus::Response>(
dbus::MethodCall* method_call)>& handler)
: handler_(handler) {
}
std::unique_ptr<dbus::Response> HandleMethod(
dbus::MethodCall* method_call) override {
return handler_.Run(method_call);
}
private:
// C++ callback to be called when a D-Bus method is dispatched.
base::Callback<std::unique_ptr<dbus::Response>(dbus::MethodCall* method_call)>
handler_;
DISALLOW_COPY_AND_ASSIGN(RawDBusInterfaceMethodHandler);
};
class DBusObject; // forward-declaration.
// This is an implementation proxy class for a D-Bus interface of an object.
// The important functionality for the users is the ability to add D-Bus method
// handlers and define D-Bus object properties. This is achieved by using one
// of the overload of AddMethodHandler() and AddProperty() respectively.
// There are six overloads for DBusInterface::AddMethodHandler():
// 1. That takes a handler as base::Callback<R(ErrorPtr*, Args...)>
// 2. That takes a static function of signature R(ErrorPtr*, Args...)
// 3. That takes a class instance pointer and a class member function of
// signature R(ErrorPtr*, Args...).
// 4-6. Same as 1-3 above but expect the callback to provide a custom response:
// std::unique_ptr<dbus::Response>(dbus::MethodCall*, Args...).
// There is also an AddRawMethodHandler() call that lets provide a custom
// handler that can parse its own input parameter and construct a custom
// response.
class CHROMEOS_EXPORT DBusInterface final {
public:
DBusInterface(DBusObject* dbus_object, const std::string& interface_name);
// Register a DBus method handler for |method_name| as base::Callback.
template<typename R, typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
const base::Callback<R(chromeos::ErrorPtr*, Args...)>& handler) {
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<R, Args...>(handler));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a D-Bus method handler for |method_name| as static function.
template<typename R, typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
R(*handler)(chromeos::ErrorPtr*, Args...)) {
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<R, Args...>(base::Bind(handler)));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a D-Bus method handler for |method_name| as class member function.
template<typename R, typename Instance, typename Class, typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
Instance instance,
R(Class::*handler)(chromeos::ErrorPtr*, Args...)) {
auto callback = base::Bind(handler, instance);
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<R, Args...>(callback));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a D-Bus method handler for |method_name| as base::Callback.
template<typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
const base::Callback<std::unique_ptr<dbus::Response>(dbus::MethodCall*,
Args...)>& handler) {
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<std::unique_ptr<dbus::Response>,
Args...>(handler));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a D-Bus method handler for |method_name| as static function.
template<typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
std::unique_ptr<dbus::Response>(*handler)(dbus::MethodCall*, Args...)) {
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<std::unique_ptr<dbus::Response>,
Args...>(base::Bind(handler)));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a D-Bus method handler for |method_name| as class member function.
template<typename Instance, typename Class, typename... Args>
inline void AddMethodHandler(
const std::string& method_name,
Instance instance,
std::unique_ptr<dbus::Response>(Class::*handler)(dbus::MethodCall*,
Args...)) {
auto callback = base::Bind(handler, instance);
std::unique_ptr<DBusInterfaceMethodHandler> typed_method_handler(
new TypedDBusInterfaceMethodHandler<std::unique_ptr<dbus::Response>,
Args...>(callback));
AddHandlerImpl(method_name, std::move(typed_method_handler));
}
// Register a raw D-Bus method handler for |method_name| as base::Callback.
inline void AddRawMethodHandler(
const std::string& method_name,
const base::Callback<std::unique_ptr<dbus::Response>(dbus::MethodCall*)>&
handler) {
std::unique_ptr<DBusInterfaceMethodHandler> raw_method_handler(
new RawDBusInterfaceMethodHandler(handler));
AddHandlerImpl(method_name, std::move(raw_method_handler));
}
// Register a D-Bus property.
void AddProperty(const std::string& property_name,
ExportedPropertyBase* prop_base);
// Registers a D-Bus signal that has a specified number and types (|Args|) of
// arguments. Returns a weak pointer to the DBusSignal object which can be
// used to send the signal on this interface when needed:
/*
DBusInterface* itf = dbus_object->AddOrGetInterface("Interface");
auto signal = itf->RegisterSignal<int, bool>("MySignal");
...
// Send the Interface.MySig(12, true) signal.
if (signal.lock()->Send(12, true)) { ... }
*/
// Or if the signal signature is long or complex, you can alias the
// DBusSignal<Args...> signal type and use RegisterSignalOfType method
// instead:
/*
DBusInterface* itf = dbus_object->AddOrGetInterface("Interface");
using MySignal = DBusSignal<int, bool>;
auto signal = itf->RegisterSignalOfType<MySignal>("MySignal");
...
// Send the Interface.MySig(12, true) signal.
if (signal.lock()->Send(12, true)) { ... }
*/
// If the signal with the given name was already registered, the existing
// copy of the signal proxy object is returned as long as the method signature
// of the original signal matches the current call. If it doesn't, the method
// aborts.
// RegisterSignalOfType can be used to create a signal if the type of the
// complete DBusSignal<Args...> class which is pre-defined/aliased earlier.
template<typename DBusSignalType>
inline std::weak_ptr<DBusSignalType> RegisterSignalOfType(
const std::string& signal_name) {
auto signal = std::make_shared<DBusSignalType>(dbus_object_,
interface_name_,
signal_name);
CHECK(signals_.insert(std::make_pair(signal_name, signal)).second)
<< "The signal '" << signal_name << "' is already registered";
return signal;
}
// For simple signal arguments, you can specify their types directly in
// RegisterSignal<t1, t2, ...>():
// auto signal = itf->RegisterSignal<int>("SignalName");
// This will create a callback signal object that expects one int argument.
template<typename... Args>
inline std::weak_ptr<DBusSignal<Args...>> RegisterSignal(
const std::string& signal_name) {
return RegisterSignalOfType<DBusSignal<Args...>>(signal_name);
}
private:
// A generic D-Bus method handler for the interface. It extracts the method
// name from |method_call|, looks up a registered handler from |handlers_|
// map and dispatched the call to that handler.
CHROMEOS_PRIVATE std::unique_ptr<dbus::Response> HandleMethodCall(
dbus::MethodCall* method_call);
// Helper to add a handler for method |method_name| to the |handlers_| map.
// Not marked CHROMEOS_PRIVATE because it needs to be called by the inline
// template functions AddMethodHandler(...)
void AddHandlerImpl(const std::string& method_name,
std::unique_ptr<DBusInterfaceMethodHandler> handler);
// Exports all the methods and properties of this interface and claims the
// D-Bus interface.
// object_manager - ExportedObjectManager instance that notifies D-Bus
// listeners of a new interface being claimed.
// exported_object - instance of D-Bus object the interface is being added to.
// object_path - D-Bus object path for the object instance.
// interface_name - name of interface being registered.
// completion_callback - a callback to be called when the asynchronous
// registration operation is completed.
CHROMEOS_PRIVATE void ExportAsync(
ExportedObjectManager* object_manager,
dbus::Bus* bus,
dbus::ExportedObject* exported_object,
const dbus::ObjectPath& object_path,
const AsyncEventSequencer::CompletionAction& completion_callback);
// Method registration map.
std::map<std::string, std::unique_ptr<DBusInterfaceMethodHandler>> handlers_;
// Signal registration map.
std::map<std::string, std::shared_ptr<DBusSignalBase>> signals_;
friend class DBusObject;
DBusObject* dbus_object_;
std::string interface_name_;
DISALLOW_COPY_AND_ASSIGN(DBusInterface);
};
// A D-Bus object implementation class. Manages the interfaces implemented
// by this object.
class CHROMEOS_EXPORT DBusObject {
public:
// object_manager - ExportedObjectManager instance that notifies D-Bus
// listeners of a new interface being claimed and property
// changes on those interfaces.
// object_path - D-Bus object path for the object instance.
DBusObject(ExportedObjectManager* object_manager,
const scoped_refptr<dbus::Bus>& bus,
const dbus::ObjectPath& object_path);
virtual ~DBusObject();
// Returns an proxy handler for the interface |interface_name|. If the
// interface proxy does not exist yet, it will be automatically created.
DBusInterface* AddOrGetInterface(const std::string& interface_name);
// Registers the object instance with D-Bus. This is an asynchronous call
// that will call |completion_callback| when the object and all of its
// interfaces are registered.
virtual void RegisterAsync(
const AsyncEventSequencer::CompletionAction& completion_callback);
// Finds a handler for the given method of a specific interface.
// Returns nullptr if the interface is not registered or there is
// no method with the specified name found on that interface.
DBusInterfaceMethodHandler* FindMethodHandler(
const std::string& interface_name, const std::string& method_name) const;
// Returns the ExportedObjectManager proxy, if any. If DBusObject has been
// constructed without an object manager, this method returns an empty
// smart pointer (containing nullptr).
const base::WeakPtr<ExportedObjectManager>& GetObjectManager() const {
return object_manager_;
}
// Sends a signal from the exported D-Bus object.
bool SendSignal(dbus::Signal* signal);
private:
// A map of all the interfaces added to this object.
std::map<std::string, std::unique_ptr<DBusInterface>> interfaces_;
// Exported property set for properties registered with the interfaces
// implemented by this D-Bus object.
ExportedPropertySet property_set_;
// Delegate object implementing org.freedesktop.DBus.ObjectManager interface.
base::WeakPtr<ExportedObjectManager> object_manager_;
// D-Bus bus object.
scoped_refptr<dbus::Bus> bus_;
// D-Bus object path for this object.
dbus::ObjectPath object_path_;
// D-Bus object instance once this object is successfully exported.
dbus::ExportedObject* exported_object_ = nullptr; // weak; owned by |bus_|.
friend class DBusInterface;
DISALLOW_COPY_AND_ASSIGN(DBusObject);
};
// Dispatches a D-Bus method call to the corresponding handler.
// Used mostly for testing purposes. This method is inlined so that it is
// not included in the shipping code of libchromeos, and included at the
// call sites.
inline std::unique_ptr<dbus::Response> CallMethod(
const DBusObject& object, dbus::MethodCall* method_call) {
DBusInterfaceMethodHandler* handler = object.FindMethodHandler(
method_call->GetInterface(), method_call->GetMember());
std::unique_ptr<dbus::Response> response;
if (!handler) {
response = CreateDBusErrorResponse(
method_call,
DBUS_ERROR_UNKNOWN_METHOD,
"Unknown method");
} else {
response = handler->HandleMethod(method_call);
}
return response;
}
} // namespace dbus_utils
} // namespace chromeos
#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_OBJECT_H_