| // Copyright 2013 The Chromium 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 "device/bluetooth/bluez/bluetooth_socket_bluez.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <queue> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/linked_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_util.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "dbus/bus.h" |
| #include "dbus/object_path.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_device.h" |
| #include "device/bluetooth/bluetooth_socket.h" |
| #include "device/bluetooth/bluetooth_socket_net.h" |
| #include "device/bluetooth/bluetooth_socket_thread.h" |
| #include "device/bluetooth/bluez/bluetooth_adapter_bluez.h" |
| #include "device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h" |
| #include "device/bluetooth/bluez/bluetooth_device_bluez.h" |
| #include "device/bluetooth/dbus/bluetooth_device_client.h" |
| #include "device/bluetooth/dbus/bluetooth_profile_manager_client.h" |
| #include "device/bluetooth/dbus/bluetooth_profile_service_provider.h" |
| #include "device/bluetooth/dbus/bluez_dbus_manager.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| using device::BluetoothAdapter; |
| using device::BluetoothDevice; |
| using device::BluetoothSocketThread; |
| using device::BluetoothUUID; |
| |
| namespace { |
| |
| const char kAcceptFailed[] = "Failed to accept connection."; |
| const char kInvalidUUID[] = "Invalid UUID"; |
| const char kSocketNotListening[] = "Socket is not listening."; |
| |
| } // namespace |
| |
| namespace bluez { |
| |
| // static |
| scoped_refptr<BluetoothSocketBlueZ> BluetoothSocketBlueZ::CreateBluetoothSocket( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| scoped_refptr<BluetoothSocketThread> socket_thread) { |
| DCHECK(ui_task_runner->RunsTasksInCurrentSequence()); |
| |
| return base::WrapRefCounted( |
| new BluetoothSocketBlueZ(ui_task_runner, socket_thread)); |
| } |
| |
| BluetoothSocketBlueZ::AcceptRequest::AcceptRequest() = default; |
| |
| BluetoothSocketBlueZ::AcceptRequest::~AcceptRequest() = default; |
| |
| BluetoothSocketBlueZ::ConnectionRequest::ConnectionRequest() |
| : accepting(false), cancelled(false) {} |
| |
| BluetoothSocketBlueZ::ConnectionRequest::~ConnectionRequest() = default; |
| |
| BluetoothSocketBlueZ::BluetoothSocketBlueZ( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| scoped_refptr<BluetoothSocketThread> socket_thread) |
| : BluetoothSocketNet(ui_task_runner, socket_thread), profile_(nullptr) {} |
| |
| BluetoothSocketBlueZ::~BluetoothSocketBlueZ() { |
| DCHECK(!profile_); |
| |
| if (adapter_.get()) { |
| adapter_->RemoveObserver(this); |
| adapter_ = nullptr; |
| } |
| } |
| |
| void BluetoothSocketBlueZ::Connect( |
| const BluetoothDeviceBlueZ* device, |
| const BluetoothUUID& uuid, |
| SecurityLevel security_level, |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!profile_); |
| |
| if (!uuid.IsValid()) { |
| error_callback.Run(kInvalidUUID); |
| return; |
| } |
| |
| device_address_ = device->GetAddress(); |
| device_path_ = device->object_path(); |
| uuid_ = uuid; |
| options_.reset(new bluez::BluetoothProfileManagerClient::Options()); |
| if (security_level == SECURITY_LEVEL_LOW) |
| options_->require_authentication.reset(new bool(false)); |
| |
| adapter_ = device->adapter(); |
| |
| RegisterProfile(device->adapter(), success_callback, error_callback); |
| } |
| |
| void BluetoothSocketBlueZ::Listen( |
| scoped_refptr<BluetoothAdapter> adapter, |
| SocketType socket_type, |
| const BluetoothUUID& uuid, |
| const BluetoothAdapter::ServiceOptions& service_options, |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!profile_); |
| |
| if (!uuid.IsValid()) { |
| error_callback.Run(kInvalidUUID); |
| return; |
| } |
| |
| adapter_ = adapter; |
| adapter_->AddObserver(this); |
| |
| uuid_ = uuid; |
| options_.reset(new bluez::BluetoothProfileManagerClient::Options()); |
| if (service_options.name) |
| options_->name.reset(new std::string(*service_options.name)); |
| |
| switch (socket_type) { |
| case kRfcomm: |
| options_->channel.reset( |
| new uint16_t(service_options.channel ? *service_options.channel : 0)); |
| break; |
| case kL2cap: |
| options_->psm.reset( |
| new uint16_t(service_options.psm ? *service_options.psm : 0)); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| RegisterProfile(static_cast<BluetoothAdapterBlueZ*>(adapter.get()), |
| success_callback, error_callback); |
| } |
| |
| void BluetoothSocketBlueZ::Close() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| if (profile_) |
| UnregisterProfile(); |
| |
| // In the case below, where an asynchronous task gets posted on the socket |
| // thread in BluetoothSocketNet::Close, a reference will be held to this |
| // socket by the callback. This may cause the BluetoothAdapter to outlive |
| // BluezDBusManager during shutdown if that callback executes too late. |
| if (adapter_.get()) { |
| adapter_->RemoveObserver(this); |
| adapter_ = nullptr; |
| } |
| |
| if (!device_path_.value().empty()) { |
| BluetoothSocketNet::Close(); |
| } else { |
| DoCloseListening(); |
| } |
| } |
| |
| void BluetoothSocketBlueZ::Disconnect(const base::Closure& callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| if (profile_) |
| UnregisterProfile(); |
| |
| if (!device_path_.value().empty()) { |
| BluetoothSocketNet::Disconnect(callback); |
| } else { |
| DoCloseListening(); |
| callback.Run(); |
| } |
| } |
| |
| void BluetoothSocketBlueZ::Accept( |
| const AcceptCompletionCallback& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| if (!device_path_.value().empty()) { |
| error_callback.Run(kSocketNotListening); |
| return; |
| } |
| |
| // Only one pending accept at a time |
| if (accept_request_.get()) { |
| error_callback.Run(net::ErrorToString(net::ERR_IO_PENDING)); |
| return; |
| } |
| |
| accept_request_.reset(new AcceptRequest); |
| accept_request_->success_callback = success_callback; |
| accept_request_->error_callback = error_callback; |
| |
| if (connection_request_queue_.size() >= 1) { |
| AcceptConnectionRequest(); |
| } |
| } |
| |
| void BluetoothSocketBlueZ::RegisterProfile( |
| BluetoothAdapterBlueZ* adapter, |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!profile_); |
| DCHECK(adapter); |
| |
| // If the adapter is not present, this is a listening socket and the |
| // adapter isn't running yet. Report success and carry on; |
| // the profile will be registered when the daemon becomes available. |
| if (!adapter->IsPresent()) { |
| VLOG(1) << uuid_.canonical_value() << " on " << device_path_.value() |
| << ": Delaying profile registration."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_callback); |
| return; |
| } |
| |
| VLOG(1) << uuid_.canonical_value() << " on " << device_path_.value() |
| << ": Acquiring profile."; |
| |
| adapter->UseProfile(uuid_, device_path_, *options_, this, |
| base::Bind(&BluetoothSocketBlueZ::OnRegisterProfile, this, |
| success_callback, error_callback), |
| base::Bind(&BluetoothSocketBlueZ::OnRegisterProfileError, |
| this, error_callback)); |
| } |
| |
| void BluetoothSocketBlueZ::OnRegisterProfile( |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback, |
| BluetoothAdapterProfileBlueZ* profile) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!profile_); |
| |
| profile_ = profile; |
| |
| if (device_path_.value().empty()) { |
| VLOG(1) << uuid_.canonical_value() << ": Profile registered."; |
| success_callback.Run(); |
| return; |
| } |
| |
| VLOG(1) << uuid_.canonical_value() << ": Got profile, connecting to " |
| << device_path_.value(); |
| |
| bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->ConnectProfile( |
| device_path_, uuid_.canonical_value(), |
| base::Bind(&BluetoothSocketBlueZ::OnConnectProfile, this, |
| success_callback), |
| base::Bind(&BluetoothSocketBlueZ::OnConnectProfileError, this, |
| error_callback)); |
| } |
| |
| void BluetoothSocketBlueZ::OnRegisterProfileError( |
| const ErrorCompletionCallback& error_callback, |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| LOG(WARNING) << uuid_.canonical_value() |
| << ": Failed to register profile: " << error_message; |
| error_callback.Run(error_message); |
| } |
| |
| void BluetoothSocketBlueZ::OnConnectProfile( |
| const base::Closure& success_callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() << ": Profile connected."; |
| UnregisterProfile(); |
| success_callback.Run(); |
| } |
| |
| void BluetoothSocketBlueZ::OnConnectProfileError( |
| const ErrorCompletionCallback& error_callback, |
| const std::string& error_name, |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| LOG(WARNING) << profile_->object_path().value() |
| << ": Failed to connect profile: " << error_name << ": " |
| << error_message; |
| UnregisterProfile(); |
| error_callback.Run(error_message); |
| } |
| |
| void BluetoothSocketBlueZ::AdapterPresentChanged(BluetoothAdapter* adapter, |
| bool present) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| if (!present) { |
| // Adapter removed, we can't use the profile anymore. |
| UnregisterProfile(); |
| return; |
| } |
| |
| DCHECK(!profile_); |
| |
| VLOG(1) << uuid_.canonical_value() << " on " << device_path_.value() |
| << ": Acquiring profile."; |
| |
| static_cast<BluetoothAdapterBlueZ*>(adapter)->UseProfile( |
| uuid_, device_path_, *options_, this, |
| base::Bind(&BluetoothSocketBlueZ::OnInternalRegisterProfile, this), |
| base::Bind(&BluetoothSocketBlueZ::OnInternalRegisterProfileError, this)); |
| } |
| |
| void BluetoothSocketBlueZ::OnInternalRegisterProfile( |
| BluetoothAdapterProfileBlueZ* profile) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!profile_); |
| |
| profile_ = profile; |
| |
| VLOG(1) << uuid_.canonical_value() << ": Profile re-registered"; |
| } |
| |
| void BluetoothSocketBlueZ::OnInternalRegisterProfileError( |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| LOG(WARNING) << "Failed to re-register profile: " << error_message; |
| } |
| |
| void BluetoothSocketBlueZ::Released() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() << ": Release"; |
| } |
| |
| void BluetoothSocketBlueZ::NewConnection( |
| const dbus::ObjectPath& device_path, |
| base::ScopedFD fd, |
| const bluez::BluetoothProfileServiceProvider::Delegate::Options& options, |
| const ConfirmationCallback& callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| VLOG(1) << uuid_.canonical_value() |
| << ": New connection from device: " << device_path.value(); |
| |
| if (!device_path_.value().empty()) { |
| DCHECK(device_path_ == device_path); |
| |
| socket_thread()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BluetoothSocketBlueZ::DoNewConnection, this, device_path_, |
| base::Passed(&fd), options, callback)); |
| } else { |
| linked_ptr<ConnectionRequest> request(new ConnectionRequest()); |
| request->device_path = device_path; |
| request->fd = std::move(fd); |
| request->options = options; |
| request->callback = callback; |
| |
| connection_request_queue_.push(request); |
| VLOG(1) << uuid_.canonical_value() << ": Connection is now pending."; |
| if (accept_request_) { |
| AcceptConnectionRequest(); |
| } |
| } |
| } |
| |
| void BluetoothSocketBlueZ::RequestDisconnection( |
| const dbus::ObjectPath& device_path, |
| const ConfirmationCallback& callback) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() << ": Request disconnection"; |
| callback.Run(SUCCESS); |
| } |
| |
| void BluetoothSocketBlueZ::Cancel() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() << ": Cancel"; |
| |
| if (connection_request_queue_.empty()) |
| return; |
| |
| // If the front request is being accepted mark it as cancelled, otherwise |
| // just pop it from the queue. |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| if (!request->accepting) { |
| request->cancelled = true; |
| } else { |
| connection_request_queue_.pop(); |
| } |
| } |
| |
| void BluetoothSocketBlueZ::AcceptConnectionRequest() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(accept_request_.get()); |
| DCHECK(connection_request_queue_.size() >= 1); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() |
| << ": Accepting pending connection."; |
| |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| request->accepting = true; |
| |
| BluetoothDeviceBlueZ* device = |
| static_cast<BluetoothAdapterBlueZ*>(adapter_.get()) |
| ->GetDeviceWithPath(request->device_path); |
| DCHECK(device); |
| |
| scoped_refptr<BluetoothSocketBlueZ> client_socket = |
| BluetoothSocketBlueZ::CreateBluetoothSocket(ui_task_runner(), |
| socket_thread()); |
| |
| client_socket->device_address_ = device->GetAddress(); |
| client_socket->device_path_ = request->device_path; |
| client_socket->uuid_ = uuid_; |
| |
| socket_thread()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BluetoothSocketBlueZ::DoNewConnection, client_socket, |
| request->device_path, base::Passed(&request->fd), |
| request->options, |
| base::Bind(&BluetoothSocketBlueZ::OnNewConnection, this, |
| client_socket, request->callback))); |
| } |
| |
| void BluetoothSocketBlueZ::DoNewConnection( |
| const dbus::ObjectPath& device_path, |
| base::ScopedFD fd, |
| const bluez::BluetoothProfileServiceProvider::Delegate::Options& options, |
| const ConfirmationCallback& callback) { |
| DCHECK(socket_thread()->task_runner()->RunsTasksInCurrentSequence()); |
| base::AssertBlockingAllowed(); |
| |
| if (!fd.is_valid()) { |
| LOG(WARNING) << uuid_.canonical_value() << " :" << fd.get() |
| << ": Invalid file descriptor received from Bluetooth Daemon."; |
| ui_task_runner()->PostTask(FROM_HERE, base::Bind(callback, REJECTED)); |
| return; |
| } |
| |
| if (tcp_socket()) { |
| LOG(WARNING) << uuid_.canonical_value() << ": Already connected"; |
| ui_task_runner()->PostTask(FROM_HERE, base::Bind(callback, REJECTED)); |
| return; |
| } |
| |
| ResetTCPSocket(); |
| |
| // Note: We don't have a meaningful |IPEndPoint|, but that is ok since the |
| // TCPSocket implementation does not actually require one. |
| int net_result = |
| tcp_socket()->AdoptConnectedSocket(fd.release(), net::IPEndPoint()); |
| if (net_result != net::OK) { |
| LOG(WARNING) << uuid_.canonical_value() << ": Error adopting socket: " |
| << std::string(net::ErrorToString(net_result)); |
| ui_task_runner()->PostTask(FROM_HERE, base::Bind(callback, REJECTED)); |
| return; |
| } |
| ui_task_runner()->PostTask(FROM_HERE, base::Bind(callback, SUCCESS)); |
| } |
| |
| void BluetoothSocketBlueZ::OnNewConnection( |
| scoped_refptr<BluetoothSocket> socket, |
| const ConfirmationCallback& callback, |
| Status status) { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(accept_request_.get()); |
| DCHECK(connection_request_queue_.size() >= 1); |
| |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| if (status == SUCCESS && !request->cancelled) { |
| BluetoothDeviceBlueZ* device = |
| static_cast<BluetoothAdapterBlueZ*>(adapter_.get()) |
| ->GetDeviceWithPath(request->device_path); |
| DCHECK(device); |
| |
| accept_request_->success_callback.Run(device, socket); |
| } else { |
| accept_request_->error_callback.Run(kAcceptFailed); |
| } |
| |
| accept_request_.reset(nullptr); |
| connection_request_queue_.pop(); |
| |
| callback.Run(status); |
| } |
| |
| void BluetoothSocketBlueZ::DoCloseListening() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| |
| if (accept_request_) { |
| accept_request_->error_callback.Run( |
| net::ErrorToString(net::ERR_CONNECTION_CLOSED)); |
| accept_request_.reset(nullptr); |
| } |
| |
| while (connection_request_queue_.size() > 0) { |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| request->callback.Run(REJECTED); |
| connection_request_queue_.pop(); |
| } |
| } |
| |
| void BluetoothSocketBlueZ::UnregisterProfile() { |
| DCHECK(ui_task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(profile_); |
| |
| VLOG(1) << profile_->object_path().value() << ": Release profile"; |
| |
| static_cast<BluetoothAdapterBlueZ*>(adapter_.get()) |
| ->ReleaseProfile(device_path_, profile_); |
| profile_ = nullptr; |
| } |
| |
| } // namespace bluez |