Commit 939b63be authored by Vicky Min's avatar Vicky Min Committed by Commit Bot

[bluetooth] Read/Write serial interface for BT

The class BluetoothSerialPortImpl is intended to support
Serial API features for Bluetooth SPP devices. StartReading and
StartWriting to read from or write to the bluetooth socket
connected, and Flush/Drain to make sure Read and Write are
handled correctly when they are finished.

Bug: 1043300
Change-Id: I97fc2970215c2bfa479a0a8c61d78a03aa9a7ae3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2334682
Commit-Queue: Vicky Min <vickymin@google.com>
Reviewed-by: default avatarMatt Reynolds <mattreynolds@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarOvidio de Jesús Ruiz-Henríquez <odejesush@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798982}
parent e62b6be0
...@@ -8,7 +8,8 @@ if (is_android) { ...@@ -8,7 +8,8 @@ if (is_android) {
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
} }
is_serial_enabled_platform = is_win || ((is_linux || is_chromeos) && use_udev) || is_mac is_serial_enabled_platform =
is_win || ((is_linux || is_chromeos) && use_udev) || is_mac
source_set("lib") { source_set("lib") {
# This should be visible only to embedders of the Device Service, and the # This should be visible only to embedders of the Device Service, and the
...@@ -247,6 +248,7 @@ source_set("tests") { ...@@ -247,6 +248,7 @@ source_set("tests") {
if (is_serial_enabled_platform) { if (is_serial_enabled_platform) {
sources += [ sources += [
"serial/bluetooth_serial_port_impl_unittest.cc",
"serial/serial_device_enumerator_linux_unittest.cc", "serial/serial_device_enumerator_linux_unittest.cc",
"serial/serial_device_enumerator_unittest.cc", "serial/serial_device_enumerator_unittest.cc",
"serial/serial_port_impl_unittest.cc", "serial/serial_port_impl_unittest.cc",
...@@ -263,6 +265,7 @@ source_set("tests") { ...@@ -263,6 +265,7 @@ source_set("tests") {
deps += [ deps += [
"//device/bluetooth:mocks", "//device/bluetooth:mocks",
"//services/device/public/cpp/bluetooth:bluetooth",
"//services/device/public/cpp/serial:switches", "//services/device/public/cpp/serial:switches",
"//services/device/serial", "//services/device/serial",
"//services/device/serial:test_support", "//services/device/serial:test_support",
......
...@@ -11,6 +11,7 @@ source_set("bluetooth") { ...@@ -11,6 +11,7 @@ source_set("bluetooth") {
deps = [ deps = [
"//base", "//base",
"//device/bluetooth", "//device/bluetooth",
"//device/bluetooth/public/cpp:cpp",
"//device/bluetooth/strings", "//device/bluetooth/strings",
"//services/device/public/mojom", "//services/device/public/mojom",
"//ui/base", "//ui/base",
......
...@@ -139,4 +139,11 @@ base::string16 GetBluetoothDeviceLabelForAccessibility( ...@@ -139,4 +139,11 @@ base::string16 GetBluetoothDeviceLabelForAccessibility(
name_utf16); name_utf16);
} }
const BluetoothUUID& GetSerialPortProfileUUID() {
// The Serial Port Profile (SPP) UUID is 1101.
// https://chromium-review.googlesource.com/c/chromium/src/+/2334682/17..19
static const BluetoothUUID kValue("1101");
return kValue;
}
} // namespace device } // namespace device
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define SERVICES_DEVICE_PUBLIC_CPP_BLUETOOTH_BLUETOOTH_UTILS_H_ #define SERVICES_DEVICE_PUBLIC_CPP_BLUETOOTH_BLUETOOTH_UTILS_H_
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "services/device/public/mojom/bluetooth_system.mojom.h" #include "services/device/public/mojom/bluetooth_system.mojom.h"
namespace device { namespace device {
...@@ -25,6 +26,9 @@ base::string16 GetBluetoothDeviceNameForDisplay( ...@@ -25,6 +26,9 @@ base::string16 GetBluetoothDeviceNameForDisplay(
base::string16 GetBluetoothDeviceLabelForAccessibility( base::string16 GetBluetoothDeviceLabelForAccessibility(
const mojom::BluetoothDeviceInfoPtr& device_info); const mojom::BluetoothDeviceInfoPtr& device_info);
// Returns a BluetoothUUID for a Bluetooth SPP device.
const BluetoothUUID& GetSerialPortProfileUUID();
} // namespace device } // namespace device
#endif // SERVICES_DEVICE_PUBLIC_CPP_BLUETOOTH_BLUETOOTH_UTILS_H_ #endif // SERVICES_DEVICE_PUBLIC_CPP_BLUETOOTH_BLUETOOTH_UTILS_H_
...@@ -23,6 +23,8 @@ if (is_win || ((is_linux || is_chromeos) && use_udev) || is_mac) { ...@@ -23,6 +23,8 @@ if (is_win || ((is_linux || is_chromeos) && use_udev) || is_mac) {
sources = [ sources = [
"bluetooth_serial_device_enumerator.cc", "bluetooth_serial_device_enumerator.cc",
"bluetooth_serial_device_enumerator.h", "bluetooth_serial_device_enumerator.h",
"bluetooth_serial_port_impl.cc",
"bluetooth_serial_port_impl.h",
"buffer.cc", "buffer.cc",
"buffer.h", "buffer.h",
"serial_device_enumerator.cc", "serial_device_enumerator.cc",
...@@ -53,6 +55,7 @@ if (is_win || ((is_linux || is_chromeos) && use_udev) || is_mac) { ...@@ -53,6 +55,7 @@ if (is_win || ((is_linux || is_chromeos) && use_udev) || is_mac) {
"//device/bluetooth/public/cpp", "//device/bluetooth/public/cpp",
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//net", "//net",
"//services/device/public/cpp/bluetooth:bluetooth",
"//services/device/public/cpp/serial:switches", "//services/device/public/cpp/serial:switches",
] ]
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_adapter_factory.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/serial/serial_switches.h" #include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/public/mojom/serial.mojom.h" #include "services/device/public/mojom/serial.mojom.h"
...@@ -20,11 +21,6 @@ BluetoothSerialDeviceEnumerator::BluetoothSerialDeviceEnumerator() { ...@@ -20,11 +21,6 @@ BluetoothSerialDeviceEnumerator::BluetoothSerialDeviceEnumerator() {
base::Unretained(this))); base::Unretained(this)));
} }
const BluetoothUUID& GetSerialPortProfileUUID() {
static const BluetoothUUID kValue("1101");
return kValue;
}
BluetoothSerialDeviceEnumerator::~BluetoothSerialDeviceEnumerator() = default; BluetoothSerialDeviceEnumerator::~BluetoothSerialDeviceEnumerator() = default;
void BluetoothSerialDeviceEnumerator::OnGotClassicAdapter( void BluetoothSerialDeviceEnumerator::OnGotClassicAdapter(
......
// 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 "services/device/serial/bluetooth_serial_port_impl.h"
#include "base/command_line.h"
#include "net/base/io_buffer.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/serial/buffer.h"
namespace device {
// static
void BluetoothSerialPortImpl::Create(
std::unique_ptr<BluetoothDevice> device,
mojo::PendingReceiver<mojom::SerialPort> receiver,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher) {
DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi));
// This BluetoothSerialPortImpl is owned by |receiver| and |watcher|.
new BluetoothSerialPortImpl(std::move(device), std::move(receiver),
std::move(watcher));
}
BluetoothSerialPortImpl::BluetoothSerialPortImpl(
std::unique_ptr<BluetoothDevice> device,
mojo::PendingReceiver<mojom::SerialPort> receiver,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher)
: receiver_(this, std::move(receiver)),
watcher_(std::move(watcher)),
in_stream_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
out_stream_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
bluetooth_device_(std::move(device)) {
receiver_.set_disconnect_handler(
base::BindOnce([](BluetoothSerialPortImpl* self) { delete self; },
base::Unretained(this)));
if (watcher_.is_bound()) {
watcher_.set_disconnect_handler(
base::BindOnce([](BluetoothSerialPortImpl* self) { delete self; },
base::Unretained(this)));
}
}
BluetoothSerialPortImpl::~BluetoothSerialPortImpl() {
if (bluetooth_socket_)
bluetooth_socket_->Close();
}
void BluetoothSerialPortImpl::Open(
mojom::SerialConnectionOptionsPtr options,
mojo::PendingRemote<mojom::SerialPortClient> client,
OpenCallback callback) {
if (client)
client_.Bind(std::move(client));
BluetoothDevice::UUIDSet device_uuids = bluetooth_device_->GetUUIDs();
if (base::Contains(device_uuids, GetSerialPortProfileUUID())) {
auto copyable_callback =
base::AdaptCallbackForRepeating(std::move(callback));
bluetooth_device_->ConnectToService(
GetSerialPortProfileUUID(),
base::BindOnce(&BluetoothSerialPortImpl::OnSocketConnected,
weak_ptr_factory_.GetWeakPtr(), copyable_callback),
base::BindOnce(&BluetoothSerialPortImpl::OnSocketConnectedError,
weak_ptr_factory_.GetWeakPtr(), copyable_callback));
return;
}
std::move(callback).Run(false);
}
void BluetoothSerialPortImpl::OnSocketConnected(
OpenCallback callback,
scoped_refptr<BluetoothSocket> socket) {
DCHECK(socket);
bluetooth_socket_ = std::move(socket);
std::move(callback).Run(true);
}
void BluetoothSerialPortImpl::OnSocketConnectedError(
OpenCallback callback,
const std::string& message) {
std::move(callback).Run(false);
}
void BluetoothSerialPortImpl::StartWriting(
mojo::ScopedDataPipeConsumerHandle consumer) {
DCHECK(!write_pending_);
if (in_stream_) {
mojo::ReportBadMessage("Data pipe consumer still open.");
return;
}
if (!bluetooth_socket_) {
mojo::ReportBadMessage("No Bluetooth socket.");
return;
}
in_stream_watcher_.Cancel();
in_stream_ = std::move(consumer);
in_stream_watcher_.Watch(
in_stream_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(&BluetoothSerialPortImpl::WriteToSocket,
weak_ptr_factory_.GetWeakPtr()));
in_stream_watcher_.ArmOrNotify();
}
void BluetoothSerialPortImpl::StartReading(
mojo::ScopedDataPipeProducerHandle producer) {
if (out_stream_) {
mojo::ReportBadMessage("Data pipe producer still open.");
return;
}
if (!bluetooth_socket_) {
mojo::ReportBadMessage("No Bluetooth socket.");
return;
}
out_stream_watcher_.Cancel();
out_stream_ = std::move(producer);
out_stream_watcher_.Watch(
out_stream_.get(),
MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(&BluetoothSerialPortImpl::ReadFromSocketAndWriteOut,
weak_ptr_factory_.GetWeakPtr()));
out_stream_watcher_.ArmOrNotify();
}
void BluetoothSerialPortImpl::ReadFromSocketAndWriteOut(
MojoResult result,
const mojo::HandleSignalsState& state) {
switch (result) {
case MOJO_RESULT_OK:
ReadMore();
break;
case MOJO_RESULT_SHOULD_WAIT:
// If there is no space to write, wait for more space.
out_stream_watcher_.ArmOrNotify();
break;
case MOJO_RESULT_FAILED_PRECONDITION:
case MOJO_RESULT_CANCELLED:
// The |out_stream_| has been closed.
out_stream_watcher_.Cancel();
out_stream_.reset();
break;
default:
NOTREACHED() << "Unexpected Mojo result: " << result;
}
}
void BluetoothSerialPortImpl::ReadMore() {
DCHECK(out_stream_.is_valid());
void* buffer = nullptr;
uint32_t buffer_max_size = 0;
// The |buffer| is owned by |out_stream_|.
MojoResult result = out_stream_->BeginWriteData(&buffer, &buffer_max_size,
MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
out_stream_watcher_.ArmOrNotify();
return;
}
if (result != MOJO_RESULT_OK) {
out_stream_watcher_.Cancel();
out_stream_.reset();
return;
}
if (!bluetooth_socket_) {
mojo::ReportBadMessage("No Bluetooth socket.");
return;
}
read_pending_ = true;
bluetooth_socket_->Receive(
buffer_max_size,
base::BindOnce(
&BluetoothSerialPortImpl::OnBluetoothSocketReceive,
weak_ptr_factory_.GetWeakPtr(),
base::make_span(reinterpret_cast<char*>(buffer), buffer_max_size)),
base::BindOnce(&BluetoothSerialPortImpl::OnBluetoothSocketReceiveError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothSerialPortImpl::OnBluetoothSocketReceive(
base::span<char> pending_write_buffer,
int num_bytes_received,
scoped_refptr<net::IOBuffer> io_buffer) {
DCHECK_GT(num_bytes_received, 0);
DCHECK(io_buffer->data());
DCHECK(out_stream_.is_valid());
read_pending_ = false;
std::copy(io_buffer->data(), io_buffer->data() + num_bytes_received,
pending_write_buffer.data());
out_stream_->EndWriteData(static_cast<uint32_t>(num_bytes_received));
if (read_flush_callback_) {
std::move(read_flush_callback_).Run();
out_stream_->EndWriteData(0);
out_stream_watcher_.Cancel();
out_stream_.reset();
return;
}
ReadMore();
}
void BluetoothSerialPortImpl::OnBluetoothSocketReceiveError(
BluetoothSocket::ErrorReason error_reason,
const std::string& error_message) {
DCHECK(out_stream_.is_valid());
read_pending_ = false;
if (client_) {
DCHECK(error_reason != BluetoothSocket::ErrorReason::kIOPending);
switch (error_reason) {
case BluetoothSocket::ErrorReason::kDisconnected:
client_->OnReadError(mojom::SerialReceiveError::DISCONNECTED);
break;
case BluetoothSocket::ErrorReason::kIOPending:
NOTREACHED();
break;
case BluetoothSocket::ErrorReason::kSystemError:
client_->OnReadError(mojom::SerialReceiveError::SYSTEM_ERROR);
break;
}
}
if (read_flush_callback_)
std::move(read_flush_callback_).Run();
out_stream_->EndWriteData(0);
out_stream_watcher_.Cancel();
out_stream_.reset();
}
void BluetoothSerialPortImpl::WriteToSocket(
MojoResult result,
const mojo::HandleSignalsState& state) {
switch (result) {
case MOJO_RESULT_OK:
WriteMore();
break;
case MOJO_RESULT_SHOULD_WAIT:
// If there is no space to write, wait for more space.
in_stream_watcher_.ArmOrNotify();
break;
case MOJO_RESULT_FAILED_PRECONDITION:
case MOJO_RESULT_CANCELLED:
// The |in_stream_| has been closed.
in_stream_watcher_.Cancel();
in_stream_.reset();
if (drain_callback_)
std::move(drain_callback_).Run();
break;
default:
NOTREACHED() << "Unexpected Mojo result: " << result;
}
}
void BluetoothSerialPortImpl::WriteMore() {
DCHECK(in_stream_.is_valid());
const void* buffer = nullptr;
uint32_t buffer_size = 0;
// |buffer| is owned by |in_stream_|.
MojoResult result = in_stream_->BeginReadData(&buffer, &buffer_size,
MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
in_stream_watcher_.ArmOrNotify();
return;
}
if (result != MOJO_RESULT_OK) {
in_stream_watcher_.Cancel();
in_stream_.reset();
return;
}
if (!bluetooth_socket_) {
mojo::ReportBadMessage("No Bluetooth socket.");
return;
}
write_pending_ = true;
bluetooth_socket_->Send(
base::MakeRefCounted<net::WrappedIOBuffer>(
reinterpret_cast<const char*>(buffer)),
buffer_size,
base::BindOnce(&BluetoothSerialPortImpl::OnBluetoothSocketSend,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&BluetoothSerialPortImpl::OnBluetoothSocketSendError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothSerialPortImpl::OnBluetoothSocketSend(int num_bytes_sent) {
DCHECK_GE(num_bytes_sent, 0);
DCHECK(in_stream_.is_valid());
write_pending_ = false;
in_stream_->EndReadData(static_cast<uint32_t>(num_bytes_sent));
if (write_flush_callback_) {
std::move(write_flush_callback_).Run();
in_stream_->EndReadData(0);
in_stream_watcher_.Cancel();
in_stream_.reset();
return;
}
WriteMore();
}
void BluetoothSerialPortImpl::OnBluetoothSocketSendError(
const std::string& error_message) {
DCHECK(in_stream_.is_valid());
write_pending_ = false;
if (client_)
client_->OnSendError(mojom::SerialSendError::SYSTEM_ERROR);
if (write_flush_callback_)
std::move(write_flush_callback_).Run();
in_stream_->EndReadData(0);
in_stream_watcher_.Cancel();
in_stream_.reset();
}
void BluetoothSerialPortImpl::Flush(mojom::SerialPortFlushMode mode,
FlushCallback callback) {
NOTIMPLEMENTED();
}
void BluetoothSerialPortImpl::Drain(DrainCallback callback) {
if (!in_stream_) {
std::move(callback).Run();
return;
}
drain_callback_ = std::move(callback);
}
void BluetoothSerialPortImpl::GetControlSignals(
GetControlSignalsCallback callback) {
NOTIMPLEMENTED();
}
void BluetoothSerialPortImpl::SetControlSignals(
mojom::SerialHostControlSignalsPtr signals,
SetControlSignalsCallback callback) {
NOTIMPLEMENTED();
}
void BluetoothSerialPortImpl::ConfigurePort(
mojom::SerialConnectionOptionsPtr options,
ConfigurePortCallback callback) {
NOTIMPLEMENTED();
}
void BluetoothSerialPortImpl::GetPortInfo(GetPortInfoCallback callback) {
NOTIMPLEMENTED();
}
void BluetoothSerialPortImpl::Close(CloseCallback callback) {
NOTIMPLEMENTED();
}
} // namespace device
// 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 SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_PORT_IMPL_H_
#define SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_PORT_IMPL_H_
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "services/device/serial/serial_io_handler.h"
#include "services/device/serial/serial_port_impl.h"
namespace device {
// This class is intended to allow serial communication using a Bluetooth
// SPP device. The Bluetooth device is used to create a Bluetooth socket
// which is closed upon error in any of the interface functions.
class BluetoothSerialPortImpl : public mojom::SerialPort {
public:
// Creates of instance of BluetoothSerialPortImpl using a Bluetooth
// device and a receiver/watcher to create a pipe. The receiver and
// watcher will own this object.
static void Create(
std::unique_ptr<BluetoothDevice> device,
mojo::PendingReceiver<mojom::SerialPort> receiver,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher);
BluetoothSerialPortImpl(
std::unique_ptr<BluetoothDevice> device,
mojo::PendingReceiver<mojom::SerialPort> receiver,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher);
BluetoothSerialPortImpl(const BluetoothSerialPortImpl&) = delete;
BluetoothSerialPortImpl& operator=(const BluetoothSerialPortImpl&) = delete;
~BluetoothSerialPortImpl() override;
private:
// mojom::SerialPort methods:
void Open(mojom::SerialConnectionOptionsPtr options,
mojo::PendingRemote<mojom::SerialPortClient> client,
OpenCallback callback) override;
void StartWriting(mojo::ScopedDataPipeConsumerHandle consumer) override;
void StartReading(mojo::ScopedDataPipeProducerHandle producer) override;
void Flush(mojom::SerialPortFlushMode mode, FlushCallback callback) override;
void Drain(DrainCallback callback) override;
void GetControlSignals(GetControlSignalsCallback callback) override;
void SetControlSignals(mojom::SerialHostControlSignalsPtr signals,
SetControlSignalsCallback callback) override;
void ConfigurePort(mojom::SerialConnectionOptionsPtr options,
ConfigurePortCallback callback) override;
void GetPortInfo(GetPortInfoCallback callback) override;
void Close(CloseCallback callback) override;
void WriteToSocket(MojoResult result, const mojo::HandleSignalsState& state);
void ReadFromSocketAndWriteOut(MojoResult result,
const mojo::HandleSignalsState& state);
void ReadMore();
void WriteMore();
void OnSocketConnected(OpenCallback callback,
scoped_refptr<BluetoothSocket> socket);
void OnSocketConnectedError(OpenCallback callback,
const std::string& message);
void OnBluetoothSocketReceive(base::span<char> 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);
void OnBluetoothSocketSend(int num_bytes_sent);
void OnBluetoothSocketSendError(const std::string& error_message);
mojo::Receiver<mojom::SerialPort> receiver_;
mojo::Remote<mojom::SerialPortConnectionWatcher> watcher_;
mojo::Remote<mojom::SerialPortClient> client_;
// Data pipes for input and output.
mojo::ScopedDataPipeConsumerHandle in_stream_;
mojo::SimpleWatcher in_stream_watcher_;
mojo::ScopedDataPipeProducerHandle out_stream_;
mojo::SimpleWatcher out_stream_watcher_;
// Holds the callback for a flush or drain until pending operations have been
// completed.
FlushCallback read_flush_callback_;
FlushCallback write_flush_callback_;
DrainCallback drain_callback_;
scoped_refptr<device::BluetoothSocket> bluetooth_socket_;
std::unique_ptr<BluetoothDevice> bluetooth_device_;
bool read_pending_ = false;
bool write_pending_ = false;
base::WeakPtrFactory<BluetoothSerialPortImpl> weak_ptr_factory_{this};
};
} // namespace device
#endif // SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_PORT_IMPL_H_
// 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 "services/device/serial/bluetooth_serial_port_impl.h"
#include "base/command_line.h"
#include "base/test/bind_test_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/io_buffer.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "services/device/serial/buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Invoke;
using ::testing::WithArgs;
constexpr char kBuffer[] = "test";
constexpr char kDeviceAddress[] = "00:00:00:00:00:00";
constexpr uint32_t kElementNumBytes = 1;
constexpr uint32_t kCapacityNumBytes = 64;
class BluetoothSerialPortImplTest : public testing::Test {
public:
BluetoothSerialPortImplTest() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi);
}
BluetoothSerialPortImplTest(const BluetoothSerialPortImplTest&) = delete;
BluetoothSerialPortImplTest& operator=(const BluetoothSerialPortImplTest&) =
delete;
~BluetoothSerialPortImplTest() override = default;
void CreatePort(
mojo::Remote<mojom::SerialPort>* port,
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher>* watcher) {
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher_remote;
*watcher = mojo::MakeSelfOwnedReceiver(
std::make_unique<mojom::SerialPortConnectionWatcher>(),
watcher_remote.InitWithNewPipeAndPassReceiver());
scoped_refptr<MockBluetoothAdapter> adapter =
base::MakeRefCounted<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
auto mock_device = std::make_unique<MockBluetoothDevice>(
adapter.get(), 0, "Test Device", kDeviceAddress, false, false);
mock_device->AddUUID(GetSerialPortProfileUUID());
EXPECT_CALL(*mock_device,
ConnectToService(GetSerialPortProfileUUID(), _, _))
.WillOnce(RunOnceCallback<1>(mock_socket_));
BluetoothSerialPortImpl::Create(std::move(mock_device),
port->BindNewPipeAndPassReceiver(),
std::move(watcher_remote));
}
void CreatePortWithSocketError(
mojo::Remote<mojom::SerialPort>* port,
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher>* watcher) {
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher_remote;
*watcher = mojo::MakeSelfOwnedReceiver(
std::make_unique<mojom::SerialPortConnectionWatcher>(),
watcher_remote.InitWithNewPipeAndPassReceiver());
scoped_refptr<MockBluetoothAdapter> adapter =
base::MakeRefCounted<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
auto mock_device = std::make_unique<MockBluetoothDevice>(
adapter.get(), 0, "Test Device", kDeviceAddress, false, false);
mock_device->AddUUID(GetSerialPortProfileUUID());
EXPECT_CALL(*mock_device,
ConnectToService(GetSerialPortProfileUUID(), _, _))
.WillOnce(RunOnceCallback<2>("Error"));
BluetoothSerialPortImpl::Create(std::move(mock_device),
port->BindNewPipeAndPassReceiver(),
std::move(watcher_remote));
}
void CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer,
mojo::ScopedDataPipeConsumerHandle* consumer) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(options);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = kElementNumBytes;
options.capacity_num_bytes = kCapacityNumBytes;
MojoResult result = mojo::CreateDataPipe(&options, producer, consumer);
DCHECK_EQ(result, MOJO_RESULT_OK);
}
MockBluetoothSocket& mock_socket() { return *mock_socket_; }
private:
scoped_refptr<MockBluetoothSocket> mock_socket_ =
base::MakeRefCounted<MockBluetoothSocket>();
base::test::SingleThreadTaskEnvironment task_environment_;
};
class FakeSerialPortClient : public mojom::SerialPortClient {
public:
FakeSerialPortClient() = default;
FakeSerialPortClient(FakeSerialPortClient&) = delete;
FakeSerialPortClient& operator=(FakeSerialPortClient&) = delete;
~FakeSerialPortClient() override = default;
void Bind(mojo::PendingReceiver<device::mojom::SerialPortClient> receiver) {
receiver_.Bind(std::move(receiver));
}
// mojom::SerialPortClient
void OnReadError(mojom::SerialReceiveError error) override {}
void OnSendError(mojom::SerialSendError error) override {}
private:
mojo::Receiver<mojom::SerialPortClient> receiver_{this};
};
} // namespace
TEST_F(BluetoothSerialPortImplTest, NullSocketTest) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePortWithSocketError(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
auto options = mojom::SerialConnectionOptions::New();
mojo::PendingRemote<mojom::SerialPortClient> client;
FakeSerialPortClient serial_client;
serial_client.Bind(client.InitWithNewPipeAndPassReceiver());
base::RunLoop loop;
serial_port->Open(std::move(options), std::move(client),
base::BindLambdaForTesting([&loop](bool success) {
EXPECT_FALSE(success);
loop.Quit();
}));
loop.Run();
EXPECT_CALL(mock_socket(), Receive(_, _, _)).Times(0);
EXPECT_CALL(mock_socket(), Close()).Times(0);
serial_port->StartReading(std::move(producer));
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, StartWritingTest) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
auto options = mojom::SerialConnectionOptions::New();
mojo::PendingRemote<mojom::SerialPortClient> client;
FakeSerialPortClient serial_client;
serial_client.Bind(client.InitWithNewPipeAndPassReceiver());
base::RunLoop loop;
serial_port->Open(std::move(options), std::move(client),
base::BindLambdaForTesting([&loop](bool success) {
EXPECT_TRUE(success);
loop.Quit();
}));
loop.Run();
uint32_t bytes_read = std::char_traits<char>::length(kBuffer);
auto write_buffer = base::MakeRefCounted<net::StringIOBuffer>(kBuffer);
MojoResult result =
producer->WriteData(&kBuffer, &bytes_read, MOJO_WRITE_DATA_FLAG_NONE);
EXPECT_EQ(result, MOJO_RESULT_OK);
EXPECT_CALL(mock_socket(), Send)
.WillOnce(WithArgs<0, 1, 2>(Invoke(
[&](scoped_refptr<net::IOBuffer> buf, int buffer_size,
MockBluetoothSocket::SendCompletionCallback success_callback) {
ASSERT_EQ(buffer_size, int{bytes_read});
// EXPECT_EQ only does a shallow comparison, so it's necessary to
// iterate through both objects and compare each character.
for (int i = 0; i < buffer_size; i++) {
EXPECT_EQ(buf->data()[i], kBuffer[i])
<< "buffer comparison failed at index " << i;
}
std::move(success_callback).Run(buffer_size);
})));
EXPECT_CALL(mock_socket(), Close());
serial_port->StartWriting(std::move(consumer));
EXPECT_EQ(write_buffer->size(), int{bytes_read});
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, StartReadingTest) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
auto options = mojom::SerialConnectionOptions::New();
mojo::PendingRemote<mojom::SerialPortClient> client;
FakeSerialPortClient serial_client;
serial_client.Bind(client.InitWithNewPipeAndPassReceiver());
base::RunLoop loop;
serial_port->Open(std::move(options), std::move(client),
base::BindLambdaForTesting([&loop](bool success) {
EXPECT_TRUE(success);
loop.Quit();
}));
loop.Run();
uint32_t bytes_read = std::char_traits<char>::length(kBuffer);
auto write_buffer = base::MakeRefCounted<net::StringIOBuffer>(kBuffer);
MojoResult result =
producer->WriteData(&kBuffer, &bytes_read, MOJO_WRITE_DATA_FLAG_NONE);
EXPECT_EQ(result, MOJO_RESULT_OK);
EXPECT_CALL(mock_socket(), Receive(_, _, _))
.WillOnce(RunOnceCallback<1>(write_buffer->size(), write_buffer))
.WillOnce(RunOnceCallback<2>(BluetoothSocket::kSystemError, "Error"));
EXPECT_CALL(mock_socket(), Close());
serial_port->StartReading(std::move(producer));
ASSERT_EQ(write_buffer->size(), int{bytes_read});
int size = write_buffer->size();
// EXPECT_EQ only does a shallow comparison, so it's necessary to iterate
// through both objects and compare each character.
for (int i = 0; i < size; i++) {
EXPECT_EQ(write_buffer->data()[i], kBuffer[i])
<< "buffer comparison failed at index " << i;
}
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
TEST_F(BluetoothSerialPortImplTest, Drain) {
mojo::Remote<mojom::SerialPort> serial_port;
mojo::SelfOwnedReceiverRef<mojom::SerialPortConnectionWatcher> watcher;
CreatePort(&serial_port, &watcher);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
CreateDataPipe(&producer, &consumer);
auto options = mojom::SerialConnectionOptions::New();
mojo::PendingRemote<mojom::SerialPortClient> client;
FakeSerialPortClient serial_client;
serial_client.Bind(client.InitWithNewPipeAndPassReceiver());
base::RunLoop loop;
serial_port->Open(std::move(options), std::move(client),
base::BindLambdaForTesting([&loop](bool success) {
EXPECT_TRUE(success);
loop.Quit();
}));
loop.Run();
serial_port->StartWriting(std::move(consumer));
producer.reset();
base::RunLoop drain_loop;
serial_port->Drain(drain_loop.QuitClosure());
drain_loop.Run();
base::RunLoop disconnect_loop;
watcher->set_connection_error_handler(disconnect_loop.QuitClosure());
serial_port.reset();
disconnect_loop.Run();
}
} // namespace device
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