| // 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/bluetooth_socket_chromeos.h" |
| |
| #include <queue> |
| #include <string> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/linked_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_util.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/threading/worker_pool.h" |
| #include "chromeos/dbus/bluetooth_device_client.h" |
| #include "chromeos/dbus/bluetooth_profile_manager_client.h" |
| #include "chromeos/dbus/bluetooth_profile_service_provider.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "dbus/bus.h" |
| #include "dbus/file_descriptor.h" |
| #include "dbus/object_path.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_adapter_chromeos.h" |
| #include "device/bluetooth/bluetooth_device.h" |
| #include "device/bluetooth/bluetooth_device_chromeos.h" |
| #include "device/bluetooth/bluetooth_socket.h" |
| #include "device/bluetooth/bluetooth_socket_net.h" |
| #include "device/bluetooth/bluetooth_socket_thread.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 chromeos { |
| |
| // static |
| scoped_refptr<BluetoothSocketChromeOS> |
| BluetoothSocketChromeOS::CreateBluetoothSocket( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| scoped_refptr<BluetoothSocketThread> socket_thread, |
| net::NetLog* net_log, |
| const net::NetLog::Source& source) { |
| DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); |
| |
| return make_scoped_refptr( |
| new BluetoothSocketChromeOS( |
| ui_task_runner, socket_thread, net_log, source)); |
| } |
| |
| BluetoothSocketChromeOS::AcceptRequest::AcceptRequest() {} |
| |
| BluetoothSocketChromeOS::AcceptRequest::~AcceptRequest() {} |
| |
| BluetoothSocketChromeOS::ConnectionRequest::ConnectionRequest() |
| : accepting(false), |
| cancelled(false) {} |
| |
| BluetoothSocketChromeOS::ConnectionRequest::~ConnectionRequest() {} |
| |
| BluetoothSocketChromeOS::BluetoothSocketChromeOS( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| scoped_refptr<BluetoothSocketThread> socket_thread, |
| net::NetLog* net_log, |
| const net::NetLog::Source& source) |
| : BluetoothSocketNet(ui_task_runner, socket_thread, net_log, source) { |
| } |
| |
| BluetoothSocketChromeOS::~BluetoothSocketChromeOS() { |
| DCHECK(object_path_.value().empty()); |
| DCHECK(profile_.get() == NULL); |
| |
| if (adapter_.get()) { |
| adapter_->RemoveObserver(this); |
| adapter_ = NULL; |
| } |
| } |
| |
| void BluetoothSocketChromeOS::Connect( |
| const BluetoothDeviceChromeOS* device, |
| const BluetoothUUID& uuid, |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(object_path_.value().empty()); |
| DCHECK(!profile_.get()); |
| |
| if (!uuid.IsValid()) { |
| error_callback.Run(kInvalidUUID); |
| return; |
| } |
| |
| device_address_ = device->GetAddress(); |
| device_path_ = device->object_path(); |
| uuid_ = uuid; |
| options_.reset(new BluetoothProfileManagerClient::Options()); |
| |
| RegisterProfile(success_callback, error_callback); |
| } |
| |
| void BluetoothSocketChromeOS::Listen( |
| scoped_refptr<BluetoothAdapter> adapter, |
| SocketType socket_type, |
| const BluetoothUUID& uuid, |
| int psm_or_channel, |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(object_path_.value().empty()); |
| DCHECK(!profile_.get()); |
| |
| if (!uuid.IsValid()) { |
| error_callback.Run(kInvalidUUID); |
| return; |
| } |
| |
| adapter_ = adapter; |
| adapter_->AddObserver(this); |
| |
| uuid_ = uuid; |
| options_.reset(new BluetoothProfileManagerClient::Options()); |
| |
| switch (socket_type) { |
| case kRfcomm: |
| options_->channel.reset(new uint16( |
| psm_or_channel == BluetoothAdapter::kChannelAuto |
| ? 0 : psm_or_channel)); |
| break; |
| case kL2cap: |
| options_->psm.reset(new uint16( |
| psm_or_channel == BluetoothAdapter::kPsmAuto |
| ? 0 : psm_or_channel)); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| RegisterProfile(success_callback, error_callback); |
| } |
| |
| void BluetoothSocketChromeOS::Close() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| if (profile_) |
| UnregisterProfile(); |
| |
| if (!device_path_.value().empty()) { |
| BluetoothSocketNet::Close(); |
| } else { |
| DoCloseListening(); |
| } |
| } |
| |
| void BluetoothSocketChromeOS::Disconnect(const base::Closure& callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| if (profile_) |
| UnregisterProfile(); |
| |
| if (!device_path_.value().empty()) { |
| BluetoothSocketNet::Disconnect(callback); |
| } else { |
| DoCloseListening(); |
| callback.Run(); |
| } |
| } |
| |
| void BluetoothSocketChromeOS::Accept( |
| const AcceptCompletionCallback& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| 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 BluetoothSocketChromeOS::RegisterProfile( |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(object_path_.value().empty()); |
| DCHECK(!profile_.get()); |
| |
| // The object path is relatively meaningless, but has to be unique, so for |
| // connecting profiles use a combination of the device address and profile |
| // UUID. |
| std::string device_address_path, uuid_path; |
| base::ReplaceChars(device_address_, ":-", "_", &device_address_path); |
| base::ReplaceChars(uuid_.canonical_value(), ":-", "_", &uuid_path); |
| if (!device_address_path.empty()) { |
| object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + |
| device_address_path + "/" + uuid_path); |
| } else { |
| object_path_ = dbus::ObjectPath("/org/chromium/bluetooth_profile/" + |
| uuid_path); |
| } |
| |
| // Create the service provider for the profile object. |
| dbus::Bus* system_bus = DBusThreadManager::Get()->GetSystemBus(); |
| profile_.reset(BluetoothProfileServiceProvider::Create( |
| system_bus, object_path_, this)); |
| DCHECK(profile_.get()); |
| |
| // Before reaching out to the Bluetooth Daemon to register a listening socket, |
| // make sure it's actually running. If not, report success and carry on; |
| // the profile will be registered when the daemon becomes available. |
| if (adapter_ && !adapter_->IsPresent()) { |
| VLOG(1) << object_path_.value() << ": Delaying profile registration."; |
| success_callback.Run(); |
| return; |
| } |
| |
| VLOG(1) << object_path_.value() << ": Registering profile."; |
| DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> |
| RegisterProfile( |
| object_path_, |
| uuid_.canonical_value(), |
| *options_, |
| base::Bind(&BluetoothSocketChromeOS::OnRegisterProfile, |
| this, |
| success_callback, |
| error_callback), |
| base::Bind(&BluetoothSocketChromeOS::OnRegisterProfileError, |
| this, |
| error_callback)); |
| } |
| |
| void BluetoothSocketChromeOS::OnRegisterProfile( |
| const base::Closure& success_callback, |
| const ErrorCompletionCallback& error_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| if (!device_path_.value().empty()) { |
| VLOG(1) << object_path_.value() << ": Profile registered, connecting to " |
| << device_path_.value(); |
| |
| DBusThreadManager::Get()->GetBluetoothDeviceClient()-> |
| ConnectProfile( |
| device_path_, |
| uuid_.canonical_value(), |
| base::Bind( |
| &BluetoothSocketChromeOS::OnConnectProfile, |
| this, |
| success_callback), |
| base::Bind( |
| &BluetoothSocketChromeOS::OnConnectProfileError, |
| this, |
| error_callback)); |
| } else { |
| VLOG(1) << object_path_.value() << ": Profile registered."; |
| success_callback.Run(); |
| } |
| } |
| |
| void BluetoothSocketChromeOS::OnRegisterProfileError( |
| const ErrorCompletionCallback& error_callback, |
| const std::string& error_name, |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| LOG(WARNING) << object_path_.value() << ": Failed to register profile: " |
| << error_name << ": " << error_message; |
| error_callback.Run(error_message); |
| } |
| |
| void BluetoothSocketChromeOS::OnConnectProfile( |
| const base::Closure& success_callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| VLOG(1) << object_path_.value() << ": Profile connected."; |
| UnregisterProfile(); |
| success_callback.Run(); |
| } |
| |
| void BluetoothSocketChromeOS::OnConnectProfileError( |
| const ErrorCompletionCallback& error_callback, |
| const std::string& error_name, |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| LOG(WARNING) << object_path_.value() << ": Failed to connect profile: " |
| << error_name << ": " << error_message; |
| UnregisterProfile(); |
| error_callback.Run(error_message); |
| } |
| |
| void BluetoothSocketChromeOS::AdapterPresentChanged(BluetoothAdapter* adapter, |
| bool present) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(!object_path_.value().empty()); |
| DCHECK(profile_.get()); |
| |
| if (!present) |
| return; |
| |
| VLOG(1) << object_path_.value() << ": Re-register profile."; |
| DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> |
| RegisterProfile( |
| object_path_, |
| uuid_.canonical_value(), |
| *options_, |
| base::Bind(&BluetoothSocketChromeOS::OnInternalRegisterProfile, |
| this), |
| base::Bind(&BluetoothSocketChromeOS::OnInternalRegisterProfileError, |
| this)); |
| } |
| |
| void BluetoothSocketChromeOS::OnInternalRegisterProfile() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| VLOG(1) << object_path_.value() << ": Profile re-registered"; |
| } |
| |
| void BluetoothSocketChromeOS::OnInternalRegisterProfileError( |
| const std::string& error_name, |
| const std::string& error_message) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| // It's okay if the profile already exists, it means we registered it on |
| // initialization. |
| if (error_name == bluetooth_profile_manager::kErrorAlreadyExists) |
| return; |
| |
| LOG(WARNING) << object_path_.value() << ": Failed to re-register profile: " |
| << error_name << ": " << error_message; |
| } |
| |
| void BluetoothSocketChromeOS::Released() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| VLOG(1) << object_path_.value() << ": Release"; |
| } |
| |
| void BluetoothSocketChromeOS::NewConnection( |
| const dbus::ObjectPath& device_path, |
| scoped_ptr<dbus::FileDescriptor> fd, |
| const BluetoothProfileServiceProvider::Delegate::Options& options, |
| const ConfirmationCallback& callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| VLOG(1) << object_path_.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( |
| &BluetoothSocketChromeOS::DoNewConnection, |
| this, |
| device_path_, |
| base::Passed(&fd), |
| options, |
| callback)); |
| } else { |
| linked_ptr<ConnectionRequest> request(new ConnectionRequest()); |
| request->device_path = device_path; |
| request->fd = fd.Pass(); |
| request->options = options; |
| request->callback = callback; |
| |
| connection_request_queue_.push(request); |
| VLOG(1) << object_path_.value() << ": Connection is now pending."; |
| if (accept_request_) { |
| AcceptConnectionRequest(); |
| } |
| } |
| } |
| |
| void BluetoothSocketChromeOS::RequestDisconnection( |
| const dbus::ObjectPath& device_path, |
| const ConfirmationCallback& callback) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| VLOG(1) << object_path_.value() << ": Request disconnection"; |
| callback.Run(SUCCESS); |
| } |
| |
| void BluetoothSocketChromeOS::Cancel() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| VLOG(1) << object_path_.value() << ": Cancel"; |
| |
| if (!connection_request_queue_.size()) |
| 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 BluetoothSocketChromeOS::AcceptConnectionRequest() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(accept_request_.get()); |
| DCHECK(connection_request_queue_.size() >= 1); |
| |
| VLOG(1) << object_path_.value() << ": Accepting pending connection."; |
| |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| request->accepting = true; |
| |
| BluetoothDeviceChromeOS* device = |
| static_cast<BluetoothAdapterChromeOS*>(adapter_.get())-> |
| GetDeviceWithPath(request->device_path); |
| DCHECK(device); |
| |
| scoped_refptr<BluetoothSocketChromeOS> client_socket = |
| BluetoothSocketChromeOS::CreateBluetoothSocket( |
| ui_task_runner(), |
| socket_thread(), |
| net_log(), |
| source()); |
| |
| 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( |
| &BluetoothSocketChromeOS::DoNewConnection, |
| client_socket, |
| request->device_path, |
| base::Passed(&request->fd), |
| request->options, |
| base::Bind(&BluetoothSocketChromeOS::OnNewConnection, |
| this, |
| client_socket, |
| request->callback))); |
| } |
| |
| void BluetoothSocketChromeOS::DoNewConnection( |
| const dbus::ObjectPath& device_path, |
| scoped_ptr<dbus::FileDescriptor> fd, |
| const BluetoothProfileServiceProvider::Delegate::Options& options, |
| const ConfirmationCallback& callback) { |
| DCHECK(socket_thread()->task_runner()->RunsTasksOnCurrentThread()); |
| base::ThreadRestrictions::AssertIOAllowed(); |
| fd->CheckValidity(); |
| |
| VLOG(1) << object_path_.value() << ": Validity check complete."; |
| if (!fd->is_valid()) { |
| LOG(WARNING) << object_path_.value() << " :" << fd->value() |
| << ": Invalid file descriptor received from Bluetooth Daemon."; |
| ui_task_runner()->PostTask(FROM_HERE, |
| base::Bind(callback, REJECTED));; |
| return; |
| } |
| |
| if (tcp_socket()) { |
| LOG(WARNING) << object_path_.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->value(), |
| net::IPEndPoint()); |
| if (net_result != net::OK) { |
| LOG(WARNING) << object_path_.value() << ": Error adopting socket: " |
| << std::string(net::ErrorToString(net_result)); |
| ui_task_runner()->PostTask(FROM_HERE, |
| base::Bind(callback, REJECTED));; |
| return; |
| } |
| |
| VLOG(2) << object_path_.value() << ": Taking descriptor, confirming success."; |
| fd->TakeValue(); |
| ui_task_runner()->PostTask(FROM_HERE, |
| base::Bind(callback, SUCCESS));; |
| } |
| |
| void BluetoothSocketChromeOS::OnNewConnection( |
| scoped_refptr<BluetoothSocket> socket, |
| const ConfirmationCallback& callback, |
| Status status) { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(accept_request_.get()); |
| DCHECK(connection_request_queue_.size() >= 1); |
| |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| if (status == SUCCESS && !request->cancelled) { |
| BluetoothDeviceChromeOS* device = |
| static_cast<BluetoothAdapterChromeOS*>(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(NULL); |
| connection_request_queue_.pop(); |
| |
| callback.Run(status); |
| } |
| |
| void BluetoothSocketChromeOS::DoCloseListening() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| |
| if (accept_request_) { |
| accept_request_->error_callback.Run( |
| net::ErrorToString(net::ERR_CONNECTION_CLOSED)); |
| accept_request_.reset(NULL); |
| } |
| |
| while (connection_request_queue_.size() > 0) { |
| linked_ptr<ConnectionRequest> request = connection_request_queue_.front(); |
| request->callback.Run(REJECTED); |
| connection_request_queue_.pop(); |
| } |
| } |
| |
| void BluetoothSocketChromeOS::UnregisterProfile() { |
| DCHECK(ui_task_runner()->RunsTasksOnCurrentThread()); |
| DCHECK(!object_path_.value().empty()); |
| DCHECK(profile_.get()); |
| |
| VLOG(1) << object_path_.value() << ": Unregister profile"; |
| DBusThreadManager::Get()->GetBluetoothProfileManagerClient()-> |
| UnregisterProfile( |
| object_path_, |
| base::Bind(&BluetoothSocketChromeOS::OnUnregisterProfile, |
| this, |
| object_path_), |
| base::Bind(&BluetoothSocketChromeOS::OnUnregisterProfileError, |
| this, |
| object_path_)); |
| |
| profile_.reset(); |
| object_path_ = dbus::ObjectPath(""); |
| } |
| |
| void BluetoothSocketChromeOS::OnUnregisterProfile( |
| const dbus::ObjectPath& object_path) { |
| VLOG(1) << object_path.value() << ": Profile unregistered"; |
| } |
| |
| void BluetoothSocketChromeOS::OnUnregisterProfileError( |
| const dbus::ObjectPath& object_path, |
| const std::string& error_name, |
| const std::string& error_message) { |
| // It's okay if the profile doesn't exist, it means we haven't registered it |
| // yet. |
| if (error_name == bluetooth_profile_manager::kErrorDoesNotExist) |
| return; |
| |
| LOG(WARNING) << object_path_.value() << ": Failed to unregister profile: " |
| << error_name << ": " << error_message; |
| } |
| |
| } // namespace chromeos |