blob: fac7fe814414b7918f6f753b97a71a2041cc9d9a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// 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 <limits.h>
#include <algorithm>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "components/device_event_log/device_event_log.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/io_buffer.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
namespace device {
namespace {
void RecordOpenSocketResult(bool success) {
static constexpr std::string_view kOpenSocketResult =
"Bluetooth.Serial.OpenSocketResult";
base::UmaHistogramBoolean(kOpenSocketResult, success);
}
} // namespace
// static
void BluetoothSerialPortImpl::Open(
scoped_refptr<BluetoothAdapter> adapter,
const std::string& address,
const BluetoothUUID& service_class_id,
mojom::SerialConnectionOptionsPtr options,
mojo::PendingRemote<mojom::SerialPortClient> client,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher,
OpenCallback callback) {
// This BluetoothSerialPortImpl is owned by its |receiver_| and |watcher_| and
// will self-destruct on connection failure.
auto* port = new BluetoothSerialPortImpl(
std::move(adapter), address, std::move(options), std::move(client),
std::move(watcher));
port->OpenSocket(service_class_id, std::move(callback));
}
BluetoothSerialPortImpl::BluetoothSerialPortImpl(
scoped_refptr<BluetoothAdapter> adapter,
const std::string& address,
mojom::SerialConnectionOptionsPtr options,
mojo::PendingRemote<mojom::SerialPortClient> client,
mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher)
: watcher_(std::move(watcher)),
client_(std::move(client)),
in_stream_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
out_stream_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
bluetooth_adapter_(std::move(adapter)),
address_(address),
options_(std::move(options)) {
if (watcher_.is_bound()) {
watcher_.set_disconnect_handler(
base::BindOnce([](BluetoothSerialPortImpl* self) { delete self; },
base::Unretained(this)));
}
}
BluetoothSerialPortImpl::~BluetoothSerialPortImpl() {
if (open_callback_) {
BLUETOOTH_LOG(ERROR) << "Callback pending at destruction: address: "
<< address_;
std::move(open_callback_).Run(mojo::NullRemote());
}
if (bluetooth_socket_)
bluetooth_socket_->Disconnect(base::DoNothing());
}
void BluetoothSerialPortImpl::OpenSocket(const BluetoothUUID& service_class_id,
OpenCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
BluetoothDevice* device = bluetooth_adapter_->GetDevice(address_);
if (!device) {
std::move(callback).Run(mojo::NullRemote());
delete this;
return;
}
open_callback_ = std::move(callback);
device->ConnectToService(
service_class_id,
base::BindOnce(&BluetoothSerialPortImpl::OnSocketConnected,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&BluetoothSerialPortImpl::OnSocketConnectedError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothSerialPortImpl::OnSocketConnected(
scoped_refptr<BluetoothSocket> socket) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(socket);
bluetooth_socket_ = std::move(socket);
mojo::PendingRemote<mojom::SerialPort> port =
receiver_.BindNewPipeAndPassRemote();
receiver_.set_disconnect_handler(
base::BindOnce([](BluetoothSerialPortImpl* self) { delete self; },
base::Unretained(this)));
std::move(open_callback_).Run(std::move(port));
RecordOpenSocketResult(/*success=*/true);
}
void BluetoothSerialPortImpl::OnSocketConnectedError(
const std::string& message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
BLUETOOTH_LOG(ERROR) << "Failed to connect socket: address: " << address_
<< ", message: " << message;
std::move(open_callback_).Run(mojo::NullRemote());
RecordOpenSocketResult(/*success=*/false);
delete this;
}
void BluetoothSerialPortImpl::StartWriting(
mojo::ScopedDataPipeConsumerHandle consumer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (in_stream_) {
receiver_.ReportBadMessage("Data pipe consumer still open.");
return;
}
if (!bluetooth_socket_) {
receiver_.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()));
if (!write_pending_)
in_stream_watcher_.ArmOrNotify();
}
void BluetoothSerialPortImpl::StartReading(
mojo::ScopedDataPipeProducerHandle producer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (out_stream_) {
receiver_.ReportBadMessage("Data pipe producer still open.");
return;
}
if (!bluetooth_socket_) {
receiver_.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()));
if (!read_pending_)
out_stream_watcher_.ArmOrNotify();
}
void BluetoothSerialPortImpl::ReadFromSocketAndWriteOut(
MojoResult result,
const mojo::HandleSignalsState& state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (result) {
case MOJO_RESULT_OK:
DCHECK(!read_pending_);
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::ResetPendingWriteBuffer() {
pending_write_buffer_ = base::span<char>();
}
void BluetoothSerialPortImpl::ResetReceiveBuffer() {
receive_buffer_size_ = 0;
receive_buffer_next_byte_pos_ = 0;
receive_buffer_.reset();
}
void BluetoothSerialPortImpl::ReadMore() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(out_stream_.is_valid());
DCHECK(!read_pending_);
base::span<uint8_t> buffer;
// The |buffer| is owned by |out_stream_|.
MojoResult result =
out_stream_->BeginWriteData(mojo::DataPipeProducerHandle::kNoSizeHint,
MOJO_WRITE_DATA_FLAG_NONE, buffer);
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;
}
if (receive_buffer_) {
const size_t num_remaining_bytes =
receive_buffer_size_ - receive_buffer_next_byte_pos_;
const size_t bytes_to_copy = std::min(num_remaining_bytes, buffer.size());
buffer.copy_prefix_from(receive_buffer_->span().subspan(
receive_buffer_next_byte_pos_, bytes_to_copy));
out_stream_->EndWriteData(bytes_to_copy);
if (bytes_to_copy == num_remaining_bytes) { // If copied the last byte.
ResetReceiveBuffer();
} else {
receive_buffer_next_byte_pos_ += bytes_to_copy;
}
out_stream_watcher_.ArmOrNotify();
return;
}
read_pending_ = true;
pending_write_buffer_ = base::as_writable_chars(buffer);
bluetooth_socket_->Receive(
base::checked_cast<int>(buffer.size()),
base::BindOnce(&BluetoothSerialPortImpl::OnBluetoothSocketReceive,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&BluetoothSerialPortImpl::OnBluetoothSocketReceiveError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothSerialPortImpl::OnBluetoothSocketReceive(
int num_bytes_received,
scoped_refptr<net::IOBuffer> receive_buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(num_bytes_received, 0);
DCHECK(receive_buffer->data());
const bool receive_completed_after_flush = pending_write_buffer_.empty();
read_pending_ = false;
if (receive_completed_after_flush) {
if (out_stream_) {
// The prior pipe was flushed while a read was in-flight. Now that
// StartReading() has been called, put this received data in the receive
// buffer which will prepend to the next read output and call ReadMore()
// to start consuming receive_buffer_.
receive_buffer_size_ = num_bytes_received;
receive_buffer_next_byte_pos_ = 0;
receive_buffer_ = std::move(receive_buffer);
ReadMore();
}
return;
}
// Note: Some platform implementations of BluetoothSocket::Receive ignore the
// buffer size parameter. This means that |num_bytes_received| could be
// larger than |pending_write_buffer_|. Check to avoid buffer overflow.
DCHECK(out_stream_);
size_t bytes_to_copy = std::min(pending_write_buffer_.size(),
static_cast<size_t>(num_bytes_received));
std::copy(receive_buffer->data(),
UNSAFE_TODO(receive_buffer->data() + bytes_to_copy),
pending_write_buffer_.data());
out_stream_->EndWriteData(bytes_to_copy);
if (pending_write_buffer_.size() < static_cast<size_t>(num_bytes_received)) {
receive_buffer_size_ = num_bytes_received;
receive_buffer_next_byte_pos_ = pending_write_buffer_.size();
receive_buffer_ = std::move(receive_buffer);
}
ResetPendingWriteBuffer();
ReadMore();
}
void BluetoothSerialPortImpl::OnBluetoothSocketReceiveError(
BluetoothSocket::ErrorReason error_reason,
const std::string& error_message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
read_pending_ = false;
ResetPendingWriteBuffer();
if (out_stream_) {
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();
case BluetoothSocket::ErrorReason::kSystemError:
client_->OnReadError(mojom::SerialReceiveError::SYSTEM_ERROR);
break;
}
}
out_stream_->EndWriteData(0);
out_stream_watcher_.Cancel();
out_stream_.reset();
ResetReceiveBuffer();
}
}
void BluetoothSerialPortImpl::WriteToSocket(
MojoResult result,
const mojo::HandleSignalsState& state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (result) {
case MOJO_RESULT_OK:
DCHECK(!write_pending_);
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_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(in_stream_.is_valid());
DCHECK(!write_pending_);
base::span<const uint8_t> buffer;
// |buffer| is owned by |in_stream_|.
MojoResult result =
in_stream_->BeginReadData(MOJO_WRITE_DATA_FLAG_NONE, buffer);
if (result == MOJO_RESULT_SHOULD_WAIT) {
in_stream_watcher_.ArmOrNotify();
return;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
in_stream_watcher_.Cancel();
in_stream_.reset();
if (drain_callback_)
std::move(drain_callback_).Run();
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;
// Copying the buffer because we might want to close in_stream_, thus
// invalidating |buffer|, which is passed to Send().
auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(buffer.size());
io_buffer->span().copy_from(buffer);
// The call to EndReadData() will be delayed until after Send() completes.
bluetooth_socket_->Send(
io_buffer, base::checked_cast<int>(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_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(num_bytes_sent, 0);
write_pending_ = false;
if (!flush_next_write_) {
DCHECK(in_stream_);
in_stream_->EndReadData(static_cast<uint32_t>(num_bytes_sent));
}
flush_next_write_ = false;
// If |in_stream_| is valid then StartWriting() has been called.
if (in_stream_) {
WriteMore();
}
}
void BluetoothSerialPortImpl::OnBluetoothSocketSendError(
const std::string& error_message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
write_pending_ = false;
flush_next_write_ = false;
if (in_stream_) {
if (client_)
client_->OnSendError(mojom::SerialSendError::SYSTEM_ERROR);
in_stream_->EndReadData(0);
in_stream_watcher_.Cancel();
in_stream_.reset();
}
}
void BluetoothSerialPortImpl::OnSocketDisconnected(CloseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(callback).Run();
bluetooth_socket_.reset(); // Avoid calling Disconnect() twice.
delete this;
}
void BluetoothSerialPortImpl::Flush(mojom::SerialPortFlushMode mode,
FlushCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (mode) {
case mojom::SerialPortFlushMode::kReceiveAndTransmit:
// Do nothing. This case exists to support the chrome.serial.flush()
// method and is not used by the Web Serial API.
break;
case mojom::SerialPortFlushMode::kReceive:
if (read_pending_)
out_stream_->EndWriteData(0);
out_stream_watcher_.Cancel();
out_stream_.reset();
ResetReceiveBuffer();
ResetPendingWriteBuffer();
break;
case mojom::SerialPortFlushMode::kTransmit:
if (write_pending_) {
flush_next_write_ = true;
in_stream_->EndReadData(0);
}
in_stream_watcher_.Cancel();
in_stream_.reset();
break;
}
std::move(callback).Run();
}
void BluetoothSerialPortImpl::Drain(DrainCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!in_stream_) {
std::move(callback).Run();
return;
}
drain_callback_ = std::move(callback);
}
void BluetoothSerialPortImpl::GetControlSignals(
GetControlSignalsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto signals = mojom::SerialPortControlSignals::New();
std::move(callback).Run(std::move(signals));
}
void BluetoothSerialPortImpl::SetControlSignals(
mojom::SerialHostControlSignalsPtr signals,
SetControlSignalsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(callback).Run(true);
}
void BluetoothSerialPortImpl::ConfigurePort(
mojom::SerialConnectionOptionsPtr options,
ConfigurePortCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
options_ = std::move(options);
std::move(callback).Run(true);
}
void BluetoothSerialPortImpl::GetPortInfo(GetPortInfoCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto info = mojom::SerialConnectionInfo::New(
/*bitrate=*/options_->bitrate, /*data_bits=*/options_->data_bits,
/*parity_bit=*/options_->parity_bit, /*stop_bits=*/options_->stop_bits,
/*cts_flow_control=*/options_->cts_flow_control);
std::move(callback).Run(std::move(info));
}
void BluetoothSerialPortImpl::Close(bool flush, CloseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// It is safe to ignore |flush| because we get the semantics of a flush simply
// by disconnecting the socket. There are no hardware buffers to clear.
bluetooth_socket_->Disconnect(
base::BindOnce(&BluetoothSerialPortImpl::OnSocketDisconnected,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
} // namespace device