chromium/chromeos/ash/services/secure_channel/nearby_connection_manager_impl.cc

// Copyright 2020 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/nearby_connection_manager_impl.h"

#include <optional>

#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/secure_channel/authenticated_channel_impl.h"
#include "chromeos/ash/services/secure_channel/device_id_pair.h"
#include "chromeos/ash/services/secure_channel/nearby_connection.h"
#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom-shared.h"
#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom.h"
#include "chromeos/ash/services/secure_channel/secure_channel_disconnector.h"

namespace ash::secure_channel {

// static
NearbyConnectionManagerImpl::Factory*
    NearbyConnectionManagerImpl::Factory::test_factory_ = nullptr;

// static
std::unique_ptr<NearbyConnectionManager>
NearbyConnectionManagerImpl::Factory::Create(
    BleScanner* ble_scanner,
    SecureChannelDisconnector* secure_channel_disconnector) {
  if (test_factory_) {
    return test_factory_->CreateInstance(ble_scanner,
                                         secure_channel_disconnector);
  }

  return base::WrapUnique(new NearbyConnectionManagerImpl(
      ble_scanner, secure_channel_disconnector));
}

// static
void NearbyConnectionManagerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

NearbyConnectionManagerImpl::Factory::~Factory() = default;

NearbyConnectionManagerImpl::NearbyConnectionManagerImpl(
    BleScanner* ble_scanner,
    SecureChannelDisconnector* secure_channel_disconnector)
    : ble_scanner_(ble_scanner),
      secure_channel_disconnector_(secure_channel_disconnector) {
  ble_scanner_->AddObserver(this);
}

NearbyConnectionManagerImpl::~NearbyConnectionManagerImpl() {
  ble_scanner_->RemoveObserver(this);
}

void NearbyConnectionManagerImpl::PerformAttemptNearbyInitiatorConnection(
    const DeviceIdPair& device_id_pair) {
  if (DoesAuthenticatingChannelExist(device_id_pair.remote_device_id()))
    return;

  ble_scanner_->AddScanRequest(ConnectionAttemptDetails(
      device_id_pair, ConnectionMedium::kNearbyConnections,
      ConnectionRole::kInitiatorRole));
}

void NearbyConnectionManagerImpl::PerformCancelNearbyInitiatorConnectionAttempt(
    const DeviceIdPair& device_id_pair) {
  if (DoesAuthenticatingChannelExist(device_id_pair.remote_device_id())) {
    // Check to see if we are removing the final request for an active channel;
    // if so, that channel needs to be disconnected.
    ProcessPotentialLingeringChannel(device_id_pair.remote_device_id());
    return;
  }

  // If a client canceled its request as a result of being notified of an
  // authenticated channel, that request was not actually active.
  if (notifying_remote_device_id_ == device_id_pair.remote_device_id())
    return;
  ble_scanner_->RemoveScanRequest(ConnectionAttemptDetails(
      device_id_pair, ConnectionMedium::kNearbyConnections,
      ConnectionRole::kInitiatorRole));
}

void NearbyConnectionManagerImpl::OnReceivedAdvertisement(
    multidevice::RemoteDeviceRef remote_device,
    device::BluetoothDevice* bluetooth_device,
    ConnectionMedium connection_medium,
    ConnectionRole connection_role,
    const std::vector<uint8_t>& eid) {
  // Only process advertisements received as part of the Nearby Connections
  // flow.
  if (connection_medium != ConnectionMedium::kNearbyConnections)
    return;

  // Create a connection to the device.
  std::unique_ptr<Connection> connection = NearbyConnection::Factory::Create(
      remote_device, eid, GetNearbyConnector());

  NotifyBleDiscoveryStateChanged(
      ChooseChannelRecipient(remote_device.GetDeviceId()),
      mojom::DiscoveryResult::kSuccess, std::nullopt);
  SetAuthenticatingChannel(
      remote_device.GetDeviceId(),
      SecureChannel::Factory::Create(std::move(connection)));
}

void NearbyConnectionManagerImpl::OnDiscoveryFailed(
    const DeviceIdPair& device_id_pair,
    mojom::DiscoveryResult discovery_result,
    std::optional<mojom::DiscoveryErrorCode> potential_error_code) {
  NotifyBleDiscoveryStateChanged(device_id_pair, discovery_result,
                                 potential_error_code);
}

void NearbyConnectionManagerImpl::OnSecureChannelStatusChanged(
    SecureChannel* secure_channel,
    const SecureChannel::Status& old_status,
    const SecureChannel::Status& new_status) {
  std::string remote_device_id =
      GetRemoteDeviceIdForSecureChannel(secure_channel);

  if (new_status == SecureChannel::Status::DISCONNECTED) {
    bool was_authenticating =
        old_status == SecureChannel::Status::AUTHENTICATING;
    HandleSecureChannelDisconnection(remote_device_id, was_authenticating);
    return;
  }

  if (new_status == SecureChannel::Status::AUTHENTICATED)
    HandleChannelAuthenticated(remote_device_id);
}

void NearbyConnectionManagerImpl::OnNearbyConnectionStateChanged(
    SecureChannel* secure_channel,
    mojom::NearbyConnectionStep step,
    mojom::NearbyConnectionStepResult result) {
  std::string remote_device_id =
      GetRemoteDeviceIdForSecureChannel(secure_channel);
  NotifyNearbyConnectionStateChanged(ChooseChannelRecipient(remote_device_id),
                                     step, result);
}

void NearbyConnectionManagerImpl::OnSecureChannelAuthenticationStateChanged(
    SecureChannel* secure_channel,
    mojom::SecureChannelState secure_channel_state) {
  std::string remote_device_id =
      GetRemoteDeviceIdForSecureChannel(secure_channel);
  NotifySecureChannelAuthenticationStateChanged(
      ChooseChannelRecipient(remote_device_id), secure_channel_state);
}

bool NearbyConnectionManagerImpl::DoesAuthenticatingChannelExist(
    const std::string& remote_device_id) {
  return base::Contains(remote_device_id_to_secure_channel_map_,
                        remote_device_id);
}

void NearbyConnectionManagerImpl::SetAuthenticatingChannel(
    const std::string& remote_device_id,
    std::unique_ptr<SecureChannel> secure_channel) {
  // Since a channel has been established, all connection attempts to the device
  // should be stopped. Otherwise, it would be possible to pick up additional
  // scan results and try to start a new connection. Multiple simultaneous
  // connections to the same device (e.g., one over BLE and one over Nearby) can
  // interfere with each other.
  PauseConnectionAttemptsToDevice(remote_device_id);

  if (DoesAuthenticatingChannelExist(remote_device_id)) {
    PA_LOG(ERROR) << "A new channel was created, one already exists for the "
                  << "same remote device ID. ID: "
                  << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                         remote_device_id);
    NOTREACHED_IN_MIGRATION();
  }

