summaryrefslogtreecommitdiff
path: root/brillo/dbus/dbus_method_invoker.h
diff options
context:
space:
mode:
Diffstat (limited to 'brillo/dbus/dbus_method_invoker.h')
-rw-r--r--brillo/dbus/dbus_method_invoker.h324
1 files changed, 324 insertions, 0 deletions
diff --git a/brillo/dbus/dbus_method_invoker.h b/brillo/dbus/dbus_method_invoker.h
new file mode 100644
index 0000000..df8c3c5
--- /dev/null
+++ b/brillo/dbus/dbus_method_invoker.h
@@ -0,0 +1,324 @@
+// 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.
+
+// This file provides a way to call D-Bus methods on objects in remote processes
+// as if they were native C++ function calls.
+
+// CallMethodAndBlock (along with CallMethodAndBlockWithTimeout) lets you call
+// a D-Bus method synchronously and pass all the required parameters as C++
+// function arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data
+// serialization implemented in brillo/dbus/data_serialization.h.
+// CallMethodAndBlock invokes the D-Bus method and returns the Response.
+
+// The method call response should be parsed with ExtractMethodCallResults().
+// The method takes an optional list of pointers to the expected return values
+// of the D-Bus method.
+
+// CallMethod and CallMethodWithTimeout are similar to CallMethodAndBlock but
+// make the calls asynchronously. They take two callbacks: one for successful
+// method invocation and the second is for error conditions.
+
+// Here is an example of synchronous calls:
+// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus:
+
+// using brillo::dbus_utils::CallMethodAndBlock;
+// using brillo::dbus_utils::ExtractMethodCallResults;
+//
+// brillo::ErrorPtr error;
+// auto resp = CallMethodAndBlock(obj,
+// "org.chromium.MyService.MyInterface",
+// "MyMethod",
+// &error,
+// 2, 8.7);
+// std::string return_value;
+// if (resp && ExtractMethodCallResults(resp.get(), &error, &return_value)) {
+// // Use the |return_value|.
+// } else {
+// // An error occurred. Use |error| to get details.
+// }
+
+// And here is how to call D-Bus methods asynchronously:
+// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus:
+
+// using brillo::dbus_utils::CallMethod;
+// using brillo::dbus_utils::ExtractMethodCallResults;
+//
+// void OnSuccess(const std::string& return_value) {
+// // Use the |return_value|.
+// }
+//
+// void OnError(brillo::Error* error) {
+// // An error occurred. Use |error| to get details.
+// }
+//
+// brillo::dbus_utils::CallMethod(obj,
+// "org.chromium.MyService.MyInterface",
+// "MyMethod",
+// base::Bind(OnSuccess),
+// base::Bind(OnError),
+// 2, 8.7);
+
+#ifndef LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_
+#define LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include <base/bind.h>
+#include <brillo/dbus/dbus_param_reader.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <brillo/dbus/utils.h>
+#include <brillo/errors/error.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/brillo_export.h>
+#include <dbus/file_descriptor.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// A helper method to dispatch a blocking D-Bus method call. Can specify
+// zero or more method call arguments in |args| which will be sent over D-Bus.
+// This method sends a D-Bus message and blocks for a time period specified
+// in |timeout_ms| while waiting for a reply. The time out is in milliseconds or
+// -1 (DBUS_TIMEOUT_USE_DEFAULT) for default, or DBUS_TIMEOUT_INFINITE for no
+// timeout. If a timeout occurs, the response object contains an error object
+// with DBUS_ERROR_NO_REPLY error code (those constants come from libdbus
+// [dbus/dbus.h]).
+// Returns a dbus::Response object on success. On failure, returns nullptr and
+// fills in additional error details into the |error| object.
+template<typename... Args>
+inline std::unique_ptr<dbus::Response> CallMethodAndBlockWithTimeout(
+ int timeout_ms,
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ ErrorPtr* error,
+ const Args&... args) {
+ dbus::MethodCall method_call(interface_name, method_name);
+ // Add method arguments to the message buffer.
+ dbus::MessageWriter writer(&method_call);
+ DBusParamWriter::Append(&writer, args...);
+ dbus::ScopedDBusError dbus_error;
+ auto response = object->CallMethodAndBlockWithErrorDetails(
+ &method_call, timeout_ms, &dbus_error);
+ if (!response) {
+ if (dbus_error.is_set()) {
+ Error::AddTo(error,
+ FROM_HERE,
+ errors::dbus::kDomain,
+ dbus_error.name(),
+ dbus_error.message());
+ } else {
+ Error::AddToPrintf(error,
+ FROM_HERE,
+ errors::dbus::kDomain,
+ DBUS_ERROR_FAILED,
+ "Failed to call D-Bus method: %s.%s",
+ interface_name.c_str(),
+ method_name.c_str());
+ }
+ }
+ return std::unique_ptr<dbus::Response>(response.release());
+}
+
+// Same as CallMethodAndBlockWithTimeout() but uses a default timeout value.
+template<typename... Args>
+inline std::unique_ptr<dbus::Response> CallMethodAndBlock(
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ ErrorPtr* error,
+ const Args&... args) {
+ return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ object,
+ interface_name,
+ method_name,
+ error,
+ args...);
+}
+
+namespace internal {
+// In order to support non-copyable dbus::FileDescriptor, we have this
+// internal::HackMove() helper function that does really nothing for normal
+// types but uses Pass() for file descriptors so we can move them out from
+// the temporaries created inside DBusParamReader<...>::Invoke().
+// If only libchrome supported real rvalues so we can just do std::move() and
+// be done with it.
+template <typename T>
+inline const T& HackMove(const T& val) {
+ return val;
+}
+
+// Even though |val| here is passed as const&, the actual value is created
+// inside DBusParamReader<...>::Invoke() and is temporary in nature, so it is
+// safe to move the file descriptor out of |val|. That's why we are doing
+// const_cast here. It is a bit hacky, but there is no negative side effects.
+inline dbus::FileDescriptor HackMove(const dbus::FileDescriptor& val) {
+ return const_cast<dbus::FileDescriptor&>(val).Pass();
+}
+} // namespace internal
+
+// Extracts the parameters of |ResultTypes...| types from the message reader
+// and puts the values in the resulting |tuple|. Returns false on error and
+// provides additional error details in |error| object.
+template<typename... ResultTypes>
+inline bool ExtractMessageParametersAsTuple(
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ std::tuple<ResultTypes...>* val_tuple) {
+ auto callback = [val_tuple](const ResultTypes&... params) {
+ *val_tuple = std::forward_as_tuple(internal::HackMove(params)...);
+ };
+ return DBusParamReader<false, ResultTypes...>::Invoke(
+ callback, reader, error);
+}
+// Overload of ExtractMessageParametersAsTuple to handle reference types in
+// tuples created with std::tie().
+template<typename... ResultTypes>
+inline bool ExtractMessageParametersAsTuple(
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ std::tuple<ResultTypes&...>* ref_tuple) {
+ auto callback = [ref_tuple](const ResultTypes&... params) {
+ *ref_tuple = std::forward_as_tuple(internal::HackMove(params)...);
+ };
+ return DBusParamReader<false, ResultTypes...>::Invoke(
+ callback, reader, error);
+}
+
+// A helper method to extract a list of values from a message buffer.
+// The function will return false and provide detailed error information on
+// failure. It fails if the D-Bus message buffer (represented by the |reader|)
+// contains too many, too few parameters or the parameters are of wrong types
+// (signatures).
+// The usage pattern is as follows:
+//
+// int32_t data1;
+// std::string data2;
+// ErrorPtr error;
+// if (ExtractMessageParameters(reader, &error, &data1, &data2)) { ... }
+//
+// The above example extracts an Int32 and a String from D-Bus message buffer.
+template<typename... ResultTypes>
+inline bool ExtractMessageParameters(dbus::MessageReader* reader,
+ ErrorPtr* error,
+ ResultTypes*... results) {
+ auto ref_tuple = std::tie(*results...);
+ return ExtractMessageParametersAsTuple<ResultTypes...>(
+ reader, error, &ref_tuple);
+}
+
+// Convenient helper method to extract return value(s) of a D-Bus method call.
+// |results| must be zero or more pointers to data expected to be returned
+// from the method called. If an error occurs, returns false and provides
+// additional details in |error| object.
+//
+// It is OK to call this method even if the D-Bus method doesn't expect
+// any return values. Just do not specify any output |results|. In this case,
+// ExtractMethodCallResults() will verify that the method didn't return any
+// data in the |message|.
+template<typename... ResultTypes>
+inline bool ExtractMethodCallResults(dbus::Message* message,
+ ErrorPtr* error,
+ ResultTypes*... results) {
+ CHECK(message) << "Unable to extract parameters from a NULL message.";
+
+ dbus::MessageReader reader(message);
+ if (message->GetMessageType() == dbus::Message::MESSAGE_ERROR) {
+ std::string error_message;
+ if (ExtractMessageParameters(&reader, error, &error_message))
+ AddDBusError(error, message->GetErrorName(), error_message);
+ return false;
+ }
+ return ExtractMessageParameters(&reader, error, results...);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Asynchronous method invocation support
+
+using AsyncErrorCallback = base::Callback<void(Error* error)>;
+
+// A helper function that translates dbus::ErrorResponse response
+// from D-Bus into brillo::Error* and invokes the |callback|.
+void BRILLO_EXPORT TranslateErrorResponse(const AsyncErrorCallback& callback,
+ dbus::ErrorResponse* resp);
+
+// A helper function that translates dbus::Response from D-Bus into
+// a list of C++ values passed as parameters to |success_callback|. If the
+// response message doesn't have the correct number of parameters, or they
+// are of wrong types, an error is sent to |error_callback|.
+template<typename... OutArgs>
+void TranslateSuccessResponse(
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ dbus::Response* resp) {
+ auto callback = [&success_callback](const OutArgs&... params) {
+ if (!success_callback.is_null()) {
+ success_callback.Run(params...);
+ }
+ };
+ ErrorPtr error;
+ dbus::MessageReader reader(resp);
+ if (!DBusParamReader<false, OutArgs...>::Invoke(callback, &reader, &error) &&
+ !error_callback.is_null()) {
+ error_callback.Run(error.get());
+ }
+}
+
+// A helper method to dispatch a non-blocking D-Bus method call. Can specify
+// zero or more method call arguments in |params| which will be sent over D-Bus.
+// This method sends a D-Bus message and returns immediately.
+// When the remote method returns successfully, the success callback is
+// invoked with the return value(s), if any.
+// On error, the error callback is called. Note, the error callback can be
+// called synchronously (before CallMethodWithTimeout returns) if there was
+// a problem invoking a method (e.g. object or method doesn't exist).
+// If the response is not received within |timeout_ms|, an error callback is
+// called with DBUS_ERROR_NO_REPLY error code.
+template<typename... InArgs, typename... OutArgs>
+inline void CallMethodWithTimeout(
+ int timeout_ms,
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ const InArgs&... params) {
+ dbus::MethodCall method_call(interface_name, method_name);
+ dbus::MessageWriter writer(&method_call);
+ DBusParamWriter::Append(&writer, params...);
+
+ dbus::ObjectProxy::ErrorCallback dbus_error_callback =
+ base::Bind(&TranslateErrorResponse, error_callback);
+ dbus::ObjectProxy::ResponseCallback dbus_success_callback = base::Bind(
+ &TranslateSuccessResponse<OutArgs...>, success_callback, error_callback);
+
+ object->CallMethodWithErrorCallback(
+ &method_call, timeout_ms, dbus_success_callback, dbus_error_callback);
+}
+
+// Same as CallMethodWithTimeout() but uses a default timeout value.
+template<typename... InArgs, typename... OutArgs>
+inline void CallMethod(dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ const InArgs&... params) {
+ return CallMethodWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ object,
+ interface_name,
+ method_name,
+ success_callback,
+ error_callback,
+ params...);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBCHROMEOS_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_