Chromium Code Reviews| Index: chromeos/components/tether/ble_connection_manager.cc |
| diff --git a/chromeos/components/tether/ble_connection_manager.cc b/chromeos/components/tether/ble_connection_manager.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4b92156ced03e1935e3c600bd9be4a81b251dd8a |
| --- /dev/null |
| +++ b/chromeos/components/tether/ble_connection_manager.cc |
| @@ -0,0 +1,472 @@ |
| +// 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/ble_connection_manager.h" |
| + |
| +#include "chromeos/components/tether/ble_constants.h" |
| +#include "components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.h" |
| +#include "components/proximity_auth/logging/logging.h" |
| +#include "device/bluetooth/bluetooth_uuid.h" |
| + |
| +namespace chromeos { |
| + |
| +namespace tether { |
| + |
| +namespace { |
| +const char kTetherFeature[] = "magic_tether"; |
| +const int64_t kAdvertisingTimeoutMillis = 12000; |
| +const int64_t kShortErrorTimeoutMillis = 100; |
|
Ryan Hansberry
2017/02/16 19:29:35
Didn't we agree to make this 0?
Kyle Horimoto
2017/02/17 01:06:52
Done.
|
| +} // namespace |
| + |
| +// static |
| +std::string BleConnectionManager::MessageTypeToString( |
| + const MessageType& reason) { |
| + switch (reason) { |
| + case MessageType::TETHER_AVAILABILITY_REQUEST: |
| + return "[TetherAvailabilityRequest]"; |
| + case MessageType::CONNECT_TETHERING_REQUEST: |
| + return "[ConnectTetheringRequest]"; |
| + case MessageType::KEEP_ALIVE_TICKLE: |
| + return "[KeepAliveTickle]"; |
| + case MessageType::DISCONNECT_TETHERING_REQUEST: |
| + return "[DisconnectTetheringRequest]"; |
| + default: |
| + return "[invalid MessageType]"; |
| + } |
| +} |
| + |
| +BleConnectionManager::ConnectionMetadata::ConnectionMetadata( |
| + const cryptauth::RemoteDevice remote_device, |
| + std::shared_ptr<base::Timer> timer, |
| + base::WeakPtr<BleConnectionManager> manager) |
| + : remote_device_(remote_device), |
| + connection_attempt_timeout_timer_(timer), |
| + manager_(manager), |
| + weak_ptr_factory_(this) {} |
| + |
| +BleConnectionManager::ConnectionMetadata::~ConnectionMetadata() {} |
| + |
| +void BleConnectionManager::ConnectionMetadata::RegisterConnectionReason( |
| + const MessageType& connection_reason) { |
| + active_connection_reasons_.insert(connection_reason); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::UnregisterConnectionReason( |
| + const MessageType& connection_reason) { |
| + active_connection_reasons_.erase(connection_reason); |
| +} |
| + |
| +bool BleConnectionManager::ConnectionMetadata::HasReasonForConnection() const { |
| + return !active_connection_reasons_.empty(); |
| +} |
| + |
| +bool BleConnectionManager::ConnectionMetadata::HasEstablishedConnection() |
| + const { |
| + return secure_channel_.get(); |
| +} |
| + |
| +cryptauth::SecureChannel::Status |
| +BleConnectionManager::ConnectionMetadata::GetStatus() const { |
|
Ryan Hansberry
2017/02/16 19:29:35
Why not always return secure_channel's status unle
Kyle Horimoto
2017/02/17 01:06:52
If the ConnectionMetadata is associated with a dev
Ryan Hansberry
2017/02/17 01:59:05
Got it. Please add a brief comment explaining that
Kyle Horimoto
2017/02/17 02:08:52
Done.
|
| + if (connection_attempt_timeout_timer_->IsRunning()) { |
| + return cryptauth::SecureChannel::Status::CONNECTING; |
| + } else if (!HasEstablishedConnection()) { |
| + return cryptauth::SecureChannel::Status::DISCONNECTED; |
| + } |
| + |
| + return secure_channel_->status(); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::StartConnectionAttemptTimer( |
| + bool use_short_error_timeout) { |
| + DCHECK(!secure_channel_); |
| + DCHECK(!connection_attempt_timeout_timer_->IsRunning()); |
| + |
| + int64_t timeout_millis = use_short_error_timeout ? kShortErrorTimeoutMillis |
| + : kAdvertisingTimeoutMillis; |
| + |
| + connection_attempt_timeout_timer_->Start( |
| + FROM_HERE, base::TimeDelta::FromMilliseconds(timeout_millis), |
| + base::Bind(&ConnectionMetadata::OnConnectionAttemptTimeout, |
| + weak_ptr_factory_.GetWeakPtr())); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::OnConnectionAttemptTimeout() { |
| + manager_->OnConnectionAttemptTimeout(remote_device_); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::SetSecureChannel( |
| + std::unique_ptr<cryptauth::SecureChannel> secure_channel) { |
| + DCHECK(!secure_channel_); |
| + |
| + // The connection has succeeded, so cancel the timeout. |
| + connection_attempt_timeout_timer_->Stop(); |
| + |
| + secure_channel_ = std::move(secure_channel); |
| + secure_channel_->AddObserver(this); |
| + secure_channel_->Initialize(); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::SendMessage( |
| + const std::string& payload) { |
| + DCHECK(GetStatus() == cryptauth::SecureChannel::Status::AUTHENTICATED); |
| + secure_channel_->SendMessage(std::string(kTetherFeature), payload); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::OnSecureChannelStatusChanged( |
| + cryptauth::SecureChannel* secure_channel, |
| + const cryptauth::SecureChannel::Status& old_status, |
| + const cryptauth::SecureChannel::Status& new_status) { |
| + DCHECK(secure_channel_.get() == secure_channel); |
| + |
| + if (new_status == cryptauth::SecureChannel::Status::CONNECTING) { |
| + // BleConnectionManager already broadcasts "disconnected => connecting" |
| + // status updates when a connection attempt begins, so there is no need to |
| + // handle this case. |
| + return; |
| + } |
| + |
| + if (new_status == cryptauth::SecureChannel::Status::DISCONNECTED) { |
| + secure_channel_->RemoveObserver(this); |
| + secure_channel_.reset(); |
| + |
| + manager_->OnSecureChannelStatusChanged( |
| + remote_device_, old_status, |
| + cryptauth::SecureChannel::Status::DISCONNECTED); |
| + return; |
|
Ryan Hansberry
2017/02/16 19:29:35
returning early is unnecessary. let it fall throug
Kyle Horimoto
2017/02/17 01:06:52
Done.
|
| + } |
| + |
| + manager_->OnSecureChannelStatusChanged(remote_device_, old_status, |
| + new_status); |
| +} |
| + |
| +void BleConnectionManager::ConnectionMetadata::OnMessageReceived( |
| + cryptauth::SecureChannel* secure_channel, |
| + const std::string& feature, |
| + const std::string& payload) { |
| + DCHECK(secure_channel_.get() == secure_channel); |
| + if (feature != std::string(kTetherFeature)) { |
| + // If the message received was not a tether feature, ignore it. |
| + return; |
| + } |
| + |
| + manager_->SendMessageReceivedEvent(remote_device_, payload); |
| +} |
| + |
| +std::unique_ptr<base::Timer> BleConnectionManager::TimerFactory::CreateTimer() { |
| + return base::MakeUnique<base::OneShotTimer>(); |
| +} |
| + |
| +BleConnectionManager::BleConnectionManager( |
| + std::unique_ptr<Delegate> delegate, |
| + scoped_refptr<device::BluetoothAdapter> adapter, |
| + const LocalDeviceDataProvider* local_device_data_provider, |
| + const cryptauth::RemoteBeaconSeedFetcher* remote_beacon_seed_fetcher, |
| + cryptauth::BluetoothThrottler* bluetooth_throttler) |
| + : BleConnectionManager( |
| + std::move(delegate), |
| + adapter, |
| + // TODO(khorimoto): Inject |adapter| into |BleScanner|. |
| + base::MakeUnique<BleScanner>(local_device_data_provider), |
| + base::MakeUnique<BleAdvertiser>(adapter, |
| + local_device_data_provider, |
| + remote_beacon_seed_fetcher), |
| + base::MakeUnique<BleAdvertisementDeviceQueue>(), |
| + base::WrapUnique<TimerFactory>(new TimerFactory()), |
| + bluetooth_throttler) {} |
| + |
| +BleConnectionManager::BleConnectionManager( |
| + std::unique_ptr<Delegate> delegate, |
| + scoped_refptr<device::BluetoothAdapter> adapter, |
| + std::unique_ptr<BleScanner> ble_scanner, |
| + std::unique_ptr<BleAdvertiser> ble_advertiser, |
| + std::unique_ptr<BleAdvertisementDeviceQueue> device_queue, |
| + std::unique_ptr<TimerFactory> timer_factory, |
| + cryptauth::BluetoothThrottler* bluetooth_throttler) |
| + : delegate_(std::move(delegate)), |
| + adapter_(adapter), |
| + ble_scanner_(std::move(ble_scanner)), |
| + ble_advertiser_(std::move(ble_advertiser)), |
| + device_queue_(std::move(device_queue)), |
| + timer_factory_(std::move(timer_factory)), |
| + bluetooth_throttler_(bluetooth_throttler), |
| + weak_ptr_factory_(this) { |
| + ble_scanner_->AddObserver(this); |
| +} |
| + |
| +BleConnectionManager::~BleConnectionManager() { |
| + ble_scanner_->RemoveObserver(this); |
| +} |
| + |
| +void BleConnectionManager::RegisterRemoteDevice( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const MessageType& connection_reason) { |
| + PA_LOG(INFO) << "Registering device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs() << " for reason " |
| + << MessageTypeToString(connection_reason); |
| + |
| + std::shared_ptr<ConnectionMetadata> data = |
| + GetConnectionMetadata(remote_device); |
| + if (!data) { |
| + data = AddMetadataForDevice(remote_device); |
| + } |
| + |
| + data->RegisterConnectionReason(connection_reason); |
| + UpdateConnectionAttempts(); |
| +} |
| + |
| +void BleConnectionManager::UnregisterRemoteDevice( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const MessageType& connection_reason) { |
| + PA_LOG(INFO) << "Unregistering device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs() << " for reason " |
| + << MessageTypeToString(connection_reason); |
| + |
| + std::shared_ptr<ConnectionMetadata> data = |
| + GetConnectionMetadata(remote_device); |
| + if (!data) { |
| + // If the device was not registered, there is nothing to do. |
| + return; |
| + } |
| + |
| + data->UnregisterConnectionReason(connection_reason); |
| + if (!data->HasReasonForConnection()) { |
| + cryptauth::SecureChannel::Status status_before_disconnect = |
| + data->GetStatus(); |
| + device_to_metadata_map_.erase(remote_device); |
| + if (status_before_disconnect == |
| + cryptauth::SecureChannel::Status::CONNECTING) { |
| + StopConnectionAttemptAndMoveToEndOfQueue(remote_device); |
| + } |
| + if (status_before_disconnect != |
| + cryptauth::SecureChannel::Status::DISCONNECTED) { |
| + // Send a status update for the disconnection. |
| + SendSecureChannelStatusChangeEvent( |
| + remote_device, status_before_disconnect, |
| + cryptauth::SecureChannel::Status::DISCONNECTED); |
| + } |
| + } |
| + |
| + UpdateConnectionAttempts(); |
| +} |
| + |
| +void BleConnectionManager::SendMessage( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const std::string& message) { |
| + std::shared_ptr<ConnectionMetadata> data = |
| + GetConnectionMetadata(remote_device); |
| + if (!data || |
| + data->GetStatus() != cryptauth::SecureChannel::Status::AUTHENTICATED) { |
| + PA_LOG(ERROR) << "Attempted to send a message to device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs() << ". " |
| + << "Message: \"" << message << "\""; |
| + return; |
| + } |
| + |
| + PA_LOG(INFO) << "Sending message to device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs() << ". " |
| + << "Message: \"" << message << "\""; |
| + data->SendMessage(message); |
| +} |
| + |
| +void BleConnectionManager::AddObserver(Observer* observer) { |
| + observer_list_.AddObserver(observer); |
| +} |
| + |
| +void BleConnectionManager::RemoveObserver(Observer* observer) { |
| + observer_list_.RemoveObserver(observer); |
| +} |
| + |
| +void BleConnectionManager::OnReceivedAdvertisementFromDevice( |
| + const std::string& device_address, |
| + cryptauth::RemoteDevice remote_device) { |
| + std::shared_ptr<ConnectionMetadata> data = |
| + GetConnectionMetadata(remote_device); |
| + if (!data) { |
| + // If an advertisement is received from a device that is not registered, |
| + // ignore it. |
| + PA_LOG(WARNING) << "Received an advertisement from a device which is not " |
| + << "registered. Bluetooth address: " << device_address |
| + << ", Remote Device ID: " << remote_device.GetDeviceId(); |
| + return; |
| + } |
| + |
| + PA_LOG(INFO) << "Received advertisement from device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs() << ". " |
| + << "Starting authentication handshake to that device."; |
| + |
| + // Create a connection to that device. |
| + std::unique_ptr<cryptauth::Connection> connection = |
| + cryptauth::weave::BluetoothLowEnergyWeaveClientConnection::Factory:: |
| + NewInstance(remote_device, device_address, adapter_, |
| + device::BluetoothUUID(std::string(kGattServerUuid)), |
| + bluetooth_throttler_); |
| + PA_LOG(INFO) << "3 " << connection.get(); |
|
Ryan Hansberry
2017/02/16 19:29:35
Are these leftover debug logs?
Kyle Horimoto
2017/02/17 01:06:52
Yes; removed.
|
| + std::unique_ptr<cryptauth::SecureChannel> secure_channel = |
| + cryptauth::SecureChannel::Factory::NewInstance( |
| + std::move(connection), delegate_->CreateSecureChannelDelegate()); |
| + PA_LOG(INFO) << "4 " << secure_channel.get(); |
| + data->SetSecureChannel(std::move(secure_channel)); |
| + |
| + // Stop trying to connect to that device, since a connection already exists. |
| + StopConnectionAttemptAndMoveToEndOfQueue(remote_device); |
| + UpdateConnectionAttempts(); |
| +} |
| + |
| +std::shared_ptr<BleConnectionManager::ConnectionMetadata> |
| +BleConnectionManager::GetConnectionMetadata( |
| + const cryptauth::RemoteDevice& remote_device) { |
| + const auto map_iter = device_to_metadata_map_.find(remote_device); |
| + if (map_iter == device_to_metadata_map_.end()) { |
| + return nullptr; |
| + } |
| + |
| + return map_iter->second; |
| +} |
| + |
| +std::shared_ptr<BleConnectionManager::ConnectionMetadata> |
| +BleConnectionManager::AddMetadataForDevice( |
| + const cryptauth::RemoteDevice& remote_device) { |
| + std::shared_ptr<ConnectionMetadata> existing_data = |
| + GetConnectionMetadata(remote_device); |
| + if (existing_data) { |
| + return existing_data; |
| + } |
| + |
| + std::unique_ptr<base::Timer> timer = timer_factory_->CreateTimer(); |
| + device_to_metadata_map_.insert( |
| + std::pair<cryptauth::RemoteDevice, std::shared_ptr<ConnectionMetadata>>( |
| + remote_device, |
| + std::shared_ptr<ConnectionMetadata>( |
| + new ConnectionMetadata(remote_device, std::move(timer), |
| + weak_ptr_factory_.GetWeakPtr())))); |
| + return device_to_metadata_map_.at(remote_device); |
| +} |
| + |
| +void BleConnectionManager::UpdateConnectionAttempts() { |
| + UpdateAdvertisementQueue(); |
| + |
| + std::vector<cryptauth::RemoteDevice> should_advertise_to = |
| + device_queue_->GetDevicesToWhichToAdvertise(); |
| + DCHECK(should_advertise_to.size() <= |
| + static_cast<size_t>(kMaxConcurrentAdvertisements)); |
| + |
| + for (const auto& remote_device : should_advertise_to) { |
| + std::shared_ptr<ConnectionMetadata> associated_data = |
| + GetConnectionMetadata(remote_device); |
| + if (associated_data->GetStatus() != |
| + cryptauth::SecureChannel::Status::CONNECTING) { |
| + // If there is no active attempt to connect to a device at the front of |
| + // the queue, start a connection attempt. |
| + StartConnectionAttempt(remote_device); |
| + } |
| + } |
| +} |
| + |
| +void BleConnectionManager::UpdateAdvertisementQueue() { |
| + std::vector<cryptauth::RemoteDevice> devices_for_queue; |
| + for (const auto& map_entry : device_to_metadata_map_) { |
| + if (map_entry.second->HasEstablishedConnection()) { |
| + // If there is already an active connection to the device, there is no |
| + // need to advertise to the device to bootstrap a connection. |
| + continue; |
| + } |
| + |
| + devices_for_queue.push_back(map_entry.first); |
| + } |
| + |
| + device_queue_->SetDevices(devices_for_queue); |
| +} |
| + |
| +void BleConnectionManager::StartConnectionAttempt( |
| + const cryptauth::RemoteDevice& remote_device) { |
| + std::shared_ptr<ConnectionMetadata> data = |
| + GetConnectionMetadata(remote_device); |
| + DCHECK(data); |
| + |
| + PA_LOG(INFO) << "Starting connection attempt to device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs(); |
| + |
| + // Send a "disconnected => connecting" update to alert clients that a |
| + // connection attempt for |remote_device| is underway. |
| + SendSecureChannelStatusChangeEvent( |
| + remote_device, cryptauth::SecureChannel::Status::DISCONNECTED, |
| + cryptauth::SecureChannel::Status::CONNECTING); |
| + |
| + bool success = ble_scanner_->RegisterScanFilterForDevice(remote_device) && |
| + ble_advertiser_->StartAdvertisingToDevice(remote_device); |
| + |
| + // Start a timer; if a connection is unable to be created before the timer |
| + // fires, a timeout occurs. Note that if this class is unable to start both |
| + // the scanner and advertiser successfully (i.e., |success| is |false|), a |
| + // short error timeout is set instead of the full timeout, which has the |
| + // effect of quickly sending out "disconnected => connecting => disconnecting" |
| + // status updates. A short timeout is used here instead of a special case in |
| + // order to route all connection failures through the same code path. |
| + data->StartConnectionAttemptTimer(/* use_short_error_timeout */ !success); |
|
Ryan Hansberry
2017/02/16 19:29:35
The param comment should come after the param.
Kyle Horimoto
2017/02/17 01:06:52
Done.
|
| +} |
| + |
| +void BleConnectionManager::StopConnectionAttemptAndMoveToEndOfQueue( |
| + const cryptauth::RemoteDevice& remote_device) { |
| + PA_LOG(INFO) << "Stopping connection attempt to device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs(); |
| + |
| + ble_scanner_->UnregisterScanFilterForDevice(remote_device); |
| + ble_advertiser_->StopAdvertisingToDevice(remote_device); |
| + |
| + device_queue_->MoveDeviceToEnd(remote_device.GetDeviceId()); |
| +} |
| + |
| +void BleConnectionManager::OnConnectionAttemptTimeout( |
| + const cryptauth::RemoteDevice& remote_device) { |
| + PA_LOG(INFO) << "Connection attempt timed out for device with ID " |
| + << remote_device.GetTruncatedDeviceIdForLogs(); |
| + |
| + StopConnectionAttemptAndMoveToEndOfQueue(remote_device); |
| + |
| + // Send a "connecting => disconnected" update to alert clients that a |
| + // connection attempt for |remote_device| has failed. |
| + SendSecureChannelStatusChangeEvent( |
| + remote_device, cryptauth::SecureChannel::Status::CONNECTING, |
| + cryptauth::SecureChannel::Status::DISCONNECTED); |
| + |
| + UpdateConnectionAttempts(); |
| +} |
| + |
| +void BleConnectionManager::OnSecureChannelStatusChanged( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const cryptauth::SecureChannel::Status& old_status, |
| + const cryptauth::SecureChannel::Status& new_status) { |
| + SendSecureChannelStatusChangeEvent(remote_device, old_status, new_status); |
| + UpdateConnectionAttempts(); |
| +} |
| + |
| +void BleConnectionManager::SendMessageReceivedEvent( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const std::string& payload) { |
| + PA_LOG(INFO) << "Broadcasting message received event: " |
| + << "Device ID \"" << remote_device.GetTruncatedDeviceIdForLogs() |
| + << "\" received message \"" << payload << "\"."; |
| + for (auto& observer : observer_list_) { |
| + observer.OnMessageReceived(remote_device, payload); |
| + } |
| +} |
| + |
| +void BleConnectionManager::SendSecureChannelStatusChangeEvent( |
| + const cryptauth::RemoteDevice& remote_device, |
| + const cryptauth::SecureChannel::Status& old_status, |
| + const cryptauth::SecureChannel::Status& new_status) { |
| + PA_LOG(INFO) << "Broadcasting status change event: " |
| + << "Device ID \"" << remote_device.GetTruncatedDeviceIdForLogs() |
| + << "\": " << cryptauth::SecureChannel::StatusToString(old_status) |
| + << " => " |
| + << cryptauth::SecureChannel::StatusToString(new_status); |
| + for (auto& observer : observer_list_) { |
| + observer.OnSecureChannelStatusChanged(remote_device, old_status, |
| + new_status); |
| + } |
| +} |
| + |
| +} // namespace tether |
| + |
| +} // namespace chromeos |