1// Copyright 2014 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// This file provides a way to call D-Bus methods on objects in remote processes 6// as if they were native C++ function calls. 7 8// CallMethodAndBlock (along with CallMethodAndBlockWithTimeout) lets you call 9// a D-Bus method synchronously and pass all the required parameters as C++ 10// function arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data 11// serialization implemented in brillo/dbus/data_serialization.h. 12// CallMethodAndBlock invokes the D-Bus method and returns the Response. 13 14// The method call response should be parsed with ExtractMethodCallResults(). 15// The method takes an optional list of pointers to the expected return values 16// of the D-Bus method. 17 18// CallMethod and CallMethodWithTimeout are similar to CallMethodAndBlock but 19// make the calls asynchronously. They take two callbacks: one for successful 20// method invocation and the second is for error conditions. 21 22// Here is an example of synchronous calls: 23// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: 24 25// using brillo::dbus_utils::CallMethodAndBlock; 26// using brillo::dbus_utils::ExtractMethodCallResults; 27// 28// brillo::ErrorPtr error; 29// auto resp = CallMethodAndBlock(obj, 30// "org.chromium.MyService.MyInterface", 31// "MyMethod", 32// &error, 33// 2, 8.7); 34// std::string return_value; 35// if (resp && ExtractMethodCallResults(resp.get(), &error, &return_value)) { 36// // Use the |return_value|. 37// } else { 38// // An error occurred. Use |error| to get details. 39// } 40 41// And here is how to call D-Bus methods asynchronously: 42// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: 43 44// using brillo::dbus_utils::CallMethod; 45// using brillo::dbus_utils::ExtractMethodCallResults; 46// 47// void OnSuccess(const std::string& return_value) { 48// // Use the |return_value|. 49// } 50// 51// void OnError(brillo::Error* error) { 52// // An error occurred. Use |error| to get details. 53// } 54// 55// brillo::dbus_utils::CallMethod(obj, 56// "org.chromium.MyService.MyInterface", 57// "MyMethod", 58// base::Bind(OnSuccess), 59// base::Bind(OnError), 60// 2, 8.7); 61 62#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ 63#define LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ 64 65#include <memory> 66#include <string> 67#include <tuple> 68 69#include <base/bind.h> 70#include <brillo/dbus/dbus_param_reader.h> 71#include <brillo/dbus/dbus_param_writer.h> 72#include <brillo/dbus/utils.h> 73#include <brillo/errors/error.h> 74#include <brillo/errors/error_codes.h> 75#include <brillo/brillo_export.h> 76#include <dbus/file_descriptor.h> 77#include <dbus/message.h> 78#include <dbus/object_proxy.h> 79 80namespace brillo { 81namespace dbus_utils { 82 83// A helper method to dispatch a blocking D-Bus method call. Can specify 84// zero or more method call arguments in |args| which will be sent over D-Bus. 85// This method sends a D-Bus message and blocks for a time period specified 86// in |timeout_ms| while waiting for a reply. The time out is in milliseconds or 87// -1 (DBUS_TIMEOUT_USE_DEFAULT) for default, or DBUS_TIMEOUT_INFINITE for no 88// timeout. If a timeout occurs, the response object contains an error object 89// with DBUS_ERROR_NO_REPLY error code (those constants come from libdbus 90// [dbus/dbus.h]). 91// Returns a dbus::Response object on success. On failure, returns nullptr and 92// fills in additional error details into the |error| object. 93template<typename... Args> 94inline std::unique_ptr<dbus::Response> CallMethodAndBlockWithTimeout( 95 int timeout_ms, 96 dbus::ObjectProxy* object, 97 const std::string& interface_name, 98 const std::string& method_name, 99 ErrorPtr* error, 100 const Args&... args) { 101 dbus::MethodCall method_call(interface_name, method_name); 102 // Add method arguments to the message buffer. 103 dbus::MessageWriter writer(&method_call); 104 DBusParamWriter::Append(&writer, args...); 105 dbus::ScopedDBusError dbus_error; 106 auto response = object->CallMethodAndBlockWithErrorDetails( 107 &method_call, timeout_ms, &dbus_error); 108 if (!response) { 109 if (dbus_error.is_set()) { 110 Error::AddTo(error, 111 FROM_HERE, 112 errors::dbus::kDomain, 113 dbus_error.name(), 114 dbus_error.message()); 115 } else { 116 Error::AddToPrintf(error, 117 FROM_HERE, 118 errors::dbus::kDomain, 119 DBUS_ERROR_FAILED, 120 "Failed to call D-Bus method: %s.%s", 121 interface_name.c_str(), 122 method_name.c_str()); 123 } 124 } 125 return std::unique_ptr<dbus::Response>(response.release()); 126} 127 128// Same as CallMethodAndBlockWithTimeout() but uses a default timeout value. 129template<typename... Args> 130inline std::unique_ptr<dbus::Response> CallMethodAndBlock( 131 dbus::ObjectProxy* object, 132 const std::string& interface_name, 133 const std::string& method_name, 134 ErrorPtr* error, 135 const Args&... args) { 136 return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, 137 object, 138 interface_name, 139 method_name, 140 error, 141 args...); 142} 143 144namespace internal { 145// In order to support non-copyable dbus::FileDescriptor, we have this 146// internal::HackMove() helper function that does really nothing for normal 147// types but uses Pass() for file descriptors so we can move them out from 148// the temporaries created inside DBusParamReader<...>::Invoke(). 149// If only libchrome supported real rvalues so we can just do std::move() and 150// be done with it. 151template <typename T> 152inline const T& HackMove(const T& val) { 153 return val; 154} 155 156// Even though |val| here is passed as const&, the actual value is created 157// inside DBusParamReader<...>::Invoke() and is temporary in nature, so it is 158// safe to move the file descriptor out of |val|. That's why we are doing 159// const_cast here. It is a bit hacky, but there is no negative side effects. 160inline dbus::FileDescriptor HackMove(const dbus::FileDescriptor& val) { 161 return std::move(const_cast<dbus::FileDescriptor&>(val)); 162} 163} // namespace internal 164 165// Extracts the parameters of |ResultTypes...| types from the message reader 166// and puts the values in the resulting |tuple|. Returns false on error and 167// provides additional error details in |error| object. 168template<typename... ResultTypes> 169inline bool ExtractMessageParametersAsTuple( 170 dbus::MessageReader* reader, 171 ErrorPtr* error, 172 std::tuple<ResultTypes...>* val_tuple) { 173 auto callback = [val_tuple](const ResultTypes&... params) { 174 *val_tuple = std::forward_as_tuple(internal::HackMove(params)...); 175 }; 176 return DBusParamReader<false, ResultTypes...>::Invoke( 177 callback, reader, error); 178} 179// Overload of ExtractMessageParametersAsTuple to handle reference types in 180// tuples created with std::tie(). 181template<typename... ResultTypes> 182inline bool ExtractMessageParametersAsTuple( 183 dbus::MessageReader* reader, 184 ErrorPtr* error, 185 std::tuple<ResultTypes&...>* ref_tuple) { 186 auto callback = [ref_tuple](const ResultTypes&... params) { 187 *ref_tuple = std::forward_as_tuple(internal::HackMove(params)...); 188 }; 189 return DBusParamReader<false, ResultTypes...>::Invoke( 190 callback, reader, error); 191} 192 193// A helper method to extract a list of values from a message buffer. 194// The function will return false and provide detailed error information on 195// failure. It fails if the D-Bus message buffer (represented by the |reader|) 196// contains too many, too few parameters or the parameters are of wrong types 197// (signatures). 198// The usage pattern is as follows: 199// 200// int32_t data1; 201// std::string data2; 202// ErrorPtr error; 203// if (ExtractMessageParameters(reader, &error, &data1, &data2)) { ... } 204// 205// The above example extracts an Int32 and a String from D-Bus message buffer. 206template<typename... ResultTypes> 207inline bool ExtractMessageParameters(dbus::MessageReader* reader, 208 ErrorPtr* error, 209 ResultTypes*... results) { 210 auto ref_tuple = std::tie(*results...); 211 return ExtractMessageParametersAsTuple<ResultTypes...>( 212 reader, error, &ref_tuple); 213} 214 215// Convenient helper method to extract return value(s) of a D-Bus method call. 216// |results| must be zero or more pointers to data expected to be returned 217// from the method called. If an error occurs, returns false and provides 218// additional details in |error| object. 219// 220// It is OK to call this method even if the D-Bus method doesn't expect 221// any return values. Just do not specify any output |results|. In this case, 222// ExtractMethodCallResults() will verify that the method didn't return any 223// data in the |message|. 224template<typename... ResultTypes> 225inline bool ExtractMethodCallResults(dbus::Message* message, 226 ErrorPtr* error, 227 ResultTypes*... results) { 228 CHECK(message) << "Unable to extract parameters from a NULL message."; 229 230 dbus::MessageReader reader(message); 231 if (message->GetMessageType() == dbus::Message::MESSAGE_ERROR) { 232 std::string error_message; 233 if (ExtractMessageParameters(&reader, error, &error_message)) 234 AddDBusError(error, message->GetErrorName(), error_message); 235 return false; 236 } 237 return ExtractMessageParameters(&reader, error, results...); 238} 239 240////////////////////////////////////////////////////////////////////////////// 241// Asynchronous method invocation support 242 243using AsyncErrorCallback = base::Callback<void(Error* error)>; 244 245// A helper function that translates dbus::ErrorResponse response 246// from D-Bus into brillo::Error* and invokes the |callback|. 247void BRILLO_EXPORT TranslateErrorResponse(const AsyncErrorCallback& callback, 248 dbus::ErrorResponse* resp); 249 250// A helper function that translates dbus::Response from D-Bus into 251// a list of C++ values passed as parameters to |success_callback|. If the 252// response message doesn't have the correct number of parameters, or they 253// are of wrong types, an error is sent to |error_callback|. 254template<typename... OutArgs> 255void TranslateSuccessResponse( 256 const base::Callback<void(OutArgs...)>& success_callback, 257 const AsyncErrorCallback& error_callback, 258 dbus::Response* resp) { 259 auto callback = [&success_callback](const OutArgs&... params) { 260 if (!success_callback.is_null()) { 261 success_callback.Run(params...); 262 } 263 }; 264 ErrorPtr error; 265 dbus::MessageReader reader(resp); 266 if (!DBusParamReader<false, OutArgs...>::Invoke(callback, &reader, &error) && 267 !error_callback.is_null()) { 268 error_callback.Run(error.get()); 269 } 270} 271 272// A helper method to dispatch a non-blocking D-Bus method call. Can specify 273// zero or more method call arguments in |params| which will be sent over D-Bus. 274// This method sends a D-Bus message and returns immediately. 275// When the remote method returns successfully, the success callback is 276// invoked with the return value(s), if any. 277// On error, the error callback is called. Note, the error callback can be 278// called synchronously (before CallMethodWithTimeout returns) if there was 279// a problem invoking a method (e.g. object or method doesn't exist). 280// If the response is not received within |timeout_ms|, an error callback is 281// called with DBUS_ERROR_NO_REPLY error code. 282template<typename... InArgs, typename... OutArgs> 283inline void CallMethodWithTimeout( 284 int timeout_ms, 285 dbus::ObjectProxy* object, 286 const std::string& interface_name, 287 const std::string& method_name, 288 const base::Callback<void(OutArgs...)>& success_callback, 289 const AsyncErrorCallback& error_callback, 290 const InArgs&... params) { 291 dbus::MethodCall method_call(interface_name, method_name); 292 dbus::MessageWriter writer(&method_call); 293 DBusParamWriter::Append(&writer, params...); 294 295 dbus::ObjectProxy::ErrorCallback dbus_error_callback = 296 base::Bind(&TranslateErrorResponse, error_callback); 297 dbus::ObjectProxy::ResponseCallback dbus_success_callback = base::Bind( 298 &TranslateSuccessResponse<OutArgs...>, success_callback, error_callback); 299 300 object->CallMethodWithErrorCallback( 301 &method_call, timeout_ms, dbus_success_callback, dbus_error_callback); 302} 303 304// Same as CallMethodWithTimeout() but uses a default timeout value. 305template<typename... InArgs, typename... OutArgs> 306inline void CallMethod(dbus::ObjectProxy* object, 307 const std::string& interface_name, 308 const std::string& method_name, 309 const base::Callback<void(OutArgs...)>& success_callback, 310 const AsyncErrorCallback& error_callback, 311 const InArgs&... params) { 312 return CallMethodWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, 313 object, 314 interface_name, 315 method_name, 316 success_callback, 317 error_callback, 318 params...); 319} 320 321} // namespace dbus_utils 322} // namespace brillo 323 324#endif // LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_ 325