// 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 LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ #define LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 response; } // Same as CallMethodAndBlockWithTimeout() but uses a default timeout value. template 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 file descriptor types, 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 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 base::ScopedFD HackMove(const base::ScopedFD& val) { return std::move(const_cast(val)); } inline FileDescriptor HackMove(const FileDescriptor& val) { return std::move(const_cast(val)); } } // 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 inline bool ExtractMessageParametersAsTuple( ::dbus::MessageReader* reader, ErrorPtr* error, std::tuple* val_tuple) { auto callback = [val_tuple](const ResultTypes&... params) { *val_tuple = std::forward_as_tuple(params...); }; return DBusParamReader::Invoke( callback, reader, error); } // Overload of ExtractMessageParametersAsTuple to handle reference types in // tuples created with std::tie(). template inline bool ExtractMessageParametersAsTuple( ::dbus::MessageReader* reader, ErrorPtr* error, std::tuple* ref_tuple) { auto callback = [ref_tuple](const ResultTypes&... params) { *ref_tuple = std::forward_as_tuple(params...); }; return DBusParamReader::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 inline bool ExtractMessageParameters(::dbus::MessageReader* reader, ErrorPtr* error, ResultTypes*... results) { auto ref_tuple = std::tie(*results...); return ExtractMessageParametersAsTuple( 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 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; // 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 void TranslateSuccessResponse( const base::Callback& 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::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 inline void CallMethodWithTimeout( int timeout_ms, ::dbus::ObjectProxy* object, const std::string& interface_name, const std::string& method_name, const base::Callback& 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, success_callback, error_callback); object->CallMethodWithErrorCallback(&method_call, timeout_ms, std::move(dbus_success_callback), std::move(dbus_error_callback)); } // Same as CallMethodWithTimeout() but uses a default timeout value. template inline void CallMethod(::dbus::ObjectProxy* object, const std::string& interface_name, const std::string& method_name, const base::Callback& 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 // LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_