// 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