  SecureChannel* secure_channel_raw = secure_channel.get();

  PA_LOG(INFO) << "Advertisement received; establishing connection. "
               << "Remote device ID: "
               << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                      remote_device_id);
  remote_device_id_to_secure_channel_map_[remote_device_id] =
      std::move(secure_channel);

  // Observe the channel to be notified of when either the channel authenticates
  // successfully or faces connection instability and disconnects.
  secure_channel_raw->AddObserver(this);
  secure_channel_raw->Initialize();
}

void NearbyConnectionManagerImpl::PauseConnectionAttemptsToDevice(
    const std::string& remote_device_id) {
  for (const auto& pair : GetDeviceIdPairsForRemoteDevice(remote_device_id))
    PerformCancelNearbyInitiatorConnectionAttempt(pair);
}

void NearbyConnectionManagerImpl::RestartPausedAttemptsToDevice(
    const std::string& remote_device_id) {
  for (const auto& pair : GetDeviceIdPairsForRemoteDevice(remote_device_id))
    PerformAttemptNearbyInitiatorConnection(pair);
}

void NearbyConnectionManagerImpl::ProcessPotentialLingeringChannel(
    const std::string& remote_device_id) {
  // If there was no authenticating SecureChannel associated with
  // |remote_device_id|, return early.
  if (!DoesAuthenticatingChannelExist(remote_device_id))
    return;

  // If there is at least one active request, the channel should remain active.
  if (!GetDeviceIdPairsForRemoteDevice(remote_device_id).empty())
    return;

  // Extract the map value and remove the entry from the map.
  std::unique_ptr<SecureChannel> secure_channel =
      std::move(remote_device_id_to_secure_channel_map_[remote_device_id]);
  remote_device_id_to_secure_channel_map_.erase(remote_device_id);

  // Disconnect the channel, since it is lingering with no active request.
  PA_LOG(VERBOSE)
      << "Disconnecting lingering channel which is no longer associated with "
      << "any active requests. Remote device ID: "
      << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
             remote_device_id);
  secure_channel->RemoveObserver(this);
  secure_channel_disconnector_->DisconnectSecureChannel(
      std::move(secure_channel));
}

