// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/services/secure_channel/ble_weave_client_connection.h"
#include <memory>
#include <sstream>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/secure_channel/background_eid_generator.h"
#include "chromeos/ash/services/secure_channel/ble_weave_packet_generator.h"
#include "chromeos/ash/services/secure_channel/ble_weave_packet_receiver.h"
#include "chromeos/ash/services/secure_channel/file_transfer_update_callback.h"
#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel_types.mojom.h"
#include "chromeos/ash/services/secure_channel/wire_message.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
namespace ash::secure_channel::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::Create(
multidevice::RemoteDeviceRef remote_device,
scoped_refptr<device::BluetoothAdapter> adapter,
const device::BluetoothUUID remote_service_uuid,
const std::string& device_address,
bool should_set_low_connection_latency) {
if (factory_instance_) {
return factory_instance_->CreateInstance(
remote_device, adapter, remote_service_uuid, device_address,
should_set_low_connection_latency);
}
return std::make_unique<BluetoothLowEnergyWeaveClientConnection>(
remote_device, adapter, remote_service_uuid, device_address,
should_set_low_connection_latency);
}
// static
void BluetoothLowEnergyWeaveClientConnection::Factory::SetFactoryForTesting(
Factory* factory) {
factory_instance_ = factory;
}
// static
base::TimeDelta BluetoothLowEnergyWeaveClientConnection::GetTimeoutForSubStatus(
SubStatus sub_status) {
switch (sub_status) {
case SubStatus::WAITING_CONNECTION_RESPONSE:
return base::Seconds(kConnectionResponseTimeoutSeconds);
case SubStatus::WAITING_CONNECTION_LATENCY:
return base::Seconds(kConnectionLatencyTimeoutSeconds);
case SubStatus::WAITING_GATT_CONNECTION:
return base::Seconds(kGattConnectionTimeoutSeconds);
case SubStatus::WAITING_CHARACTERISTICS:
return base::Seconds(kGattCharacteristicsTimeoutSeconds);
case SubStatus::WAITING_NOTIFY_SESSION:
return base::Seconds(kNotifySessionTimeoutSeconds);
case SubStatus::CONNECTED_AND_SENDING_MESSAGE:
return base::Seconds(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,
const std::string& device_address,
bool should_set_low_connection_latency)
: Connection(device),
initial_device_address_(device_address),
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::SingleThreadTaskRunner::GetCurrentDefault()),
timer_(std::make_unique<base::OneShotTimer>()),
sub_status_(SubStatus::DISCONNECTED) {
DCHECK(!initial_device_address_.empty());
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::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::OnGattConnectionCreated,
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_IN_MIGRATION();
}
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::kRegular
: WriteRequestType::kMessageComplete;
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::RegisterPayloadFileImpl(
int64_t payload_id,
mojom::PayloadFilesPtr payload_files,
FileTransferUpdateCallback file_transfer_update_callback,
base::OnceCallback<void(bool)> registration_result_callback) {
// Currently the only user of this API is Phone Hub, which only works over
// Nearby Connections.
PA_LOG(WARNING)
<< "RegisterPayloadFile is not supported over BLE connections.";
std::move(registration_result_callback).Run(/*success=*/false);
}
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_IN_MIGRATION();
}
}
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::OnGattConnectionCreated(
std::unique_ptr<device::BluetoothGattConnection> gatt_connection,
std::optional<device::BluetoothDevice::ConnectErrorCode> error_code) {
DCHECK(sub_status() == SubStatus::WAITING_GATT_CONNECTION);
if (error_code.has_value()) {
RecordGattConnectionResult(
BluetoothDeviceConnectErrorCodeToGattConnectionResult(
error_code.value()));
PA_LOG(WARNING) << "Error creating GATT connection to "
<< GetDeviceInfoLogString()
<< ". Error code: " << error_code.value();
DestroyConnection(
BleWeaveConnectionResult::
BLE_WEAVE_CONNECTION_RESULT_ERROR_CREATING_GATT_CONNECTION);
return;
}
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::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::OnCharacteristicsFound,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&BluetoothLowEnergyWeaveClientConnection::
OnCharacteristicsFinderError,
weak_ptr_factory_.GetWeakPtr())));
}
BluetoothLowEnergyCharacteristicsFinder*
BluetoothLowEnergyWeaveClientConnection::CreateCharacteristicsFinder(
BluetoothLowEnergyCharacteristicsFinder::SuccessCallback success_callback,
base::OnceClosure error_callback) {
return new BluetoothLowEnergyCharacteristicsFinder(
adapter_, GetBluetoothDevice(), remote_service_, tx_characteristic_,
rx_characteristic_, std::move(success_callback),
std::move(error_callback), remote_device(),
std::make_unique<BackgroundEidGenerator>());
}
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() {
DCHECK(sub_status() == SubStatus::WAITING_CHARACTERISTICS);
PA_LOG(ERROR) << "Could not find GATT characteristics for "
<< GetDeviceInfoLogString();
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::BindOnce(
&BluetoothLowEnergyWeaveClientConnection::OnNotifySessionStarted,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(
&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::BluetoothGattService::GattErrorCode error) {
DCHECK(sub_status() == SubStatus::WAITING_NOTIFY_SESSION);
RecordGattNotifySessionResult(
BluetoothRemoteDeviceGattServiceGattErrorCodeToGattServiceOperationResult(
error));
PA_LOG(ERROR) << "Cannot start notification session for "
<< GetDeviceInfoLogString()
<< ". Error: " << static_cast<int>(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::kConnectionRequest));
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);
// Note: the Android implementation of this GATT characteristic does not
// support kWithoutResponse; we must specify kWithResponse.
characteristic->WriteRemoteCharacteristic(
pending_write_request_->value,
device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
base::BindOnce(&BluetoothLowEnergyWeaveClientConnection::
OnRemoteCharacteristicWritten,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&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::kConnectionClose) {
// 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::kMessageComplete) {
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::BluetoothGattService::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::kRegular ||
pending_write_request_->request_type ==
WriteRequestType::kMessageComplete) {
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::kConnectionClose));
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()
: initial_device_address_;
}
void BluetoothLowEnergyWeaveClientConnection::GetConnectionRssi(
base::OnceCallback<void(std::optional<int32_t>)> callback) {
device::BluetoothDevice* bluetooth_device = GetBluetoothDevice();
if (!bluetooth_device || !bluetooth_device->IsConnected()) {
std::move(callback).Run(std::nullopt);
return;
}
bluetooth_device->GetConnectionInfo(
base::BindOnce(&BluetoothLowEnergyWeaveClientConnection::OnConnectionInfo,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void BluetoothLowEnergyWeaveClientConnection::OnConnectionInfo(
base::OnceCallback<void(std::optional<int32_t>)> rssi_callback,
const device::BluetoothDevice::ConnectionInfo& connection_info) {
if (connection_info.rssi == device::BluetoothDevice::kUnknownPower) {
std::move(rssi_callback).Run(std::nullopt);
return;
}
std::move(rssi_callback).Run(connection_info.rssi);
}
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);
}
device::BluetoothDevice*
BluetoothLowEnergyWeaveClientConnection::GetBluetoothDevice() {
return adapter_ ? adapter_->GetDevice(GetDeviceAddress()) : nullptr;
}
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_IN_MIGRATION();
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::BluetoothGattService::GattErrorCode error_code) {
switch (error_code) {
case device::BluetoothGattService::GattErrorCode::kUnknown:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_UNKNOWN;
case device::BluetoothGattService::GattErrorCode::kFailed:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_FAILED;
case device::BluetoothGattService::GattErrorCode::kInProgress:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_IN_PROGRESS;
case device::BluetoothGattService::GattErrorCode::kInvalidLength:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_INVALID_LENGTH;
case device::BluetoothGattService::GattErrorCode::kNotPermitted:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_PERMITTED;
case device::BluetoothGattService::GattErrorCode::kNotAuthorized:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_AUTHORIZED;
case device::BluetoothGattService::GattErrorCode::kNotPaired:
return GattServiceOperationResult::
GATT_SERVICE_OPERATION_RESULT_GATT_ERROR_NOT_PAIRED;
case device::BluetoothGattService::GattErrorCode::kNotSupported:
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 ash::secure_channel::weave