Commit a39051bb authored by Ryan Hansberry's avatar Ryan Hansberry Committed by Commit Bot

[Bluetooth] Add mojom::Socket and associated mojom::Adapter method.

Implement mojom::Socket, a Mojo interface for interacting with
device::BluetoothSocket. This implementation mirrors the
implementation of tcp_socket.mojom: it uses a pair of Mojo
DataPipes to stream sent and received data, instead of mirroring
device::BluetoothSocket's Send() and Receive() methods.

This CL only adds mojom::Adapter::ConnectToServiceInsecurely(), to
allow clients to initiate a connection. A subsequent CL will add
mojom::Adapter::CreateRfcommService() to allow listening for an
incoming connection.

This implementation has been manually verified by running a Nearby Share
(go/nearby-chrome-bt) prototype on a Pixelbook, communicating with a
Pixel 3.

Please see design doc go/nearby-chrome-bt for more details.

Bug: 1043300
Change-Id: Ice8110ef9bce905ab46cc38fee3110f8d0775644
Fixed: b:157750558, b:157484150
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2292871
Commit-Queue: Ryan Hansberry <hansberry@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarJames Vecore <vecore@google.com>
Reviewed-by: default avatarAlex Chau <alexchau@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795318}
parent 7d51b29b
......@@ -27,4 +27,9 @@ void FakeAdapter::SetClient(
void FakeAdapter::StartDiscoverySession(
StartDiscoverySessionCallback callback) {}
void FakeAdapter::ConnectToServiceInsecurely(
const std::string& address,
const device::BluetoothUUID& service_uuid,
ConnectToServiceInsecurelyCallback callback) {}
} // namespace bluetooth
......@@ -24,6 +24,10 @@ class FakeAdapter : public mojom::Adapter {
void GetInfo(GetInfoCallback callback) override;
void SetClient(::mojo::PendingRemote<mojom::AdapterClient> client) override;
void StartDiscoverySession(StartDiscoverySessionCallback callback) override;
void ConnectToServiceInsecurely(
const std::string& address,
const device::BluetoothUUID& service_uuid,
ConnectToServiceInsecurelyCallback callback) override;
mojo::Receiver<mojom::Adapter> adapter{this};
bool present = true;
......
......@@ -130,6 +130,10 @@ BluetoothInternalsTest.prototype = {
return {session: null};
}
async connectToServiceInsecurely(address, service_uuid) {
return {result: null};
}
setTestConnectResult(connectResult) {
this.connectResult_ = connectResult;
}
......
......@@ -41,6 +41,7 @@ test("device_unittests") {
"bluetooth/bluetooth_task_manager_win_unittest.cc",
"bluetooth/device_unittest.cc",
"bluetooth/public/cpp/bluetooth_uuid_unittest.cc",
"bluetooth/socket_unittest.cc",
"bluetooth/string_util_icu_unittest.cc",
"bluetooth/test/bluetooth_gatt_server_test.cc",
"bluetooth/test/bluetooth_gatt_server_test.h",
......
......@@ -40,12 +40,15 @@ source_set("deprecated_experimental_mojo") {
"device.h",
"discovery_session.cc",
"discovery_session.h",
"socket.cc",
"socket.h",
]
deps = [
":bluetooth",
"//device/bluetooth/public/mojom:deprecated_experimental_interfaces",
"//mojo/public/cpp/bindings",
"//net",
]
visibility = [
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/adapter.h"
#include <string>
#include <utility>
#include <vector>
......@@ -10,10 +12,12 @@
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "device/bluetooth/adapter.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/device.h"
#include "device/bluetooth/discovery_session.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/public/mojom/connect_result_type_converter.h"
#include "device/bluetooth/socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
......@@ -35,7 +39,7 @@ void Adapter::ConnectToDevice(const std::string& address,
if (!device) {
std::move(callback).Run(mojom::ConnectResult::DEVICE_NO_LONGER_IN_RANGE,
/* device */ mojo::NullRemote());
/*device=*/mojo::NullRemote());
return;
}
......@@ -87,6 +91,19 @@ void Adapter::StartDiscoverySession(StartDiscoverySessionCallback callback) {
weak_ptr_factory_.GetWeakPtr(), copyable_callback));
}
void Adapter::ConnectToServiceInsecurely(
const std::string& address,
const device::BluetoothUUID& service_uuid,
ConnectToServiceInsecurelyCallback callback) {
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
adapter_->GetDevice(address)->ConnectToServiceInsecurely(
service_uuid,
base::BindOnce(&Adapter::OnConnectToService,
weak_ptr_factory_.GetWeakPtr(), copyable_callback),
base::BindOnce(&Adapter::OnConnectToServiceError,
weak_ptr_factory_.GetWeakPtr(), copyable_callback));
}
void Adapter::AdapterPresentChanged(device::BluetoothAdapter* adapter,
bool present) {
if (client_)
......@@ -148,7 +165,7 @@ void Adapter::OnConnectError(
ConnectToDeviceCallback callback,
device::BluetoothDevice::ConnectErrorCode error_code) {
std::move(callback).Run(mojo::ConvertTo<mojom::ConnectResult>(error_code),
/* device */ mojo::NullRemote());
/*device=*/mojo::NullRemote());
}
void Adapter::OnStartDiscoverySession(
......@@ -162,7 +179,57 @@ void Adapter::OnStartDiscoverySession(
}
void Adapter::OnDiscoverySessionError(StartDiscoverySessionCallback callback) {
std::move(callback).Run(mojo::NullRemote() /* session */);
std::move(callback).Run(/*session=*/mojo::NullRemote());
}
void Adapter::OnConnectToService(
ConnectToServiceInsecurelyCallback callback,
scoped_refptr<device::BluetoothSocket> socket) {
mojo::ScopedDataPipeProducerHandle receive_pipe_producer_handle;
mojo::ScopedDataPipeConsumerHandle receive_pipe_consumer_handle;
MojoResult result =
mojo::CreateDataPipe(/*options=*/nullptr, &receive_pipe_producer_handle,
&receive_pipe_consumer_handle);
if (result != MOJO_RESULT_OK) {
socket->Close();
OnConnectToServiceError(std::move(callback),
"Failed to create receiving DataPipe.");
return;
}
mojo::ScopedDataPipeProducerHandle send_pipe_producer_handle;
mojo::ScopedDataPipeConsumerHandle send_pipe_consumer_handle;
result = mojo::CreateDataPipe(/*options=*/nullptr, &send_pipe_producer_handle,
&send_pipe_consumer_handle);
if (result != MOJO_RESULT_OK) {
socket->Close();
OnConnectToServiceError(std::move(callback),
"Failed to create sending DataPipe.");
return;
}
mojo::PendingRemote<mojom::Socket> pending_socket;
mojo::MakeSelfOwnedReceiver(
std::make_unique<Socket>(std::move(socket),
std::move(receive_pipe_producer_handle),
std::move(send_pipe_consumer_handle)),
pending_socket.InitWithNewPipeAndPassReceiver());
mojom::ConnectToServiceResultPtr connect_to_service_result =
mojom::ConnectToServiceResult::New();
connect_to_service_result->socket = std::move(pending_socket);
connect_to_service_result->receive_stream =
std::move(receive_pipe_consumer_handle);
connect_to_service_result->send_stream = std::move(send_pipe_producer_handle);
std::move(callback).Run(std::move(connect_to_service_result));
}
void Adapter::OnConnectToServiceError(
ConnectToServiceInsecurelyCallback callback,
const std::string& message) {
DLOG(ERROR) << "Failed to connect to service: '" << message << "'";
std::move(callback).Run(/*result=*/nullptr);
}
} // namespace bluetooth
......@@ -12,6 +12,7 @@
#include "base/memory/ref_counted.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "device/bluetooth/public/mojom/device.mojom-forward.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
......@@ -36,6 +37,10 @@ class Adapter : public mojom::Adapter,
void GetInfo(GetInfoCallback callback) override;
void SetClient(mojo::PendingRemote<mojom::AdapterClient> client) override;
void StartDiscoverySession(StartDiscoverySessionCallback callback) override;
void ConnectToServiceInsecurely(
const std::string& address,
const device::BluetoothUUID& service_uuid,
ConnectToServiceInsecurelyCallback callback) override;
// device::BluetoothAdapter::Observer overrides:
void AdapterPresentChanged(device::BluetoothAdapter* adapter,
......@@ -67,6 +72,12 @@ class Adapter : public mojom::Adapter,
void OnDiscoverySessionError(StartDiscoverySessionCallback callback);
void OnConnectToService(ConnectToServiceInsecurelyCallback callback,
scoped_refptr<device::BluetoothSocket> socket);
void OnConnectToServiceError(ConnectToServiceInsecurelyCallback callback,
const std::string& message);
// The current Bluetooth adapter.
scoped_refptr<device::BluetoothAdapter> adapter_;
......
......@@ -5,6 +5,7 @@
module bluetooth.mojom;
import "device/bluetooth/public/mojom/device.mojom";
import "device/bluetooth/public/mojom/uuid.mojom";
// Possible errors sent as a response by Adapter.ConnectToDevice on a Device
// connection request.
......@@ -21,7 +22,15 @@ enum ConnectResult {
DEVICE_NO_LONGER_IN_RANGE,
};
// The results of a successful connection via ConnectToServiceInsecurely().
struct ConnectToServiceResult {
pending_remote<Socket> socket;
handle<data_pipe_consumer> receive_stream;
handle<data_pipe_producer> send_stream;
};
struct AdapterInfo {
// TODO(crbug.com/1112886): Use fixed-size array, not string, for address.
string address;
string name;
string system_name;
......@@ -52,10 +61,25 @@ interface DiscoverySession {
Stop() => (bool success);
};
// Represents an open connection to a remote device. Releasing it will destroy
// the underlying connection, but callers should prefer to let a call to
// Disconnect() to finish first.
interface Socket {
// Use to gracefully close the underlying connection before destroying. The
// reply callback can be used to synchronize a reconnection attempt;
// attempting to reconnect to the same service on this device may fail with a
// busy error until the reply callback is invoked.
[Sync]
Disconnect() => ();
};
// Handles requests to either query Bluetooth adapter capabilities or state, or
// find or connect to remote devices. Backed by //device/bluetooth.
interface Adapter {
// Creates a GATT connection to the device with |address| and returns a
// Device if the connection was succesful. The GATT connection is tied to the
// the lifetime of the Device message pipe.
// TODO(crbug.com/1112886): Use fixed-size array, not string, for address.
ConnectToDevice(string address) =>
(ConnectResult result, pending_remote<Device>? device);
......@@ -73,6 +97,16 @@ interface Adapter {
// Requests the adapter to start a new discovery session. Returns null if
// session not created successfully.
StartDiscoverySession() => (pending_remote<DiscoverySession>? session);
// Attempts to initiate an insecure outgoing L2CAP or RFCOMM connection to the
// advertised service on this device matching |service_uuid|. This method is
// marked as "Insecurely" because the outgoing connection will not request
// bonding (pairing) to the remote device. All return values will be null if
// the connection attempt fails.
// TODO(crbug.com/1112886): Use fixed-size array, not string, for address.
[Sync]
ConnectToServiceInsecurely(string address, UUID service_uuid)
=> (ConnectToServiceResult? result);
};
interface AdapterClient {
......
// Copyright 2020 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/socket.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/io_buffer.h"
namespace bluetooth {
Socket::Socket(scoped_refptr<device::BluetoothSocket> bluetooth_socket,
mojo::ScopedDataPipeProducerHandle receive_stream,
mojo::ScopedDataPipeConsumerHandle send_stream)
: bluetooth_socket_(std::move(bluetooth_socket)),
receive_stream_(std::move(receive_stream)),
send_stream_(std::move(send_stream)),
receive_stream_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL),
send_stream_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL) {
receive_stream_watcher_.Watch(
receive_stream_.get(),
MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::BindRepeating(&Socket::OnReceiveStreamWritable,
base::Unretained(this)));
send_stream_watcher_.Watch(
send_stream_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::BindRepeating(&Socket::OnSendStreamReadable,
base::Unretained(this)));
ReceiveMore();
SendMore();
}
Socket::~Socket() {
ShutdownReceive();
ShutdownSend();
bluetooth_socket_->Close();
}
void Socket::Disconnect(DisconnectCallback callback) {
bluetooth_socket_->Disconnect(std::move(callback));
}
void Socket::OnReceiveStreamWritable(MojoResult result) {
DCHECK(receive_stream_.is_valid());
if (result == MOJO_RESULT_OK) {
ReceiveMore();
return;
}
ShutdownReceive();
}
void Socket::ShutdownReceive() {
receive_stream_watcher_.Cancel();
receive_stream_.reset();
}
void Socket::ReceiveMore() {
DCHECK(receive_stream_.is_valid());
// The destination to which we will write incoming bytes from
// |bluetooth_socket_|. The allocated buffer and its max available size
// (assigned to |pending_write_buffer_max_size|) will be fetched by calling
// BeginWriteData() below. This already-allocated buffer is a buffer shared
// between the 2 sides of |receive_stream_|.
void* pending_write_buffer = nullptr;
// Passing 0 as the initial value allows |pending_write_buffer_max_size| to be
// assigned the buffer's max size.
uint32_t pending_write_buffer_max_size = 0;
MojoResult result = receive_stream_->BeginWriteData(
&pending_write_buffer, &pending_write_buffer_max_size,
MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
receive_stream_watcher_.ArmOrNotify();
return;
} else if (result != MOJO_RESULT_OK) {
ShutdownReceive();
return;
}
bluetooth_socket_->Receive(
pending_write_buffer_max_size,
base::BindOnce(&Socket::OnBluetoothSocketReceive,
weak_ptr_factory_.GetWeakPtr(), pending_write_buffer),
base::BindOnce(&Socket::OnBluetoothSocketReceiveError,
weak_ptr_factory_.GetWeakPtr()));
}
void Socket::OnBluetoothSocketReceive(void* pending_write_buffer,
int num_bytes_received,
scoped_refptr<net::IOBuffer> io_buffer) {
DCHECK_GT(num_bytes_received, 0);
DCHECK(io_buffer->data());
if (!receive_stream_.is_valid())
return;
memcpy(pending_write_buffer, io_buffer->data(), num_bytes_received);
receive_stream_->EndWriteData(static_cast<uint32_t>(num_bytes_received));
ReceiveMore();
}
void Socket::OnBluetoothSocketReceiveError(
device::BluetoothSocket::ErrorReason error_reason,
const std::string& error_message) {
DLOG(ERROR) << "Failed to receive data for reason '" << error_reason << "': '"
<< error_message << "'";
if (receive_stream_.is_valid()) {
receive_stream_->EndWriteData(0);
ShutdownReceive();
}
}
void Socket::OnSendStreamReadable(MojoResult result) {
DCHECK(send_stream_.is_valid());
if (result == MOJO_RESULT_OK)
SendMore();
else
ShutdownSend();
}
void Socket::ShutdownSend() {
send_stream_watcher_.Cancel();
send_stream_.reset();
}
void Socket::SendMore() {
DCHECK(send_stream_.is_valid());
// The source from which we will write outgoing bytes to
// |bluetooth_socket_|. The allocated buffer and the number of bytes already
// written by the other side of |send_stream_| (assigned to
// |pending_read_buffer_size|) will be fetched by calling BeginReadData()
// below. This already-allocated buffer is a buffer shared between the 2 sides
// of |send_stream_|.
const void* pending_read_buffer = nullptr;
// Passing 0 as the initial value allows |pending_read_buffer_size| to be
// assigned the number of bytes that the other side of |send_stream_| has
// already written.
uint32_t pending_read_buffer_size = 0;
MojoResult result = send_stream_->BeginReadData(&pending_read_buffer,
&pending_read_buffer_size,
MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
send_stream_watcher_.ArmOrNotify();
return;
} else if (result != MOJO_RESULT_OK) {
ShutdownSend();
return;
}
bluetooth_socket_->Send(base::MakeRefCounted<net::WrappedIOBuffer>(
static_cast<const char*>(pending_read_buffer)),
pending_read_buffer_size,
base::BindOnce(&Socket::OnBluetoothSocketSend,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&Socket::OnBluetoothSocketSendError,
weak_ptr_factory_.GetWeakPtr()));
}
void Socket::OnBluetoothSocketSend(int num_bytes_sent) {
DCHECK_GE(num_bytes_sent, 0);
if (!send_stream_.is_valid())
return;
send_stream_->EndReadData(static_cast<uint32_t>(num_bytes_sent));
SendMore();
}
void Socket::OnBluetoothSocketSendError(const std::string& error_message) {
DLOG(ERROR) << "Failed to send data: '" << error_message << "'";
if (send_stream_.is_valid()) {
send_stream_->EndReadData(0);
ShutdownSend();
}
}
} // namespace bluetooth
// Copyright 2020 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.
#ifndef DEVICE_BLUETOOTH_SOCKET_H_
#define DEVICE_BLUETOOTH_SOCKET_H_
#include <memory>
#include <string>
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
namespace net {
class IOBuffer;
} // namespace net
namespace bluetooth {
// Implementation of Mojo Socket in
// device/bluetooth/public/mojom/adapter.mojom.
// It handles requests to interact with a Socket.
// Uses the platform abstraction of //device/bluetooth.
// An instance of this class is constructed by Adapter and strongly bound to its
// MessagePipe. When the instance is destroyed, the underlying BluetoothSocket
// is destroyed.
class Socket : public mojom::Socket {
public:
Socket(scoped_refptr<device::BluetoothSocket> bluetooth_socket,
mojo::ScopedDataPipeProducerHandle receive_stream,
mojo::ScopedDataPipeConsumerHandle send_stream);
~Socket() override;
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
// mojom::Socket:
void Disconnect(DisconnectCallback callback) override;
private:
// "Receiving" in this context means receiving data from |bluetooth_socket_|
// via BluetoothSocket::Receive() and *writing* it to |receive_stream_|.
void OnReceiveStreamWritable(MojoResult result);
void ShutdownReceive();
void ReceiveMore();
void OnBluetoothSocketReceive(void* pending_write_buffer,
int num_bytes_received,
scoped_refptr<net::IOBuffer> io_buffer);
void OnBluetoothSocketReceiveError(
device::BluetoothSocket::ErrorReason error_reason,
const std::string& error_message);
// "Sending" in this context means *reading* data from |send_stream_| and
// sending it over the |bluetooth_socket_| via BluetoothSocket::Send().
void OnSendStreamReadable(MojoResult result);
void ShutdownSend();
void SendMore();
void OnBluetoothSocketSend(int num_bytes_sent);
void OnBluetoothSocketSendError(const std::string& error_message);
scoped_refptr<device::BluetoothSocket> bluetooth_socket_;
mojo::ScopedDataPipeProducerHandle receive_stream_;
mojo::ScopedDataPipeConsumerHandle send_stream_;
mojo::SimpleWatcher receive_stream_watcher_;
mojo::SimpleWatcher send_stream_watcher_;
base::WeakPtrFactory<Socket> weak_ptr_factory_{this};
};
} // namespace bluetooth
#endif // DEVICE_BLUETOOTH_SOCKET_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment