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..0b6c4cec022798ebcc043f9bbdea3a5dcc9a6318 |
--- /dev/null |
+++ b/chromeos/components/tether/ble_connection_manager.cc |
@@ -0,0 +1,479 @@ |
+// 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"; |
+} // namespace |
+ |
+const int64_t BleConnectionManager::kAdvertisingTimeoutMillis = 12000; |
+const int64_t BleConnectionManager::kFailImmediatelyTimeoutMillis = 0; |
+ |
+// 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 { |
+ if (connection_attempt_timeout_timer_->IsRunning()) { |
+ // If the timer is running, a connection attempt is in progress but a |
+ // channel has not been established. |
+ return cryptauth::SecureChannel::Status::CONNECTING; |
+ } else if (!HasEstablishedConnection()) { |
+ // If there is no timer and a channel has not been established, the channel |
+ // is disconnected. |
+ return cryptauth::SecureChannel::Status::DISCONNECTED; |
+ } |
+ |
+ // If a channel has been established, return its status. |
+ return secure_channel_->status(); |
+} |
+ |
+void BleConnectionManager::ConnectionMetadata::StartConnectionAttemptTimer( |
+ bool fail_immediately) { |
+ DCHECK(!secure_channel_); |
+ DCHECK(!connection_attempt_timeout_timer_->IsRunning()); |
+ |
+ int64_t timeout_millis = fail_immediately ? kFailImmediatelyTimeoutMillis |
+ : 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; |
+ } |
+ |
+ // Make a copy of the two statuses. If |secure_channel_.reset()| is called |
+ // below, the SecureChannel instance will be destroyed and |old_status| and |
+ // |new_status| may refer to memory which has been deleted. |
+ const cryptauth::SecureChannel::Status old_status_copy = old_status; |
+ const cryptauth::SecureChannel::Status new_status_copy = new_status; |
+ |
+ if (new_status == cryptauth::SecureChannel::Status::DISCONNECTED) { |
+ secure_channel_->RemoveObserver(this); |
+ secure_channel_.reset(); |
+ } |
+ |
+ manager_->OnSecureChannelStatusChanged(remote_device_, old_status_copy, |
+ new_status_copy); |
+} |
+ |
+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> connection_metadata = |
+ GetConnectionMetadata(remote_device); |
+ if (!connection_metadata) { |
+ connection_metadata = AddMetadataForDevice(remote_device); |
+ } |
+ |
+ connection_metadata->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> connection_metadata = |
+ GetConnectionMetadata(remote_device); |
+ if (!connection_metadata) { |
+ // If the device was not registered, there is nothing to do. |
+ return; |
+ } |
+ |
+ connection_metadata->UnregisterConnectionReason(connection_reason); |
+ if (!connection_metadata->HasReasonForConnection()) { |
+ cryptauth::SecureChannel::Status status_before_disconnect = |
+ connection_metadata->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> connection_metadata = |
+ GetConnectionMetadata(remote_device); |
+ if (!connection_metadata || |
+ connection_metadata->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 << "\""; |
+ connection_metadata->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> connection_metadata = |
+ GetConnectionMetadata(remote_device); |
+ if (!connection_metadata) { |
+ // 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_); |
+ std::unique_ptr<cryptauth::SecureChannel> secure_channel = |
+ cryptauth::SecureChannel::Factory::NewInstance( |
+ std::move(connection), delegate_->CreateSecureChannelDelegate()); |
+ connection_metadata->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> connection_metadata = |
+ GetConnectionMetadata(remote_device); |
+ DCHECK(connection_metadata); |
+ |
+ 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 |
+ // the connection fails immediately insetad of waiting for a timeout, which |
+ // has the effect of quickly sending out "disconnected => connecting => |
+ // disconnecting" status updates. The timer is used here instead of a special |
+ // case in order to route all connection failures through the same code path. |
+ connection_metadata->StartConnectionAttemptTimer( |
+ !success /* fail_immediately */); |
+} |
+ |
+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 |