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