| // 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_ |