| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "bluetooth/common/dbus_client.h" |
| |
| #include <memory> |
| |
| #include <base/location.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/task_runner.h> |
| #include <dbus/message.h> |
| #include <dbus/scoped_dbus_error.h> |
| |
| namespace { |
| |
| // D-Bus constants for subscribing to NameOwnerChanged signals. |
| constexpr char kDBusSystemObjectPath[] = "/org/freedesktop/DBus"; |
| constexpr char kDBusSystemObjectInterface[] = "org.freedesktop.DBus"; |
| constexpr char kDBusSystemObjectAddress[] = "org.freedesktop.DBus"; |
| constexpr char kNameOwnerChangedMember[] = "NameOwnerChanged"; |
| |
| } // namespace |
| |
| namespace bluetooth { |
| |
| DBusClient::DBusClient(const scoped_refptr<dbus::Bus>& bus, |
| const std::string& client_address) |
| : bus_(bus), client_address_(client_address), weak_ptr_factory_(this) {} |
| |
| DBusClient::~DBusClient() { |
| // Clean up the match rule that has been added before. |
| if (!client_availability_match_rule_.empty()) { |
| dbus::ScopedDBusError error; |
| bus_->RemoveMatch(client_availability_match_rule_, error.get()); |
| if (error.is_set()) |
| LOG(ERROR) << "Failed to remove match rule \"" |
| << client_availability_match_rule_ << "\". Got " |
| << error.name() << ": " << error.message(); |
| client_availability_match_rule_.clear(); |
| } |
| |
| // Stop listening for any messages from D-Bus daemon. |
| if (!client_unavailable_callback_.is_null()) |
| bus_->RemoveFilterFunction(&DBusClient::HandleMessageThunk, this); |
| } |
| |
| void DBusClient::WatchClientUnavailable( |
| const base::Closure& client_unavailable_callback) { |
| CHECK(client_unavailable_callback_.is_null()) |
| << "Client watch has been added before"; |
| |
| client_unavailable_callback_ = client_unavailable_callback; |
| |
| // Listen to messages from D-Bus daemon. |
| bus_->AddFilterFunction(&DBusClient::HandleMessageThunk, this); |
| |
| // We are only interested in the signal about the client becoming unavailable. |
| // Add a filter here to be notified only for signals about the client's |
| // NameOwnerChanged events. |
| client_availability_match_rule_ = base::StringPrintf( |
| "type='signal',interface='%s',member='%s',path='%s',sender='%s'," |
| "arg0='%s'", |
| kDBusSystemObjectInterface, kNameOwnerChangedMember, |
| kDBusSystemObjectPath, kDBusSystemObjectAddress, client_address_.c_str()); |
| dbus::ScopedDBusError error; |
| bus_->AddMatch(client_availability_match_rule_, error.get()); |
| if (error.is_set()) |
| LOG(ERROR) << "Failed to add match rule \"" |
| << client_availability_match_rule_ << "\". Got " << error.name() |
| << ": " << error.message(); |
| } |
| |
| DBusHandlerResult DBusClient::HandleMessageThunk(DBusConnection* connection, |
| DBusMessage* raw_message, |
| void* user_data) { |
| DBusClient* self = reinterpret_cast<DBusClient*>(user_data); |
| CHECK(self); |
| return self->HandleMessage(connection, raw_message); |
| } |
| |
| DBusHandlerResult DBusClient::HandleMessage(DBusConnection* connection, |
| DBusMessage* raw_message) { |
| if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| // raw_message will be unrefed on exit of the function. Increment the |
| // reference so we can use it in |signal|. |
| // TODO(https://crbug.com/911903): libchrome should provide an API so we can |
| // avoid using direct libdbus call dbus_message_ref(). |
| dbus_message_ref(raw_message); |
| std::unique_ptr<dbus::Signal> signal( |
| dbus::Signal::FromRawMessage(raw_message)); |
| |
| // Confirm the validity of the NameOwnerChanged signal. |
| if (signal->GetPath().value() == kDBusSystemObjectPath && |
| signal->GetMember() == kNameOwnerChangedMember && |
| signal->GetInterface() == kDBusSystemObjectInterface && |
| signal->GetSender() == kDBusSystemObjectAddress) { |
| dbus::MessageReader reader(signal.get()); |
| std::string address, old_owner, new_owner; |
| // A client is considered disconnected if |new_owner| is empty. |
| if (reader.PopString(&address) && reader.PopString(&old_owner) && |
| reader.PopString(&new_owner) && address == client_address_ && |
| old_owner == client_address_ && new_owner.empty()) |
| bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, |
| client_unavailable_callback_); |
| } |
| |
| // Always return unhandled to let other handlers handle the same signal. |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| } // namespace bluetooth |