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