blob: c11da0ea5146f62bdde91dc6d9fd95cee11daee6 [file] [log] [blame]
// Copyright 2016 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 "chromeos/services/secure_channel/ble_weave_client_connection.h"
#include <memory>
#include <sstream>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/timer/timer.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/secure_channel/wire_message.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
namespace chromeos {
namespace secure_channel {
namespace weave {
namespace {
typedef BluetoothLowEnergyWeavePacketReceiver::State ReceiverState;
// The UUID of the TX characteristic used to transmit data to the server.
const char kTXCharacteristicUUID[] = "00000100-0004-1000-8000-001A11000101";
// The UUID of the RX characteristic used to receive data from the server.
const char kRXCharacteristicUUID[] = "00000100-0004-1000-8000-001A11000102";
// If sending a message fails, retry up to 2 additional times. This means that
// each message gets 3 attempts: the first one, and 2 retries.
const int kMaxNumberOfRetryAttempts = 2;
// Timeouts for various status types.
const int kConnectionLatencyTimeoutSeconds = 2;
const int kGattConnectionTimeoutSeconds = 15;
const int kGattCharacteristicsTimeoutSeconds = 10;
const int kNotifySessionTimeoutSeconds = 5;
const int kConnectionResponseTimeoutSeconds = 2;
const int kSendingMessageTimeoutSeconds = 5;
} // namespace
// static
BluetoothLowEnergyWeaveClientConnection::Factory*
BluetoothLowEnergyWeaveClientConnection::Factory::factory_instance_ =
nullptr;
// static
std::unique_ptr<Connection>
BluetoothLowEnergyWeaveClientConnection::Factory::NewInstance(
multidevice::RemoteDeviceRef remote_device,
scoped_refptr<device::BluetoothAdapter> adapter,
const device::BluetoothUUID remote_service_uuid,
device::BluetoothDevice* bluetooth_device,
bool should_set_low_connection_latency) {
if (!factory_instance_) {
factory_instance_ = new Factory();
}
return factory_instance_->BuildInstance(remote_device, adapter,
remote_service_uuid, bluetooth_device,
should_set_low_connection_latency);
}
// static
void BluetoothLowEnergyWeaveClientConnection::Factory::SetInstanceForTesting(
Factory* factory) {
factory_instance_ = factory;
}
std::unique_ptr<Connection>
BluetoothLowEnergyWeaveClientConnection::Factory::BuildInstance(
multidevice::RemoteDeviceRef remote_device,
scoped_refptr<device::BluetoothAdapter> adapter,
const device::BluetoothUUID remote_service_uuid,
device::BluetoothDevice* bluetooth_device,
bool should_set_low_connection_latency) {
return std::make_unique<BluetoothLowEnergyWeaveClientConnection>(
remote_device, adapter, remote_service_uuid, bluetooth_device,
should_set_low_connection_latency);
}
// static
base::TimeDelta BluetoothLowEnergyWeaveClientConnection::GetTimeoutForSubStatus(
SubStatus sub_status) {
switch (sub_status) {
case SubStatus::WAITING_CONNECTION_RESPONSE:
return base::TimeDelta::FromSeconds(kConnectionResponseTimeoutSeconds);
case SubStatus::WAITING_CONNECTION_LATENCY:
return base::TimeDelta::FromSeconds(kConnectionLatencyTimeoutSeconds);
case SubStatus::WAITING_GATT_CONNECTION:
return base::TimeDelta::FromSeconds(kGattConnectionTimeoutSeconds);
case SubStatus::WAITING_CHARACTERISTICS:
return base::TimeDelta::FromSeconds(kGattCharacteristicsTimeoutSeconds);
case SubStatus::WAITING_NOTIFY_SESSION:
return base::TimeDelta::FromSeconds(kNotifySessionTimeoutSeconds);
case SubStatus::CONNECTED_AND_SENDING_MESSAGE:
return base::TimeDelta::FromSeconds(kSendingMessageTimeoutSeconds);
default:
// Max signifies that there should be no timeout.
return base::TimeDelta::Max();
}
}
// static
std::string BluetoothLowEnergyWeaveClientConnection::SubStatusToString(
SubStatus sub_status) {
switch (sub_status) {
case SubStatus::DISCONNECTED:
return "[disconnected]";
case SubStatus::WAITING_CONNECTION_LATENCY:
return "[waiting to set connection latency]";
case SubStatus::WAITING_GATT_CONNECTION:
return "[waiting for GATT connection to be created]";
case SubStatus::WAITING_CHARACTERISTICS:
return "[waiting for GATT characteristics to be found]";
case SubStatus::CHARACTERISTICS_FOUND:
return "[GATT characteristics have been found]";
case SubStatus::WAITING_NOTIFY_SESSION:
return "[waiting for notify session to begin]";
case SubStatus::NOTIFY_SESSION_READY:
return "[notify session is ready]";
case SubStatus::WAITING_CONNECTION_RESPONSE:
return "[waiting for \"connection response\" uWeave packet]";
case SubStatus::CONNECTED_AND_IDLE:
return "[connected and idle]";
case SubStatus::CONNECTED_AND_SENDING_MESSAGE:
return "[connected and sending message]";
default:
return "[invalid state]";
}
}
BluetoothLowEnergyWeaveClientConnection::
BluetoothLowEnergyWeaveClientConnection(
multidevice::RemoteDeviceRef device,
scoped_refptr<device::BluetoothAdapter> adapter,
const device::BluetoothUUID remote_service_uuid,
device::BluetoothDevice* bluetooth_device,
bool should_set_low_connection_latency)
: Connection(device),
bluetooth_device_(bluetooth_device),
should_set_low_connection_latency_(should_set_low_connection_latency),
adapter_(adapter),
remote_service_({remote_service_uuid, std::string()}),
packet_generator_(
std::make_unique<BluetoothLowEnergyWeavePacketGenerator>()),
packet_receiver_(std::make_unique<BluetoothLowEnergyWeavePacketReceiver>(
BluetoothLowEnergyWeavePacketReceiver::ReceiverType::CLIENT)),
tx_characteristic_(
{device::BluetoothUUID(kTXCharacteristicUUID), std::string()}),
rx_characteristic_(
{device::BluetoothUUID(kRXCharacteristicUUID), std::string()}),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
timer_(std::make_unique<base::OneShotTimer>()),
sub_status_(SubStatus::DISCONNECTED),
weak_ptr_factory_(this) {
adapter_->AddObserver(this);
}
BluetoothLowEnergyWeaveClientConnection::
~BluetoothLowEnergyWeaveClientConnection() {
if (sub_status() == SubStatus::WAITING_CONNECTION_RESPONSE || IsConnected()) {
// Deleting this object without calling Disconnect() may result in the
// connection staying active longer than intended, which can lead to errors.
// See https://crbug.com/763604.
PA_LOG(WARNING) << "Warning: Deleting "
<< "BluetoothLowEnergyWeaveClientConnection object with an "
<< "active connection to " << GetDeviceInfoLogString()
<< ". This may result in disconnection errors; trying to "
<< "send uWeave \"connection close\" packet before "
<< "deleting.";
ClearQueueAndSendConnectionClose();
}
DestroyConnection(
BleWeaveConnectionResult::BLE_WEAVE_CONNECTION_RESULT_CLOSED_NORMALLY);
}
void BluetoothLowEnergyWeaveClientConnection::Connect() {
DCHECK(sub_status() == SubStatus::DISCONNECTED);
if (should_set_low_connection_latency_)
SetConnectionLatency();
else
CreateGattConnection();
}
void BluetoothLowEnergyWeaveClientConnection::SetConnectionLatency() {
DCHECK(sub_status() == SubStatus::DISCONNECTED);
SetSubStatus(SubStatus::WAITING_CONNECTION_LATENCY);
device::BluetoothDevice* bluetooth_device = GetBluetoothDevice();
if (!bluetooth_device) {
PA_LOG(WARNING) << "Device not found; cannot set connection latency for "
<< GetDeviceInfoLogString() << ".";
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_BLUETOOTH_DEVICE_NOT_AVAILABLE);
return;
}
bluetooth_device->SetConnectionLatency(
device::BluetoothDevice::ConnectionLatency::CONNECTION_LATENCY_LOW,
base::BindRepeating(&BluetoothLowEnergyWeaveClientConnection::
OnSetConnectionLatencySuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&BluetoothLowEnergyWeaveClientConnection::
OnSetConnectionLatencyErrorOrTimeout,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothLowEnergyWeaveClientConnection::CreateGattConnection() {
DCHECK(sub_status() == SubStatus::DISCONNECTED ||
sub_status() == SubStatus::WAITING_CONNECTION_LATENCY);
SetSubStatus(SubStatus::WAITING_GATT_CONNECTION);
device::BluetoothDevice* bluetooth_device = GetBluetoothDevice();
if (!bluetooth_device) {
PA_LOG(WARNING) << "Device not found; cannot create GATT connection to "
<< GetDeviceInfoLogString() << ".";
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_BLUETOOTH_DEVICE_NOT_AVAILABLE);
return;
}
PA_LOG(INFO) << "Creating GATT connection with " << GetDeviceInfoLogString()
<< ".";
bluetooth_device->CreateGattConnection(
base::Bind(
&BluetoothLowEnergyWeaveClientConnection::OnGattConnectionCreated,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(
&BluetoothLowEnergyWeaveClientConnection::OnCreateGattConnectionError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothLowEnergyWeaveClientConnection::Disconnect() {
if (IsConnected()) {
// If a disconnection is already in progress, there is nothing to do.
if (has_triggered_disconnection_)
return;
has_triggered_disconnection_ = true;
PA_LOG(INFO) << "Disconnection requested; sending \"connection close\" "
<< "uWeave packet to " << GetDeviceInfoLogString() << ".";
// Send a "connection close" uWeave packet. After the send has completed,
// the connection will disconnect automatically.
ClearQueueAndSendConnectionClose();
return;
}
DestroyConnection(
BleWeaveConnectionResult::BLE_WEAVE_CONNECTION_RESULT_CLOSED_NORMALLY);
}
void BluetoothLowEnergyWeaveClientConnection::DestroyConnection(
BleWeaveConnectionResult result) {
if (!has_recorded_connection_result_) {
has_recorded_connection_result_ = true;
RecordBleWeaveConnectionResult(result);
}
if (adapter_) {
adapter_->RemoveObserver(this);
adapter_ = nullptr;
}
weak_ptr_factory_.InvalidateWeakPtrs();
notify_session_.reset();
characteristic_finder_.reset();
if (gatt_connection_) {
PA_LOG(INFO) << "Disconnecting from " << GetDeviceInfoLogString();
gatt_connection_.reset();
}
SetSubStatus(SubStatus::DISCONNECTED);
}
void BluetoothLowEnergyWeaveClientConnection::SetSubStatus(
SubStatus new_sub_status) {
sub_status_ = new_sub_status;
timer_->Stop();
base::TimeDelta timeout_for_sub_status = GetTimeoutForSubStatus(sub_status_);
if (!timeout_for_sub_status.is_max()) {
timer_->Start(
FROM_HERE, timeout_for_sub_status,
base::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::OnTimeoutForSubStatus,
weak_ptr_factory_.GetWeakPtr(), sub_status_));
}
// Sets the status of base class Connection.
switch (new_sub_status) {
case SubStatus::CONNECTED_AND_IDLE:
case SubStatus::CONNECTED_AND_SENDING_MESSAGE:
SetStatus(Status::CONNECTED);
break;
case SubStatus::DISCONNECTED:
SetStatus(Status::DISCONNECTED);
break;
default:
SetStatus(Status::IN_PROGRESS);
}
}
void BluetoothLowEnergyWeaveClientConnection::OnTimeoutForSubStatus(
SubStatus timed_out_sub_status) {
// Ensure that |timed_out_sub_status| is still the active status.
DCHECK(timed_out_sub_status == sub_status());
if (timed_out_sub_status == SubStatus::WAITING_CONNECTION_LATENCY) {
OnSetConnectionLatencyErrorOrTimeout();
return;
}
PA_LOG(ERROR) << "Timed out waiting during SubStatus "
<< SubStatusToString(timed_out_sub_status) << ". "
<< "Destroying connection.";
BleWeaveConnectionResult result =
BleWeaveConnectionResult::BLE_WEAVE_CONNECTION_RESULT_MAX;
switch (timed_out_sub_status) {
case SubStatus::WAITING_GATT_CONNECTION:
result = BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_CREATING_GATT_CONNECTION;
break;
case SubStatus::WAITING_CHARACTERISTICS:
result = BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_FINDING_GATT_CHARACTERISTICS;
break;
case SubStatus::WAITING_NOTIFY_SESSION:
result = BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_STARTING_NOTIFY_SESSION;
break;
case SubStatus::WAITING_CONNECTION_RESPONSE:
result = BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_WAITING_FOR_CONNECTION_RESPONSE;
break;
case SubStatus::CONNECTED_AND_SENDING_MESSAGE:
result = BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_WAITING_FOR_MESSAGE_TO_SEND;
break;
default:
NOTREACHED();
}
DestroyConnection(result);
}
void BluetoothLowEnergyWeaveClientConnection::SetupTestDoubles(
scoped_refptr<base::TaskRunner> test_task_runner,
std::unique_ptr<base::OneShotTimer> test_timer,
std::unique_ptr<BluetoothLowEnergyWeavePacketGenerator> test_generator,
std::unique_ptr<BluetoothLowEnergyWeavePacketReceiver> test_receiver) {
task_runner_ = test_task_runner;
timer_ = std::move(test_timer);
packet_generator_ = std::move(test_generator);
packet_receiver_ = std::move(test_receiver);
}
void BluetoothLowEnergyWeaveClientConnection::SendMessageImpl(
std::unique_ptr<WireMessage> message) {
DCHECK(IsConnected());
// Split |message| up into multiple packets which can be sent as one uWeave
// message.
std::vector<Packet> weave_packets =
packet_generator_->EncodeDataMessage(message->Serialize());
// For each packet, create a WriteRequest and add it to the queue.
for (uint32_t i = 0; i < weave_packets.size(); ++i) {
WriteRequestType request_type = (i != weave_packets.size() - 1)
? WriteRequestType::REGULAR
: WriteRequestType::MESSAGE_COMPLETE;
queued_write_requests_.emplace(std::make_unique<WriteRequest>(
weave_packets[i], request_type, message.get()));
}
// Add |message| to the queue of WireMessages.
queued_wire_messages_.emplace(std::move(message));
ProcessNextWriteRequest();
}
void BluetoothLowEnergyWeaveClientConnection::DeviceConnectedStateChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool is_now_connected) {
// Ignore updates about other devices.
if (device->GetAddress() != GetDeviceAddress())
return;
if (sub_status() == SubStatus::DISCONNECTED ||
sub_status() == SubStatus::WAITING_CONNECTION_LATENCY ||
sub_status() == SubStatus::WAITING_GATT_CONNECTION) {
// Ignore status change events if a connection has not yet occurred.
return;
}
// If a connection has already occurred and |device| is still connected, there
// is nothing to do.
if (is_now_connected)
return;
PA_LOG(WARNING) << "GATT connection to " << GetDeviceInfoLogString()
<< " has been dropped.";
DestroyConnection(BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_CONNECTION_DROPPED);
}
void BluetoothLowEnergyWeaveClientConnection::GattCharacteristicValueChanged(
device::BluetoothAdapter* adapter,
device::BluetoothRemoteGattCharacteristic* characteristic,
const Packet& value) {
DCHECK_EQ(adapter, adapter_.get());
// Ignore characteristics which do not apply to this connection.
if (characteristic->GetIdentifier() != rx_characteristic_.id)
return;
if (sub_status() != SubStatus::WAITING_CONNECTION_RESPONSE &&
!IsConnected()) {
PA_LOG(WARNING) << "Received message from " << GetDeviceInfoLogString()
<< ", but was not expecting one. sub_status() = "
<< sub_status();
return;
}
switch (packet_receiver_->ReceivePacket(value)) {
case ReceiverState::DATA_READY:
OnBytesReceived(packet_receiver_->GetDataMessage());
break;
case ReceiverState::CONNECTION_CLOSED:
PA_LOG(INFO) << "Received \"connection close\" uWeave packet from "
<< GetDeviceInfoLogString()
<< ". Reason: " << GetReasonForClose() << ".";
DestroyConnection(BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_CLOSED_NORMALLY);
return;
case ReceiverState::ERROR_DETECTED:
PA_LOG(ERROR) << "Received invalid packet from "
<< GetDeviceInfoLogString() << ". ";
ClearQueueAndSendConnectionClose();
break;
case ReceiverState::WAITING:
CompleteConnection();
break;
case ReceiverState::RECEIVING_DATA:
// Continue to wait for more packets to arrive; once the rest of the
// packets for this message are received, |packet_receiver_| will
// transition to the DATA_READY state.
break;
default:
NOTREACHED();
}
}
void BluetoothLowEnergyWeaveClientConnection::CompleteConnection() {
DCHECK(sub_status() == SubStatus::WAITING_CONNECTION_RESPONSE);
uint16_t max_packet_size = packet_receiver_->GetMaxPacketSize();
PA_LOG(INFO) << "Received uWeave \"connection response\" packet; connection "
<< "is now fully initialized for " << GetDeviceInfoLogString()
<< ". Max packet size: " << max_packet_size;
// Now that the "connection close" uWeave packet has been received,
// |packet_receiver_| should have received a max packet size from the GATT
// server.
packet_generator_->SetMaxPacketSize(max_packet_size);
SetSubStatus(SubStatus::CONNECTED_AND_IDLE);
}
void BluetoothLowEnergyWeaveClientConnection::OnSetConnectionLatencySuccess() {
// TODO(crbug.com/929518): Record how long it took to set connection latency.
// It's possible that we timed out when attempting to set the connection
// latency before this callback was called, resulting in this class moving
// forward with a GATT connection (i.e., in any state other than
// |SubStatus::WAITING_CONNECTION_LATENCY|). That could mean, at this point in
// time, that we're in the middle of connecting, already connected, or any
// state in between. That's fine; simply early return in order to prevent
// trying to create yet another GATT connection (which will fail).
if (sub_status() != SubStatus::WAITING_CONNECTION_LATENCY) {
PA_LOG(WARNING) << "Setting connection latency succeeded but GATT "
<< "connection to " << GetDeviceInfoLogString()
<< " is already in progress or complete.";
return;
}
CreateGattConnection();
}
void BluetoothLowEnergyWeaveClientConnection::
OnSetConnectionLatencyErrorOrTimeout() {
// TODO(crbug.com/929518): Record when setting connection latency fails or
// times out.
DCHECK(sub_status_ == SubStatus::WAITING_CONNECTION_LATENCY);
PA_LOG(WARNING)
<< "Error or timeout setting connection latency for connection to "
<< GetDeviceInfoLogString() << ".";
// Even if setting the connection latency fails, continue with the
// connection. This is unfortunate but should not be considered a fatal error.
CreateGattConnection();
}
void BluetoothLowEnergyWeaveClientConnection::OnCreateGattConnectionError(
device::BluetoothDevice::ConnectErrorCode error_code) {
DCHECK(sub_status_ == SubStatus::WAITING_GATT_CONNECTION);
RecordGattConnectionResult(
BluetoothDeviceConnectErrorCodeToGattConnectionResult(error_code));
PA_LOG(WARNING) << "Error creating GATT connection to "
<< GetDeviceInfoLogString() << ". Error code: " << error_code;
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_CREATING_GATT_CONNECTION);
}
void BluetoothLowEnergyWeaveClientConnection::OnGattConnectionCreated(
std::unique_ptr<device::BluetoothGattConnection> gatt_connection) {
DCHECK(sub_status() == SubStatus::WAITING_GATT_CONNECTION);
RecordGattConnectionResult(
GattConnectionResult::GATT_CONNECTION_RESULT_SUCCESS);
gatt_connection_ = std::move(gatt_connection);
SetSubStatus(SubStatus::WAITING_CHARACTERISTICS);
PA_LOG(INFO) << "Finding GATT characteristics for "
<< GetDeviceInfoLogString() << ".";
characteristic_finder_.reset(CreateCharacteristicsFinder(
base::Bind(
&BluetoothLowEnergyWeaveClientConnection::OnCharacteristicsFound,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&BluetoothLowEnergyWeaveClientConnection::
OnCharacteristicsFinderError,
weak_ptr_factory_.GetWeakPtr())));
}
BluetoothLowEnergyCharacteristicsFinder*
BluetoothLowEnergyWeaveClientConnection::CreateCharacteristicsFinder(
const BluetoothLowEnergyCharacteristicsFinder::SuccessCallback&
success_callback,
const BluetoothLowEnergyCharacteristicsFinder::ErrorCallback&
error_callback) {
return new BluetoothLowEnergyCharacteristicsFinder(
adapter_, GetBluetoothDevice(), remote_service_, tx_characteristic_,
rx_characteristic_, success_callback, error_callback);
}
void BluetoothLowEnergyWeaveClientConnection::OnCharacteristicsFound(
const RemoteAttribute& service,
const RemoteAttribute& tx_characteristic,
const RemoteAttribute& rx_characteristic) {
DCHECK(sub_status() == SubStatus::WAITING_CHARACTERISTICS);
remote_service_ = service;
tx_characteristic_ = tx_characteristic;
rx_characteristic_ = rx_characteristic;
characteristic_finder_.reset();
SetSubStatus(SubStatus::CHARACTERISTICS_FOUND);
StartNotifySession();
}
void BluetoothLowEnergyWeaveClientConnection::OnCharacteristicsFinderError(
const RemoteAttribute& tx_characteristic,
const RemoteAttribute& rx_characteristic) {
DCHECK(sub_status() == SubStatus::WAITING_CHARACTERISTICS);
std::stringstream ss;
ss << "Could not find GATT characteristics for " << GetDeviceInfoLogString()
<< ": ";
if (tx_characteristic.id.empty()) {
ss << "[TX: " << tx_characteristic.uuid.canonical_value() << "]";
if (rx_characteristic.id.empty())
ss << ", ";
}
if (rx_characteristic.id.empty())
ss << "[RX: " << rx_characteristic.uuid.canonical_value() << "]";
PA_LOG(ERROR) << ss.str();
characteristic_finder_.reset();
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_FINDING_GATT_CHARACTERISTICS);
}
void BluetoothLowEnergyWeaveClientConnection::StartNotifySession() {
DCHECK(sub_status() == SubStatus::CHARACTERISTICS_FOUND);
device::BluetoothRemoteGattCharacteristic* characteristic =
GetGattCharacteristic(rx_characteristic_.id);
if (!characteristic) {
PA_LOG(ERROR) << "Characteristic no longer available after it was found. "
<< "Cannot start notification session for "
<< GetDeviceInfoLogString() << ".";
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_GATT_CHARACTERISTIC_NOT_AVAILABLE);
return;
}
// Workaround for crbug.com/507325. If |characteristic| is already notifying,
// characteristic->StartNotifySession() fails with GATT_ERROR_FAILED.
if (characteristic->IsNotifying()) {
SetSubStatus(SubStatus::NOTIFY_SESSION_READY);
SendConnectionRequest();
return;
}
SetSubStatus(SubStatus::WAITING_NOTIFY_SESSION);
PA_LOG(INFO) << "Starting notification session for "
<< GetDeviceInfoLogString() << ".";
characteristic->StartNotifySession(
base::Bind(
&BluetoothLowEnergyWeaveClientConnection::OnNotifySessionStarted,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&BluetoothLowEnergyWeaveClientConnection::OnNotifySessionError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothLowEnergyWeaveClientConnection::OnNotifySessionStarted(
std::unique_ptr<device::BluetoothGattNotifySession> notify_session) {
DCHECK(sub_status() == SubStatus::WAITING_NOTIFY_SESSION);
RecordGattNotifySessionResult(
GattServiceOperationResult::GATT_SERVICE_OPERATION_RESULT_SUCCESS);
notify_session_ = std::move(notify_session);
SetSubStatus(SubStatus::NOTIFY_SESSION_READY);
SendConnectionRequest();
}
void BluetoothLowEnergyWeaveClientConnection::OnNotifySessionError(
device::BluetoothRemoteGattService::GattErrorCode error) {
DCHECK(sub_status() == SubStatus::WAITING_NOTIFY_SESSION);
RecordGattNotifySessionResult(
BluetoothRemoteDeviceGattServiceGattErrorCodeToGattServiceOperationResult(
error));
PA_LOG(ERROR) << "Cannot start notification session for "
<< GetDeviceInfoLogString() << ". Error: " << error << ".";
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_STARTING_NOTIFY_SESSION);
}
void BluetoothLowEnergyWeaveClientConnection::SendConnectionRequest() {
DCHECK(sub_status() == SubStatus::NOTIFY_SESSION_READY);
SetSubStatus(SubStatus::WAITING_CONNECTION_RESPONSE);
PA_LOG(INFO) << "Sending \"connection request\" uWeave packet to "
<< GetDeviceInfoLogString();
queued_write_requests_.emplace(std::make_unique<WriteRequest>(
packet_generator_->CreateConnectionRequest(),
WriteRequestType::CONNECTION_REQUEST));
ProcessNextWriteRequest();
}
void BluetoothLowEnergyWeaveClientConnection::ProcessNextWriteRequest() {
// If there is already an in-progress write or there are no pending
// WriteRequests, there is nothing to do.
if (pending_write_request_ || queued_write_requests_.empty())
return;
pending_write_request_ = std::move(queued_write_requests_.front());
queued_write_requests_.pop();
PA_LOG(INFO) << "Writing " << pending_write_request_->value.size() << " "
<< "bytes to " << GetDeviceInfoLogString() << ".";
SendPendingWriteRequest();
}
void BluetoothLowEnergyWeaveClientConnection::SendPendingWriteRequest() {
DCHECK(pending_write_request_);
device::BluetoothRemoteGattCharacteristic* characteristic =
GetGattCharacteristic(tx_characteristic_.id);
if (!characteristic) {
PA_LOG(ERROR) << "Characteristic no longer available after it was found. "
<< "Cannot process write request for "
<< GetDeviceInfoLogString() << ".";
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_GATT_CHARACTERISTIC_NOT_AVAILABLE);
return;
}
// If the current status is CONNECTED_AND_IDLE, transition to
// CONNECTED_AND_SENDING_MESSAGE. This function also runs when the status is
// WAITING_CONNECTION_RESPONSE; in that case, the status should remain
// unchanged until the connection response has been received.
if (sub_status() == SubStatus::CONNECTED_AND_IDLE)
SetSubStatus(SubStatus::CONNECTED_AND_SENDING_MESSAGE);
characteristic->WriteRemoteCharacteristic(
pending_write_request_->value,
base::Bind(&BluetoothLowEnergyWeaveClientConnection::
OnRemoteCharacteristicWritten,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&BluetoothLowEnergyWeaveClientConnection::
OnWriteRemoteCharacteristicError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothLowEnergyWeaveClientConnection::OnRemoteCharacteristicWritten() {
DCHECK(sub_status() == SubStatus::WAITING_CONNECTION_RESPONSE ||
sub_status() == SubStatus::CONNECTED_AND_SENDING_MESSAGE);
if (sub_status() == SubStatus::CONNECTED_AND_SENDING_MESSAGE)
SetSubStatus(SubStatus::CONNECTED_AND_IDLE);
RecordGattWriteCharacteristicResult(
GattServiceOperationResult::GATT_SERVICE_OPERATION_RESULT_SUCCESS);
if (!pending_write_request_) {
PA_LOG(ERROR) << "OnRemoteCharacteristicWritten() called, but no pending "
<< "WriteRequest. Stopping connection to "
<< GetDeviceInfoLogString();
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_WRITE_QUEUE_OUT_OF_SYNC);
return;
}
if (pending_write_request_->request_type ==
WriteRequestType::CONNECTION_CLOSE) {
// Once a "connection close" uWeave packet has been sent, the connection
// is ready to be disconnected.
PA_LOG(INFO) << "uWeave \"connection close\" packet sent to "
<< GetDeviceInfoLogString() << ". Destroying connection.";
DestroyConnection(
BleWeaveConnectionResult::BLE_WEAVE_CONNECTION_RESULT_CLOSED_NORMALLY);
return;
}
if (pending_write_request_->request_type ==
WriteRequestType::MESSAGE_COMPLETE) {
if (queued_wire_messages_.empty()) {
PA_LOG(ERROR) << "Sent a WriteRequest with type == MESSAGE_COMPLETE, but "
<< "there were no queued WireMessages. Cannot process "
<< "completed write to " << GetDeviceInfoLogString();
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_WRITING_GATT_CHARACTERISTIC);
return;
}
std::unique_ptr<WireMessage> sent_message =
std::move(queued_wire_messages_.front());
queued_wire_messages_.pop();
DCHECK_EQ(sent_message.get(),
pending_write_request_->associated_wire_message);
// Notify observers of the message being sent via a task on the run loop to
// ensure that if an observer deletes this object in response to receiving
// the OnSendCompleted() callback, a null pointer is not deferenced.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::OnDidSendMessage,
weak_ptr_factory_.GetWeakPtr(), *sent_message, true /* success */));
}
pending_write_request_.reset();
ProcessNextWriteRequest();
}
void BluetoothLowEnergyWeaveClientConnection::OnWriteRemoteCharacteristicError(
device::BluetoothRemoteGattService::GattErrorCode error) {
DCHECK(sub_status() == SubStatus::WAITING_CONNECTION_RESPONSE ||
sub_status() == SubStatus::CONNECTED_AND_SENDING_MESSAGE);
if (sub_status() == SubStatus::CONNECTED_AND_SENDING_MESSAGE)
SetSubStatus(SubStatus::CONNECTED_AND_IDLE);
RecordGattWriteCharacteristicResult(
BluetoothRemoteDeviceGattServiceGattErrorCodeToGattServiceOperationResult(
error));
if (!pending_write_request_) {
PA_LOG(ERROR) << "OnWriteRemoteCharacteristicError() called, but no "
<< "pending WriteRequest. Stopping connection to "
<< GetDeviceInfoLogString();
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_WRITE_QUEUE_OUT_OF_SYNC);
return;
}
++pending_write_request_->number_of_failed_attempts;
if (pending_write_request_->number_of_failed_attempts <
kMaxNumberOfRetryAttempts + 1) {
PA_LOG(WARNING) << "Error sending WriteRequest to "
<< GetDeviceInfoLogString() << "; failure number "
<< pending_write_request_->number_of_failed_attempts
<< ". Retrying.";
SendPendingWriteRequest();
return;
}
if (pending_write_request_->request_type == WriteRequestType::REGULAR ||
pending_write_request_->request_type ==
WriteRequestType::MESSAGE_COMPLETE) {
std::unique_ptr<WireMessage> failed_message =
std::move(queued_wire_messages_.front());
queued_wire_messages_.pop();
DCHECK_EQ(failed_message.get(),
pending_write_request_->associated_wire_message);
OnDidSendMessage(*failed_message, false /* success */);
}
// Since the try limit has been hit, this is a fatal error. Destroy the
// connection, but post it as a new task to ensure that observers have a
// chance to process the OnSendCompleted() call.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::DestroyConnection,
weak_ptr_factory_.GetWeakPtr(),
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_WRITING_GATT_CHARACTERISTIC));
}
void BluetoothLowEnergyWeaveClientConnection::OnDidSendMessage(
const WireMessage& message,
bool success) {
Connection::OnDidSendMessage(message, success);
}
void BluetoothLowEnergyWeaveClientConnection::
ClearQueueAndSendConnectionClose() {
DCHECK(sub_status() == SubStatus::WAITING_CONNECTION_RESPONSE ||
IsConnected());
// The connection is now in an invalid state. Clear queued writes.
while (!queued_write_requests_.empty())
queued_write_requests_.pop();
// Now, queue up a "connection close" uWeave packet. If there was a pending
// write, we must wait for it to complete before the "connection close" can
// be sent.
queued_write_requests_.emplace(
std::make_unique<WriteRequest>(packet_generator_->CreateConnectionClose(
packet_receiver_->GetReasonToClose()),
WriteRequestType::CONNECTION_CLOSE));
if (pending_write_request_) {
PA_LOG(WARNING) << "Waiting for current write to complete, then will send "
<< "a \"connection close\" uWeave packet to "
<< GetDeviceInfoLogString() << ".";
} else {
PA_LOG(INFO) << "Sending a \"connection close\" uWeave packet to "
<< GetDeviceInfoLogString() << ".";
}
ProcessNextWriteRequest();
}
std::string BluetoothLowEnergyWeaveClientConnection::GetDeviceAddress() {
// When the remote device is connected, rely on the address given by
// |gatt_connection_|. Unpaired BLE device addresses are ephemeral and are
// expected to change periodically.
return gatt_connection_ ? gatt_connection_->GetDeviceAddress()
: bluetooth_device_->GetAddress();
}
void BluetoothLowEnergyWeaveClientConnection::GetConnectionRssi(
base::OnceCallback<void(base::Optional<int32_t>)> callback) {
device::BluetoothDevice* device = GetBluetoothDevice();
if (!device || !device->IsConnected()) {
std::move(callback).Run(base::nullopt);
return;
}
// device::BluetoothDevice has not converted to using a base::OnceCallback
// instead of a base::Callback, so use a wrapper for now.
auto callback_holder = base::AdaptCallbackForRepeating(std::move(callback));
device->GetConnectionInfo(
base::Bind(&BluetoothLowEnergyWeaveClientConnection::OnConnectionInfo,
weak_ptr_factory_.GetWeakPtr(), callback_holder));
}
void BluetoothLowEnergyWeaveClientConnection::OnConnectionInfo(
base::RepeatingCallback<void(base::Optional<int32_t>)> rssi_callback,
const device::BluetoothDevice::ConnectionInfo& connection_info) {
if (connection_info.rssi == device::BluetoothDevice::kUnknownPower) {
std::move(rssi_callback).Run(base::nullopt);
return;
}
std::move(rssi_callback).Run(connection_info.rssi);
}
device::BluetoothDevice*
BluetoothLowEnergyWeaveClientConnection::GetBluetoothDevice() {
return bluetooth_device_;
}
device::BluetoothRemoteGattService*
BluetoothLowEnergyWeaveClientConnection::GetRemoteService() {
device::BluetoothDevice* bluetooth_device = GetBluetoothDevice();
if (!bluetooth_device) {
PA_LOG(WARNING) << "Cannot find Bluetooth device for "
<< GetDeviceInfoLogString();
return nullptr;
}
if (remote_service_.id.empty()) {
for (auto* service : bluetooth_device->GetGattServices()) {
if (service->GetUUID() == remote_service_.uuid) {
remote_service_.id = service->GetIdentifier();
break;
}
}
}
return bluetooth_device->GetGattService(remote_service_.id);
}
device::BluetoothRemoteGattCharacteristic*
BluetoothLowEnergyWeaveClientConnection::GetGattCharacteristic(
const std::string& gatt_characteristic) {
device::BluetoothRemoteGattService* remote_service = GetRemoteService();
if (!remote_service) {
PA_LOG(WARNING) << "Cannot find GATT service for "
<< GetDeviceInfoLogString();
return nullptr;
}
return remote_service->GetCharacteristic(gatt_characteristic);
}
std::string BluetoothLowEnergyWeaveClientConnection::GetReasonForClose() {
switch (packet_receiver_->GetReasonForClose()) {
case ReasonForClose::CLOSE_WITHOUT_ERROR:
return "CLOSE_WITHOUT_ERROR";
case ReasonForClose::UNKNOWN_ERROR:
return "UNKNOWN_ERROR";
case ReasonForClose::NO_COMMON_VERSION_SUPPORTED:
return "NO_COMMON_VERSION_SUPPORTED";
case ReasonForClose::RECEIVED_PACKET_OUT_OF_SEQUENCE:
return "RECEIVED_PACKET_OUT_OF_SEQUENCE";
case ReasonForClose::APPLICATION_ERROR:
return "APPLICATION_ERROR";
default:
NOTREACHED();
return "";
}
}
void BluetoothLowEnergyWeaveClientConnection::RecordBleWeaveConnectionResult(
BleWeaveConnectionResult result) {
UMA_HISTOGRAM_ENUMERATION(
"ProximityAuth.BleWeaveConnectionResult", result,
BleWeaveConnectionResult::BLE_WEAVE_CONNECTION_RESULT_MAX);
}
void BluetoothLowEnergyWeaveClientConnection::RecordGattConnectionResult(
GattConnectionResult result) {
UMA_HISTOGRAM_ENUMERATION("ProximityAuth.BluetoothGattConnectionResult",
result,
GattConnectionResult::GATT_CONNECTION_RESULT_MAX);
}
BluetoothLowEnergyWeaveClientConnection::GattConnectionResult
BluetoothLowEnergyWeaveClientConnection::
BluetoothDeviceConnectErrorCodeToGattConnectionResult(
device::BluetoothDevice::ConnectErrorCode error_code) {
switch (error_code) {
case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_AUTH_CANCELED;
case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_FAILED:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_AUTH_FAILED;
case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_AUTH_REJECTED;
case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_TIMEOUT:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_AUTH_TIMEOUT;
case device::BluetoothDevice::ConnectErrorCode::ERROR_FAILED:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_FAILED;
case device::BluetoothDevice::ConnectErrorCode::ERROR_INPROGRESS:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_INPROGRESS;
case device::BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN:
return GattConnectionResult::GATT_CONNECTION_RESULT_ERROR_UNKNOWN;
case device::BluetoothDevice::ConnectErrorCode::ERROR_UNSUPPORTED_DEVICE:
return GattConnectionResult::
GATT_CONNECTION_RESULT_ERROR_UNSUPPORTED_DEVICE;
default:
return GattConnectionResult::GATT_CONNECTION_RESULT_UNKNOWN;
}
}
void BluetoothLowEnergyWeaveClientConnection::RecordGattNotifySessionResult(
GattServiceOperationResult result) {
UMA_HISTOGRAM_ENUMERATION(
"ProximityAuth.BluetoothGattNotifySessionResult", result,
GattServiceOperationResult::GATT_SERVICE_OPERATION_RESULT_MAX);
}
void BluetoothLowEnergyWeaveClientConnection::
RecordGattWriteCharacteristicResult(GattServiceOperationResult result) {
UMA_HISTOGRAM_ENUMERATION(
"ProximityAuth.BluetoothGattWriteCharacteristicResult", result,
GattServiceOperationResult::GATT_SERVICE_OPERATION_RESULT_MAX);
}
BluetoothLowEnergyWeaveClientConnection::GattServiceOperationResult
BluetoothLowEnergyWeaveClientConnection::
BluetoothRemoteDeviceGattServiceGattErrorCodeToGattServiceOperationResult(
device::BluetoothRemoteGattService::GattErrorCode error_code) {
switch (error_code) {
case device::BluetoothRemoteGattService::GattErrorCode::GATT_ERROR_UNKNOWN:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_UNKNOWN;
case device::BluetoothRemoteGattService::GattErrorCode::GATT_ERROR_FAILED:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_FAILED;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_IN_PROGRESS:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_IN_PROGRESS;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_INVALID_LENGTH:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_INVALID_LENGTH;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_NOT_PERMITTED:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_PERMITTED;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_NOT_AUTHORIZED:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_AUTHORIZED;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_NOT_PAIRED:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_PAIRED;
case device::BluetoothRemoteGattService::GattErrorCode::
GATT_ERROR_NOT_SUPPORTED:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_SUPPORTED;
default:
return GattServiceOperationResult::GATT_SERVICE_OPERATION_RESULT_UNKNOWN;
}
}
BluetoothLowEnergyWeaveClientConnection::WriteRequest::WriteRequest(
const Packet& val,
WriteRequestType request_type,
WireMessage* associated_wire_message)
: value(val),
request_type(request_type),
associated_wire_message(associated_wire_message) {}
BluetoothLowEnergyWeaveClientConnection::WriteRequest::WriteRequest(
const Packet& val,
WriteRequestType request_type)
: WriteRequest(val, request_type, nullptr /* associated_wire_message */) {}
BluetoothLowEnergyWeaveClientConnection::WriteRequest::~WriteRequest() {}
} // namespace weave
} // namespace secure_channel
} // namespace chromeos