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