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