1// Copyright (c) 2012 The Chromium 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#include "dbus/exported_object.h"
6
7#include <stdint.h>
8#include <utility>
9
10#include "base/bind.h"
11#include "base/logging.h"
12#include "base/memory/ref_counted.h"
13#include "base/message_loop/message_loop.h"
14#include "base/metrics/histogram.h"
15#include "base/threading/thread_restrictions.h"
16#include "base/time/time.h"
17#include "dbus/bus.h"
18#include "dbus/message.h"
19#include "dbus/object_path.h"
20#include "dbus/scoped_dbus_error.h"
21#include "dbus/util.h"
22
23namespace dbus {
24
25namespace {
26
27// Used for success ratio histograms. 1 for success, 0 for failure.
28const int kSuccessRatioHistogramMaxValue = 2;
29
30}  // namespace
31
32ExportedObject::ExportedObject(Bus* bus,
33                               const ObjectPath& object_path)
34    : bus_(bus),
35      object_path_(object_path),
36      object_is_registered_(false) {
37}
38
39ExportedObject::~ExportedObject() {
40  DCHECK(!object_is_registered_);
41}
42
43bool ExportedObject::ExportMethodAndBlock(
44    const std::string& interface_name,
45    const std::string& method_name,
46    MethodCallCallback method_call_callback) {
47  bus_->AssertOnDBusThread();
48
49  // Check if the method is already exported.
50  const std::string absolute_method_name =
51      GetAbsoluteMemberName(interface_name, method_name);
52  if (method_table_.find(absolute_method_name) != method_table_.end()) {
53    LOG(ERROR) << absolute_method_name << " is already exported";
54    return false;
55  }
56
57  if (!bus_->Connect())
58    return false;
59  if (!bus_->SetUpAsyncOperations())
60    return false;
61  if (!Register())
62    return false;
63
64  // Add the method callback to the method table.
65  method_table_[absolute_method_name] = method_call_callback;
66
67  return true;
68}
69
70void ExportedObject::ExportMethod(const std::string& interface_name,
71                                  const std::string& method_name,
72                                  MethodCallCallback method_call_callback,
73                                  OnExportedCallback on_exported_calback) {
74  bus_->AssertOnOriginThread();
75
76  base::Closure task = base::Bind(&ExportedObject::ExportMethodInternal,
77                                  this,
78                                  interface_name,
79                                  method_name,
80                                  method_call_callback,
81                                  on_exported_calback);
82  bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
83}
84
85void ExportedObject::SendSignal(Signal* signal) {
86  // For signals, the object path should be set to the path to the sender
87  // object, which is this exported object here.
88  CHECK(signal->SetPath(object_path_));
89
90  // Increment the reference count so we can safely reference the
91  // underlying signal message until the signal sending is complete. This
92  // will be unref'ed in SendSignalInternal().
93  DBusMessage* signal_message = signal->raw_message();
94  dbus_message_ref(signal_message);
95
96  const base::TimeTicks start_time = base::TimeTicks::Now();
97  if (bus_->GetDBusTaskRunner()->RunsTasksOnCurrentThread()) {
98    // The Chrome OS power manager doesn't use a dedicated TaskRunner for
99    // sending DBus messages.  Sending signals asynchronously can cause an
100    // inversion in the message order if the power manager calls
101    // ObjectProxy::CallMethodAndBlock() before going back to the top level of
102    // the MessageLoop: crbug.com/472361.
103    SendSignalInternal(start_time, signal_message);
104  } else {
105    bus_->GetDBusTaskRunner()->PostTask(
106        FROM_HERE,
107        base::Bind(&ExportedObject::SendSignalInternal,
108                   this,
109                   start_time,
110                   signal_message));
111  }
112}
113
114void ExportedObject::Unregister() {
115  bus_->AssertOnDBusThread();
116
117  if (!object_is_registered_)
118    return;
119
120  bus_->UnregisterObjectPath(object_path_);
121  object_is_registered_ = false;
122}
123
124void ExportedObject::ExportMethodInternal(
125    const std::string& interface_name,
126    const std::string& method_name,
127    MethodCallCallback method_call_callback,
128    OnExportedCallback on_exported_calback) {
129  bus_->AssertOnDBusThread();
130
131  const bool success = ExportMethodAndBlock(interface_name,
132                                            method_name,
133                                            method_call_callback);
134  bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
135                                        base::Bind(&ExportedObject::OnExported,
136                                                   this,
137                                                   on_exported_calback,
138                                                   interface_name,
139                                                   method_name,
140                                                   success));
141}
142
143void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
144                                const std::string& interface_name,
145                                const std::string& method_name,
146                                bool success) {
147  bus_->AssertOnOriginThread();
148
149  on_exported_callback.Run(interface_name, method_name, success);
150}
151
152void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
153                                        DBusMessage* signal_message) {
154  uint32_t serial = 0;
155  bus_->Send(signal_message, &serial);
156  dbus_message_unref(signal_message);
157  // Record time spent to send the the signal. This is not accurate as the
158  // signal will actually be sent from the next run of the message loop,
159  // but we can at least tell the number of signals sent.
160  UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
161                      base::TimeTicks::Now() - start_time);
162}
163
164bool ExportedObject::Register() {
165  bus_->AssertOnDBusThread();
166
167  if (object_is_registered_)
168    return true;
169
170  ScopedDBusError error;
171
172  DBusObjectPathVTable vtable;
173  memset(&vtable, 0, sizeof(vtable));
174  vtable.message_function = &ExportedObject::HandleMessageThunk;
175  vtable.unregister_function = &ExportedObject::OnUnregisteredThunk;
176  const bool success = bus_->TryRegisterObjectPath(object_path_,
177                                                   &vtable,
178                                                   this,
179                                                   error.get());
180  if (!success) {
181    LOG(ERROR) << "Failed to register the object: " << object_path_.value()
182               << ": " << (error.is_set() ? error.message() : "");
183    return false;
184  }
185
186  object_is_registered_ = true;
187  return true;
188}
189
190DBusHandlerResult ExportedObject::HandleMessage(
191    DBusConnection* /* connection */,
192    DBusMessage* raw_message) {
193  bus_->AssertOnDBusThread();
194  DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
195
196  // raw_message will be unrefed on exit of the function. Increment the
197  // reference so we can use it in MethodCall.
198  dbus_message_ref(raw_message);
199  scoped_ptr<MethodCall> method_call(
200      MethodCall::FromRawMessage(raw_message));
201  const std::string interface = method_call->GetInterface();
202  const std::string member = method_call->GetMember();
203
204  if (interface.empty()) {
205    // We don't support method calls without interface.
206    LOG(WARNING) << "Interface is missing: " << method_call->ToString();
207    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
208  }
209
210  // Check if we know about the method.
211  const std::string absolute_method_name = GetAbsoluteMemberName(
212      interface, member);
213  MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
214  if (iter == method_table_.end()) {
215    // Don't know about the method.
216    LOG(WARNING) << "Unknown method: " << method_call->ToString();
217    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
218  }
219
220  const base::TimeTicks start_time = base::TimeTicks::Now();
221  if (bus_->HasDBusThread()) {
222    // Post a task to run the method in the origin thread.
223    bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
224                                          base::Bind(&ExportedObject::RunMethod,
225                                                     this,
226                                                     iter->second,
227                                                     base::Passed(&method_call),
228                                                     start_time));
229  } else {
230    // If the D-Bus thread is not used, just call the method directly.
231    MethodCall* method = method_call.get();
232    iter->second.Run(method,
233                     base::Bind(&ExportedObject::SendResponse,
234                                this,
235                                start_time,
236                                base::Passed(&method_call)));
237  }
238
239  // It's valid to say HANDLED here, and send a method response at a later
240  // time from OnMethodCompleted() asynchronously.
241  return DBUS_HANDLER_RESULT_HANDLED;
242}
243
244void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
245                               scoped_ptr<MethodCall> method_call,
246                               base::TimeTicks start_time) {
247  bus_->AssertOnOriginThread();
248  MethodCall* method = method_call.get();
249  method_call_callback.Run(method,
250                           base::Bind(&ExportedObject::SendResponse,
251                                      this,
252                                      start_time,
253                                      base::Passed(&method_call)));
254}
255
256void ExportedObject::SendResponse(base::TimeTicks start_time,
257                                  scoped_ptr<MethodCall> method_call,
258                                  scoped_ptr<Response> response) {
259  DCHECK(method_call);
260  if (bus_->HasDBusThread()) {
261    bus_->GetDBusTaskRunner()->PostTask(
262        FROM_HERE,
263        base::Bind(&ExportedObject::OnMethodCompleted,
264                   this,
265                   base::Passed(&method_call),
266                   base::Passed(&response),
267                   start_time));
268  } else {
269    OnMethodCompleted(std::move(method_call), std::move(response), start_time);
270  }
271}
272
273void ExportedObject::OnMethodCompleted(scoped_ptr<MethodCall> method_call,
274                                       scoped_ptr<Response> response,
275                                       base::TimeTicks start_time) {
276  bus_->AssertOnDBusThread();
277
278  // Record if the method call is successful, or not. 1 if successful.
279  UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
280                            response ? 1 : 0,
281                            kSuccessRatioHistogramMaxValue);
282
283  // Check if the bus is still connected. If the method takes long to
284  // complete, the bus may be shut down meanwhile.
285  if (!bus_->is_connected())
286    return;
287
288  if (!response) {
289    // Something bad happened in the method call.
290    scoped_ptr<ErrorResponse> error_response(
291        ErrorResponse::FromMethodCall(
292            method_call.get(),
293            DBUS_ERROR_FAILED,
294            "error occurred in " + method_call->GetMember()));
295    bus_->Send(error_response->raw_message(), NULL);
296    return;
297  }
298
299  // The method call was successful.
300  bus_->Send(response->raw_message(), NULL);
301
302  // Record time spent to handle the the method call. Don't include failures.
303  UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
304                      base::TimeTicks::Now() - start_time);
305}
306
307void ExportedObject::OnUnregistered(DBusConnection* /* connection */) {
308}
309
310DBusHandlerResult ExportedObject::HandleMessageThunk(
311    DBusConnection* connection,
312    DBusMessage* raw_message,
313    void* user_data) {
314  ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
315  return self->HandleMessage(connection, raw_message);
316}
317
318void ExportedObject::OnUnregisteredThunk(DBusConnection *connection,
319                                         void* user_data) {
320  ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
321  return self->OnUnregistered(connection);
322}
323
324}  // namespace dbus
325