std::string NearbyConnectionManagerImpl::GetRemoteDeviceIdForSecureChannel(
    SecureChannel* secure_channel) {
  for (const auto& map_entry : remote_device_id_to_secure_channel_map_) {
    if (map_entry.second.get() == secure_channel)
      return map_entry.first;
  }

  PA_LOG(ERROR) << "No remote device ID mapped to the provided SecureChannel.";
  NOTREACHED_IN_MIGRATION();
  return std::string();
}

void NearbyConnectionManagerImpl::HandleSecureChannelDisconnection(
    const std::string& remote_device_id,
    bool was_authenticating) {
  if (!DoesAuthenticatingChannelExist(remote_device_id)) {
    PA_LOG(ERROR) << "HandleSecureChannelDisconnection(): Disconnected channel "
                  << "not present in map. Remote device ID: "
                  << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                         remote_device_id);
    NOTREACHED_IN_MIGRATION();
  }

  for (const auto& pair : GetDeviceIdPairsForRemoteDevice(remote_device_id)) {
    NotifyNearbyInitiatorFailure(
        pair, was_authenticating
                  ? NearbyInitiatorFailureType::kAuthenticationError
                  : NearbyInitiatorFailureType::kConnectivityError);
  }

  auto it = remote_device_id_to_secure_channel_map_.find(remote_device_id);

  // It is possible that the NotifyNearbyInitiatorFailure() calls above resulted
  // in observers responding to the failure by canceling the connection attempt.
  // If all attempts to |remote_device_id| were cancelled, the disconnected
  // channel will have already been cleaned up via
  // ProcessPotentialLingeringChannel(). In that case, return early.
  if (it == remote_device_id_to_secure_channel_map_.end())
    return;

  // Stop observing the disconnected channel and remove it from the map.
  SecureChannel* secure_channel = it->second.get();
  secure_channel->RemoveObserver(this);
  remote_device_id_to_secure_channel_map_.erase(it);

  // Since the previous connection failed, the connection attempts that were
  // paused in SetAuthenticatingChannel() need to be started up again.
  RestartPausedAttemptsToDevice(remote_device_id);
}

void NearbyConnectionManagerImpl::HandleChannelAuthenticated(
    const std::string& remote_device_id) {
  // Extract the map value and remove the entry from the map.
  std::unique_ptr<SecureChannel> secure_channel =
      std::move(remote_device_id_to_secure_channel_map_[remote_device_id]);
  remote_device_id_to_secure_channel_map_.erase(remote_device_id);

  // Stop observing the channel; it is about to be passed to a client.
  secure_channel->RemoveObserver(this);

  // Before notifying clients, set |notifying_remote_device_id_|. This ensure
  // that the PerformCancel*() functions can check to see whether requests need
  // to be removed from BleScanner/BleAdvertiser.
  notifying_remote_device_id_ = remote_device_id;
  NotifyNearbyInitiatorConnectionSuccess(
      ChooseChannelRecipient(remote_device_id),
      AuthenticatedChannelImpl::Factory::Create(
          std::vector<mojom::ConnectionCreationDetail>(),
          std::move(secure_channel)));
  notifying_remote_device_id_.reset();

  // Restart any attempts which still exist.
  RestartPausedAttemptsToDevice(remote_device_id);
}

DeviceIdPair NearbyConnectionManagerImpl::ChooseChannelRecipient(
    const std::string& remote_device_id) {
  const base::flat_set<DeviceIdPair>& pairs =
      GetDeviceIdPairsForRemoteDevice(remote_device_id);
  DCHECK(!pairs.empty());
  return *pairs.begin();
}

}  // namespace ash::secure_channel