blob: 5e65e53c785e6d11ff442734f8418ffcaf516f71 [file] [log] [blame]
// Copyright 2017 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/components/tether/message_transfer_operation.h"
#include <memory>
#include <set>
#include "base/metrics/histogram_macros.h"
#include "chromeos/components/tether/message_wrapper.h"
#include "chromeos/components/tether/timer_factory.h"
#include "components/proximity_auth/logging/logging.h"
namespace chromeos {
namespace tether {
namespace {
std::vector<cryptauth::RemoteDevice> RemoveDuplicatesFromVector(
const std::vector<cryptauth::RemoteDevice>& remote_devices) {
std::vector<cryptauth::RemoteDevice> updated_remote_devices;
std::set<cryptauth::RemoteDevice> remote_devices_set;
for (const auto& remote_device : remote_devices) {
// Only add the device to the output vector if it has not already been put
// into the set.
if (remote_devices_set.find(remote_device) == remote_devices_set.end()) {
remote_devices_set.insert(remote_device);
updated_remote_devices.push_back(remote_device);
}
}
return updated_remote_devices;
}
} // namespace
// static
const uint32_t MessageTransferOperation::kMaxEmptyScansPerDevice = 3;
// static
const uint32_t MessageTransferOperation::kMaxGattConnectionAttemptsPerDevice =
6;
MessageTransferOperation::MessageTransferOperation(
const std::vector<cryptauth::RemoteDevice>& devices_to_connect,
BleConnectionManager* connection_manager)
: remote_devices_(RemoveDuplicatesFromVector(devices_to_connect)),
connection_manager_(connection_manager),
timer_factory_(std::make_unique<TimerFactory>()),
weak_ptr_factory_(this) {}
MessageTransferOperation::~MessageTransferOperation() {
connection_manager_->RemoveObserver(this);
// If initialization never occurred, devices were never registered.
if (!initialized_)
return;
shutting_down_ = true;
// Unregister any devices that are still registered; otherwise, Bluetooth
// connections will continue to stay alive until the Tether component is shut
// down (see crbug.com/761106). Note that a copy of |remote_devices_| is used
// here because UnregisterDevice() will modify |remote_devices_| internally.
std::vector<cryptauth::RemoteDevice> remote_devices_copy = remote_devices_;
for (const auto& remote_device : remote_devices_copy)
UnregisterDevice(remote_device);
}
void MessageTransferOperation::Initialize() {
if (initialized_) {
return;
}
initialized_ = true;
// Store the message type for this connection as a private field. This is
// necessary because when UnregisterDevice() is called in the destructor, the
// derived class has already been destroyed, so invoking
// GetMessageTypeForConnection() will fail due to it being a pure virtual
// function at that time.
message_type_for_connection_ = GetMessageTypeForConnection();
connection_manager_->AddObserver(this);
OnOperationStarted();
for (const auto& remote_device : remote_devices_) {
connection_manager_->RegisterRemoteDevice(remote_device.GetDeviceId(),
message_type_for_connection_);
cryptauth::SecureChannel::Status status;
if (connection_manager_->GetStatusForDevice(remote_device.GetDeviceId(),
&status) &&
status == cryptauth::SecureChannel::Status::AUTHENTICATED) {
StartTimerForDevice(remote_device);
OnDeviceAuthenticated(remote_device);
}
}
}
void MessageTransferOperation::OnSecureChannelStatusChanged(
const std::string& device_id,
const cryptauth::SecureChannel::Status& old_status,
const cryptauth::SecureChannel::Status& new_status,
BleConnectionManager::StateChangeDetail status_change_detail) {
cryptauth::RemoteDevice* remote_device = GetRemoteDevice(device_id);
if (!remote_device) {
// If the device whose status has changed does not correspond to any of the
// devices passed to this MessageTransferOperation instance, ignore the
// status change.
return;
}
switch (new_status) {
case cryptauth::SecureChannel::Status::AUTHENTICATED:
UMA_HISTOGRAM_BOOLEAN(
"InstantTethering.GattConnectionAttempt.SuccessRate", true);
UMA_HISTOGRAM_BOOLEAN(
"InstantTethering.GattConnectionAttempt."
"EffectiveSuccessRateWithRetries",
true);
StartTimerForDevice(*remote_device);
OnDeviceAuthenticated(*remote_device);
break;
case cryptauth::SecureChannel::Status::DISCONNECTED:
HandleDeviceDisconnection(*remote_device, status_change_detail);
break;
default:
// Note: In success cases, the channel advances from DISCONNECTED to
// CONNECTING to CONNECTED to AUTHENTICATING to AUTHENTICATED. If the
// channel fails to advance at any of those stages, it transitions back to
// DISCONNECTED and starts over. There is no need for special handling for
// any of these interim states since they will eventually progress to
// either AUTHENTICATED or DISCONNECTED.
break;
}
}
void MessageTransferOperation::OnMessageReceived(const std::string& device_id,
const std::string& payload) {
cryptauth::RemoteDevice* remote_device = GetRemoteDevice(device_id);
if (!remote_device) {
// If the device from which the message has been received does not
// correspond to any of the devices passed to this MessageTransferOperation
// instance, ignore the message.
return;
}
std::unique_ptr<MessageWrapper> message_wrapper =
MessageWrapper::FromRawMessage(payload);
if (message_wrapper) {
OnMessageReceived(std::move(message_wrapper), *remote_device);
}
}
void MessageTransferOperation::UnregisterDevice(
const cryptauth::RemoteDevice& remote_device) {
// Note: This function may be called from the destructor. It is invalid to
// invoke any virtual methods if |shutting_down_| is true.
// Make a copy of |remote_device| before continuing, since the code below may
// cause the original reference to be deleted.
cryptauth::RemoteDevice remote_device_copy = remote_device;
remote_device_to_attempts_map_.erase(remote_device_copy);
remote_devices_.erase(std::remove(remote_devices_.begin(),
remote_devices_.end(), remote_device_copy),
remote_devices_.end());
StopTimerForDeviceIfRunning(remote_device_copy);
connection_manager_->UnregisterRemoteDevice(remote_device_copy.GetDeviceId(),
message_type_for_connection_);
if (!shutting_down_ && remote_devices_.empty())
OnOperationFinished();
}
int MessageTransferOperation::SendMessageToDevice(
const cryptauth::RemoteDevice& remote_device,
std::unique_ptr<MessageWrapper> message_wrapper) {
return connection_manager_->SendMessage(remote_device.GetDeviceId(),
message_wrapper->ToRawMessage());
}
uint32_t MessageTransferOperation::GetTimeoutSeconds() {
return MessageTransferOperation::kDefaultTimeoutSeconds;
}
void MessageTransferOperation::SetTimerFactoryForTest(
std::unique_ptr<TimerFactory> timer_factory_for_test) {
timer_factory_ = std::move(timer_factory_for_test);
}
void MessageTransferOperation::HandleDeviceDisconnection(
const cryptauth::RemoteDevice& remote_device,
BleConnectionManager::StateChangeDetail status_change_detail) {
ConnectAttemptCounts& attempts_for_device =
remote_device_to_attempts_map_[remote_device];
switch (status_change_detail) {
case BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE:
PA_LOG(ERROR) << "State transitioned to DISCONNECTED, but no "
<< "StateChangeDetail was provided. Treating this as a "
<< "failure to discover the device.";
// Note: Intentional fall-through to next case.
case BleConnectionManager::StateChangeDetail::
STATE_CHANGE_DETAIL_COULD_NOT_ATTEMPT_CONNECTION:
++attempts_for_device.empty_scan_attempts;
PA_LOG(INFO) << "Connection attempt failed; could not discover the "
<< "device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ". "
<< "Number of failures to establish connection: "
<< attempts_for_device.empty_scan_attempts;
if (attempts_for_device.empty_scan_attempts >= kMaxEmptyScansPerDevice) {
PA_LOG(INFO) << "Reached retry limit for failing to discover the "
<< "device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ". "
<< "Unregistering device.";
UnregisterDevice(remote_device);
}
break;
case BleConnectionManager::StateChangeDetail::
STATE_CHANGE_DETAIL_GATT_CONNECTION_WAS_ATTEMPTED:
++attempts_for_device.gatt_connection_attempts;
PA_LOG(INFO) << "Connection attempt failed; GATT connection error for "
<< "device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ". "
<< "Number of GATT error: "
<< attempts_for_device.gatt_connection_attempts;
UMA_HISTOGRAM_BOOLEAN(
"InstantTethering.GattConnectionAttempt.SuccessRate", false);
if (attempts_for_device.gatt_connection_attempts >=
kMaxGattConnectionAttemptsPerDevice) {
PA_LOG(INFO) << "Reached retry limit for GATT connection errors for "
<< "device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ". "
<< "Unregistering device.";
UMA_HISTOGRAM_BOOLEAN(
"InstantTethering.GattConnectionAttempt."
"EffectiveSuccessRateWithRetries",
false);
UnregisterDevice(remote_device);
}
break;
case BleConnectionManager::StateChangeDetail::
STATE_CHANGE_DETAIL_INTERRUPTED_BY_HIGHER_PRIORITY:
// If the connection attempt was interrupted by a higher-priority message,
// this is not a true failure. There is nothing to do until the next state
// change occurs.
break;
case BleConnectionManager::StateChangeDetail::
STATE_CHANGE_DETAIL_DEVICE_WAS_UNREGISTERED:
// This state change is expected to be handled as a result of calls to
// UnregisterDevice(). There is no need for special handling.
break;
default:
NOTREACHED();
break;
}
}
void MessageTransferOperation::StartTimerForDevice(
const cryptauth::RemoteDevice& remote_device) {
PA_LOG(INFO) << "Starting timer for operation with message type "
<< message_type_for_connection_ << " from device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ".";
remote_device_to_timer_map_.emplace(remote_device,
timer_factory_->CreateOneShotTimer());
remote_device_to_timer_map_[remote_device]->Start(
FROM_HERE, base::TimeDelta::FromSeconds(GetTimeoutSeconds()),
base::Bind(&MessageTransferOperation::OnTimeout,
weak_ptr_factory_.GetWeakPtr(), remote_device));
}
void MessageTransferOperation::StopTimerForDeviceIfRunning(
const cryptauth::RemoteDevice& remote_device) {
if (!remote_device_to_timer_map_[remote_device])
return;
remote_device_to_timer_map_[remote_device]->Stop();
remote_device_to_timer_map_.erase(remote_device);
}
void MessageTransferOperation::OnTimeout(
const cryptauth::RemoteDevice& remote_device) {
PA_LOG(WARNING) << "Timed out operation for message type "
<< message_type_for_connection_ << " from device with ID "
<< remote_device.GetTruncatedDeviceIdForLogs() << ".";
remote_device_to_timer_map_.erase(remote_device);
UnregisterDevice(remote_device);
}
cryptauth::RemoteDevice* MessageTransferOperation::GetRemoteDevice(
const std::string& device_id) {
for (auto& remote_device : remote_devices_) {
if (remote_device.GetDeviceId() == device_id)
return &remote_device;
}
return nullptr;
}
} // namespace tether
} // namespace chromeos