// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/fido/fido_request_handler_base.h"

#include <string_view>
#include <utility>

#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/ble_adapter_manager.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/features.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_base.h"
#include "device/fido/fido_discovery_factory.h"

#if BUILDFLAG(IS_WIN)
#include "device/fido/win/authenticator.h"
#include "device/fido/win/util.h"
#include "device/fido/win/webauthn_api.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "base/process/process_info.h"
#include "device/fido/mac/icloud_keychain.h"
#include "device/fido/mac/util.h"
#endif

namespace device {

namespace {

bool IsGpmPasskeyAuthenticator(const FidoAuthenticator& authenticator) {
  switch (authenticator.GetType()) {
    case AuthenticatorType::kWinNative:
    case AuthenticatorType::kTouchID:
    case AuthenticatorType::kChromeOS:
    case AuthenticatorType::kPhone:
    case AuthenticatorType::kICloudKeychain:
#if BUILDFLAG(ARKWEB_FIDO)
    case AuthenticatorType::kOhosNative:
#endif // BUILDFLAG(ARKWEB_FIDO)
    case AuthenticatorType::kOther:
      return false;
    case AuthenticatorType::kEnclave:
      return true;
  }
  NOTREACHED();
}

void MaybeRecordPlatformCredentialStatus(AuthenticatorType type,
                                         base::TimeDelta elapsed_time) {
  std::string metric_name;

  switch (type) {
    case AuthenticatorType::kWinNative:
      metric_name = "WebAuthentication.CredentialFetchDuration.WinHello";
      break;
    case AuthenticatorType::kTouchID:
      metric_name = "WebAuthentication.CredentialFetchDuration.TouchId";
      break;
    case AuthenticatorType::kChromeOS:
      metric_name = "WebAuthentication.CredentialFetchDuration.ChromeOS";
      break;
    case AuthenticatorType::kICloudKeychain:
      metric_name = "WebAuthentication.CredentialFetchDuration.ICloudKeychain";
      break;
    default:
      return;
  }

  base::UmaHistogramTimes(metric_name, elapsed_time);
}

}  // namespace

// TransportAvailabilityCallbackReadiness stores state that tracks whether
// |FidoRequestHandlerBase| is ready to call
// |OnTransportAvailabilityEnumerated|.
struct TransportAvailabilityCallbackReadiness {
  // callback_made is true if the |OnTransportAvailabilityEnumerated| callback
  // has been made.
  bool callback_made = false;

  // ble_information_pending is true if the |OnTransportAvailabilityEnumerated|
  // callback is pending BLE status information.
  bool ble_information_pending = false;

  // num_platform_credential_checks_pending is true if the
  // |OnTransportAvailabilityEnumerated| callback is pending
  // |OnHasRecognizedPlatformCredentialFilled| being called after the platform
  // authenticator has decided if it has credentials that are responsive to the
  // request.
  unsigned num_platform_credential_checks_pending = 0;

  // win_is_uvpaa_check_pending is true if |OnTransportAvailabilityEnumerated|
  // callback is pending |OnIsUvpaa| being called.
  bool win_is_uvpaa_check_pending = false;

  // platform_biometrics_check_pending is set if an asynchronous check for
  // local biometric availability is pending.
  bool platform_biometrics_check_pending = false;

  // num_discoveries_pending is the number of discoveries that are still yet to
  // signal that they have started.
  unsigned num_discoveries_pending = 0;

  // This separately counts for platform discoveries in order to track whether
  // at least one discovery succeeded, for situations where there is more than
  // one platform authenticator available.
  unsigned num_platform_discoveries_pending = 0;
  bool platform_discovery_succeeded = false;

  bool CanMakeCallback() const {
    return !callback_made && !ble_information_pending &&
           num_platform_credential_checks_pending == 0 &&
           !win_is_uvpaa_check_pending && !platform_biometrics_check_pending &&
           num_discoveries_pending == 0;
  }
};

// FidoRequestHandlerBase::TransportAvailabilityInfo --------------------------

FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() =
    default;

FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo(
    const TransportAvailabilityInfo& data) = default;

FidoRequestHandlerBase::TransportAvailabilityInfo&
FidoRequestHandlerBase::TransportAvailabilityInfo::operator=(
    const TransportAvailabilityInfo& other) = default;

FidoRequestHandlerBase::TransportAvailabilityInfo::
    ~TransportAvailabilityInfo() = default;

// FidoRequestHandlerBase::Observer -------------------------------------------

FidoRequestHandlerBase::Observer::~Observer() = default;

// FidoRequestHandlerBase::ScopedAlwaysAllowBLECalls --------------------------

static bool g_always_allow_ble_calls = false;

FidoRequestHandlerBase::ScopedAlwaysAllowBLECalls::ScopedAlwaysAllowBLECalls() {
  CHECK(!g_always_allow_ble_calls);
  g_always_allow_ble_calls = true;
}

FidoRequestHandlerBase::ScopedAlwaysAllowBLECalls::
    ~ScopedAlwaysAllowBLECalls() {
  CHECK(g_always_allow_ble_calls);
  g_always_allow_ble_calls = false;
}

// FidoRequestHandlerBase -----------------------------------------------------

FidoRequestHandlerBase::FidoRequestHandlerBase()
    : transport_availability_callback_readiness_(
          new TransportAvailabilityCallbackReadiness) {}

FidoRequestHandlerBase::FidoRequestHandlerBase(
    FidoDiscoveryFactory* fido_discovery_factory,
    const base::flat_set<FidoTransportProtocol>& available_transports)
    : device::FidoRequestHandlerBase(fido_discovery_factory,
                                     /*additional_discoveries=*/{},
                                     available_transports) {}

FidoRequestHandlerBase::FidoRequestHandlerBase(
    FidoDiscoveryFactory* fido_discovery_factory,
    std::vector<std::unique_ptr<FidoDiscoveryBase>> additional_discoveries,
    const base::flat_set<FidoTransportProtocol>& available_transports)
    : FidoRequestHandlerBase() {
  InitDiscoveries(fido_discovery_factory, std::move(additional_discoveries),
                  available_transports,
                  /*consider_enclave=*/true);
}

void FidoRequestHandlerBase::InitDiscoveries(
    FidoDiscoveryFactory* fido_discovery_factory,
    std::vector<std::unique_ptr<FidoDiscoveryBase>> additional_discoveries,
    base::flat_set<FidoTransportProtocol> available_transports,
    bool consider_enclave) {
  FIDO_LOG(DEBUG) << "Initializing FIDO discoveries";
#if BUILDFLAG(IS_WIN)
  // Try to instantiate the discovery for proxying requests to the native
  // Windows WebAuthn API; or fall back to using the regular device transport
  // discoveries if the API is unavailable.
  auto win_discovery =
      fido_discovery_factory->MaybeCreateWinWebAuthnApiDiscovery();
  if (win_discovery) {
    // The Windows WebAuthn API is available. On this platform, communicating
    // with authenticator devices directly is blocked by the OS, so we need to
    // go through the native API instead. No device discoveries may be
    // instantiated. The embedder will be responsible for dispatch of the
    // authenticator and whether they display any UI in addition to the one
    // provided by the OS.
    FIDO_LOG(DEBUG) << "Adding Windows Hello discovery";
    win_discovery->set_observer(this);
    discoveries_.push_back(std::move(win_discovery));

    transport_availability_info_.has_win_native_api_authenticator = true;
    transport_availability_callback_readiness_->win_is_uvpaa_check_pending =
        true;
    WinWebAuthnApiAuthenticator::IsUserVerifyingPlatformAuthenticatorAvailable(
        device::WinWebAuthnApi::GetDefault(),
        base::BindOnce(&FidoRequestHandlerBase::OnWinIsUvpaa,
                       weak_factory_.GetWeakPtr()));

    // Allow caBLE as a potential additional transport if requested by
    // the implementing class because it is not subject to the OS'
    // device communication block (only GetAssertionRequestHandler uses
    // caBLE). Otherwise, do not instantiate any other transports.
    base::EraseIf(available_transports, [](auto transport) {
      return transport != FidoTransportProtocol::kHybrid;
    });
  }
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ARKWEB_FIDO)
  auto ohos_discovery =
      fido_discovery_factory->CreateOhosFidoDiscovery();
  if (ohos_discovery) {
    ohos_discovery->set_observer(this);
    discoveries_.push_back(std::move(ohos_discovery));

    // On OHOS all authenticators in one fido discovery.
    base::EraseIf(available_transports, [](auto transport) {
      return transport != FidoTransportProtocol::kHybrid;
    });
  }
#endif // BUILDFLAG(ARKWEB_FIDO)

  transport_availability_info_.available_transports = available_transports;
  for (const auto transport : available_transports) {
    std::vector<std::unique_ptr<FidoDiscoveryBase>> discoveries =
        fido_discovery_factory->Create(transport);
    if (discoveries.empty()) {
      // This can occur in tests when a ScopedVirtualU2fDevice is in effect and
      // HID transports are not configured or when caBLE discovery data isn't
      // available.
      transport_availability_info_.available_transports.erase(transport);
      continue;
    }

    FIDO_LOG(DEBUG) << "Adding discovery for transport "
                    << static_cast<int>(transport);
    for (auto& discovery : discoveries) {
      discovery->set_observer(this);
      discoveries_.emplace_back(std::move(discovery));
    }
  }

  // `additional_discoveries` are injected by
  // AuthenticatorRequestClientDelegate.
  for (auto& discovery : additional_discoveries) {
    // TODO: Make this work better for non-standard discoveries like Windows,
    // which currently pretends to be `kInternal`.
    if (!base::Contains(available_transports, discovery->transport())) {
      continue;
    }
    discovery->set_observer(this);
    discoveries_.emplace_back(std::move(discovery));
  }

  if (consider_enclave) {
    std::optional<std::unique_ptr<FidoDiscoveryBase>> enclave_discovery =
        fido_discovery_factory->MaybeCreateEnclaveDiscovery();
    if (enclave_discovery) {
      FIDO_LOG(DEBUG) << "Adding discovery for enclave";
      enclave_discovery.value()->set_observer(this);
      discoveries_.emplace_back(std::move(*enclave_discovery));
    }
  }

  for (auto& discovery : discoveries_) {
    if (discovery->transport() == FidoTransportProtocol::kInternal) {
      transport_availability_callback_readiness_
          ->num_platform_discoveries_pending++;
    }
  }
  transport_availability_callback_readiness_->num_discoveries_pending =
      discoveries_.size();

#if BUILDFLAG(IS_MAC)
  // On recent macOS a process must have listed Bluetooth metadata in its
  // Info.plist in order to call Bluetooth APIs. Failure to do so results in
  // the system killing with process with SIGABRT once Bluetooth calls are
  // made.
  //
  // However, unless Chromium is started from the Finder, or with special
  // posix_spawn flags, then the responsible process—the one that needs to have
  // the right Info.plist—is one of the parent processes, often the terminal
  // emulator. This can lead to Chromium getting killed when trying to do
  // WebAuthn. This also affects layout tests.
  //
  // Thus, if the responsible process is not Chromium itself, then we do not
  // make any Bluetooth API calls.
  const bool can_call_ble_apis =
      g_always_allow_ble_calls ||
      base::DoesResponsibleProcessHaveBluetoothMetadata();
  if (!can_call_ble_apis) {
    FIDO_LOG(ERROR) << "Cannot use Bluetooth because the responsible app for "
                       "the process does not have Bluetooth metadata in its "
                       "Info.plist. Launch from Finder to fix.";
  }
#else
  const bool can_call_ble_apis = true;
#endif

  // Check if the platform supports BLE before trying to get a power manager.
  // CaBLE might be in |available_transports| without actual BLE support under
  // the virtual environment.
  // TODO(nsatragno): Move the BLE power manager logic to CableDiscoveryFactory
  // so we don't need this additional check.
  if (can_call_ble_apis &&
      device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() &&
      base::Contains(transport_availability_info_.available_transports,
                     FidoTransportProtocol::kHybrid)) {
    FIDO_LOG(DEBUG) << "Checking for bluetooth availability";
    transport_availability_callback_readiness_->ble_information_pending = true;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&FidoRequestHandlerBase::ConstructBleAdapterPowerManager,
                       weak_factory_.GetWeakPtr()));
  }

#if BUILDFLAG(IS_MAC)
  transport_availability_info_.platform_has_biometrics =
      device::fido::mac::DeviceHasBiometricsAvailable();
  FIDO_LOG(DEBUG) << "MacOS biometrics availability check done";
  MaybeSignalTransportsEnumerated();
#elif BUILDFLAG(IS_WIN)
  transport_availability_callback_readiness_
      ->platform_biometrics_check_pending = true;
  FIDO_LOG(DEBUG) << "Checking for Windows biometrics availability";
  device::fido::win::DeviceHasBiometricsAvailable(base::BindOnce(
      [](base::WeakPtr<FidoRequestHandlerBase> handler,
         bool biometrics_available) {
        if (!handler) {
          return;
        }
        handler->transport_availability_info_.platform_has_biometrics =
            biometrics_available;
        handler->transport_availability_callback_readiness_
            ->platform_biometrics_check_pending = false;
        FIDO_LOG(DEBUG) << "Windows biometric availability check done";
        handler->MaybeSignalTransportsEnumerated();
      },
      GetWeakPtr()));
#else
  FIDO_LOG(DEBUG) << "No need to check for biometrics on this platform";
  MaybeSignalTransportsEnumerated();
#endif
}

FidoRequestHandlerBase::~FidoRequestHandlerBase() {
  if (observer_) {
    observer_->StopObserving(this);
  }
  CancelActiveAuthenticators();
}

void FidoRequestHandlerBase::StartAuthenticatorRequest(
    const std::string& authenticator_id) {
  InitializeAuthenticatorAndDispatchRequest(authenticator_id);
}

void FidoRequestHandlerBase::CancelActiveAuthenticators(
    std::string_view exclude_device_id) {
  for (auto task_it = active_authenticators_.begin();
       task_it != active_authenticators_.end();) {
    DCHECK(!task_it->first.empty());
    if (task_it->first != exclude_device_id) {
      DCHECK(task_it->second);
      task_it->second->Cancel();

      // Note that the pointer being erased is non-owning. The actual
      // FidoAuthenticator instance is owned by its discovery (which in turn is
      // owned by |discoveries_|.
      task_it = active_authenticators_.erase(task_it);
    } else {
      ++task_it;
    }
  }
}

void FidoRequestHandlerBase::OnBluetoothAdapterEnumerated(
    bool is_present,
    BleStatus ble_status,
    bool can_power_on,
    bool is_peripheral_role_supported) {
  if (!is_present) {
    transport_availability_info_.available_transports.erase(
        FidoTransportProtocol::kHybrid);
  }

  transport_availability_callback_readiness_->ble_information_pending = false;
  transport_availability_info_.ble_status = ble_status;
  transport_availability_info_.can_power_on_ble_adapter = can_power_on;
  FIDO_LOG(DEBUG) << "Bluetooth status enumerated";
  MaybeSignalTransportsEnumerated();
}

void FidoRequestHandlerBase::OnBluetoothAdapterStatusChanged(
    BleStatus ble_status) {
  transport_availability_info_.ble_status = ble_status;

  if (observer_) {
    observer_->BluetoothAdapterStatusChanged(ble_status);
  }
}

void FidoRequestHandlerBase::PowerOnBluetoothAdapter() {
  if (!bluetooth_adapter_manager_) {
    return;
  }

  bluetooth_adapter_manager_->SetAdapterPower(true /* set_power_on */);
}

void FidoRequestHandlerBase::RequestBluetoothPermission(
    BlePermissionCallback callback) {
  return bluetooth_adapter_manager_->RequestBluetoothPermission(
      std::move(callback));
}

base::WeakPtr<FidoRequestHandlerBase> FidoRequestHandlerBase::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void FidoRequestHandlerBase::SetObserver(
    FidoRequestHandlerBase::Observer* observer) {
  DCHECK(!observer_) << "Only one observer is supported.";
  observer_ = observer;

  FIDO_LOG(DEBUG) << "FidoRequestHandler observer set";
  MaybeSignalTransportsEnumerated();
}

void FidoRequestHandlerBase::RemoveObserver(
    FidoRequestHandlerBase::Observer* observer) {
  observer_ = nullptr;
}

void FidoRequestHandlerBase::Start() {
  for (const auto& discovery : discoveries_) {
    discovery->Start();
  }
}

void FidoRequestHandlerBase::AuthenticatorRemoved(
    FidoDiscoveryBase* discovery,
    FidoAuthenticator* authenticator) {
  // Device connection has been lost or device has already been removed.
  // Thus, calling CancelTask() is not necessary. Also, below
  // ongoing_tasks_.erase() will have no effect for the devices that have been
  // already removed due to processing error or due to invocation of
  // CancelOngoingTasks().
  auto authenticator_it = active_authenticators_.find(authenticator->GetId());
  if (authenticator_it == active_authenticators_.end()) {
    return;
  }
  DCHECK_EQ(authenticator_it->second, authenticator);
  active_authenticators_.erase(authenticator_it);
  if (observer_) {
    observer_->FidoAuthenticatorRemoved(authenticator->GetId());
  }
}

void FidoRequestHandlerBase::DiscoveryStarted(
    FidoDiscoveryBase* discovery,
    bool success,
    std::vector<FidoAuthenticator*> authenticators) {
  transport_availability_callback_readiness_->num_discoveries_pending--;

  bool is_platform_discovery =
      discovery->transport() == FidoTransportProtocol::kInternal;
  if (is_platform_discovery) {
    CHECK(transport_availability_callback_readiness_
              ->num_platform_discoveries_pending > 0);
    transport_availability_callback_readiness_
        ->num_platform_discoveries_pending--;
  }

  if (!success) {
    if (!is_platform_discovery ||
        (transport_availability_callback_readiness_
                 ->num_platform_discoveries_pending == 0 &&
         !transport_availability_callback_readiness_
              ->platform_discovery_succeeded)) {
      transport_availability_info_.available_transports.erase(
          discovery->transport());
    }
  } else {
    for (auto* authenticator : authenticators) {
      AuthenticatorAdded(discovery, authenticator);
    }

    // Allow GetAssertionRequestHandler to asynchronously check for known
    // platform credentials and defer |OnTransportAvailabilityEnumerated| until
    // that check is done.
    // |authenticators| can be empty in tests.
    if (is_platform_discovery && !authenticators.empty()) {
      transport_availability_callback_readiness_->platform_discovery_succeeded =
          true;
      for (FidoAuthenticator* platform_authenticator : authenticators) {
        if (IsGpmPasskeyAuthenticator(*platform_authenticator)) {
          // GPM credential availability is checked in
          // ChromeAuthenticatorRequestDelegate, so the authenticators don't
          // implement GetPlatformCredentialStatus.
          continue;
        }
        transport_availability_info_.has_icloud_keychain |=
            platform_authenticator->GetType() ==
            AuthenticatorType::kICloudKeychain;
        transport_availability_callback_readiness_
            ->num_platform_credential_checks_pending++;
        FIDO_LOG(DEBUG) << "Getting platform credential status";
        GetPlatformCredentialStatus(platform_authenticator);
      }
    }
  }

  FIDO_LOG(DEBUG) << "Discovery started for transport "
                  << static_cast<int>(discovery->transport());
  MaybeSignalTransportsEnumerated();
}

void FidoRequestHandlerBase::AuthenticatorAdded(
    FidoDiscoveryBase* discovery,
    FidoAuthenticator* authenticator) {
  DCHECK(!authenticator->GetId().empty());
  bool was_inserted;
  std::tie(std::ignore, was_inserted) =
      active_authenticators_.insert({authenticator->GetId(), authenticator});
  if (!was_inserted) {
    NOTREACHED() << "Authenticator with duplicate ID "
                 << authenticator->GetId();
  }

  // If |observer_| exists, dispatching request to |authenticator| is
  // delegated to |observer_|. Else, dispatch request to |authenticator|
  // immediately.
  bool embedder_controls_dispatch = false;
  if (observer_) {
    embedder_controls_dispatch =
        observer_->EmbedderControlsAuthenticatorDispatch(*authenticator);
    observer_->FidoAuthenticatorAdded(*authenticator);
  }

  if (!embedder_controls_dispatch) {
    // Post |InitializeAuthenticatorAndDispatchRequest| into its own task. This
    // avoids hairpinning, even if the authenticator immediately invokes the
    // request callback.
    VLOG(2)
        << "Request handler dispatching request to authenticator immediately.";
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest,
            GetWeakPtr(), authenticator->GetId()));
  } else {
    VLOG(2) << "Embedder controls the dispatch.";
  }

#if BUILDFLAG(IS_WIN)
  if (authenticator->GetType() == AuthenticatorType::kWinNative) {
    DCHECK(transport_availability_info_.has_win_native_api_authenticator);
    transport_availability_info_
        .win_native_ui_shows_resident_credential_notice =
        static_cast<WinWebAuthnApiAuthenticator*>(authenticator)
            ->ShowsResidentCredentialNotice();
  }
#endif  // BUILDFLAG(IS_WIN)
}

void FidoRequestHandlerBase::GetPlatformCredentialStatus(
    FidoAuthenticator* platform_authenticator) {
  transport_availability_callback_readiness_
      ->num_platform_credential_checks_pending--;
}

void FidoRequestHandlerBase::OnHavePlatformCredentialStatus(
    AuthenticatorType authenticator_type,
    std::optional<base::ElapsedTimer> timer,
    std::vector<DiscoverableCredentialMetadata> creds,
    RecognizedCredential has_credentials) {
  if (creds.size() > 0 && timer.has_value()) {
    MaybeRecordPlatformCredentialStatus(authenticator_type, timer->Elapsed());
  }

  if (authenticator_type == AuthenticatorType::kICloudKeychain) {
    // iCloud Keychain is the second platform authenticator on the system and
    // its status is reported via a different field.
    DCHECK_EQ(transport_availability_info_.has_icloud_keychain_credential,
              RecognizedCredential::kNoRecognizedCredential);
    transport_availability_info_.has_icloud_keychain_credential =
        has_credentials;
  } else {
    DCHECK_EQ(
        transport_availability_info_.has_platform_authenticator_credential,
        RecognizedCredential::kNoRecognizedCredential);
    transport_availability_info_.has_platform_authenticator_credential =
        has_credentials;
    if (has_credentials == RecognizedCredential::kNoRecognizedCredential) {
      transport_availability_info_.available_transports.erase(
          FidoTransportProtocol::kInternal);
    }
  }

  auto& out_creds = transport_availability_info_.recognized_credentials;
  if (out_creds.empty()) {
    out_creds = std::move(creds);
  } else if (!creds.empty()) {
    out_creds.insert(out_creds.end(), creds.begin(), creds.end());
  }

  transport_availability_callback_readiness_
      ->num_platform_credential_checks_pending--;
  FIDO_LOG(DEBUG) << "Obtained platform credential status";
  MaybeSignalTransportsEnumerated();
}

bool FidoRequestHandlerBase::HasAuthenticator(
    const std::string& authenticator_id) const {
  return base::Contains(active_authenticators_, authenticator_id);
}

void FidoRequestHandlerBase::MaybeSignalTransportsEnumerated() {
  if (!observer_ ||
      !transport_availability_callback_readiness_->CanMakeCallback()) {
    FIDO_LOG(DEBUG) << "Transport availability not yet ready";
    return;
  }

  FIDO_LOG(DEBUG) << "Transport availability checks done";
  transport_availability_callback_readiness_->callback_made = true;
  observer_->OnTransportAvailabilityEnumerated(transport_availability_info_);
}

void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest(
    const std::string& authenticator_id) {
  auto authenticator_it = active_authenticators_.find(authenticator_id);
  if (authenticator_it == active_authenticators_.end()) {
    return;
  }
  FidoAuthenticator* authenticator = authenticator_it->second;
  authenticator->InitializeAuthenticator(
      base::BindOnce(&FidoRequestHandlerBase::DispatchRequest,
                     weak_factory_.GetWeakPtr(), authenticator));
}

void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() {
  bluetooth_adapter_manager_ = std::make_unique<BleAdapterManager>(this);
}

void FidoRequestHandlerBase::OnWinIsUvpaa(bool is_uvpaa) {
  transport_availability_info_.win_is_uvpaa = is_uvpaa;
  transport_availability_callback_readiness_->win_is_uvpaa_check_pending =
      false;
  FIDO_LOG(DEBUG) << "Windows Hello IsUvpaa check done";
  MaybeSignalTransportsEnumerated();
}

void FidoRequestHandlerBase::StopDiscoveries() {
  for (const auto& discovery : discoveries_) {
    discovery->Stop();
  }
}

constexpr base::TimeDelta
    FidoRequestHandlerBase::kMinExpectedAuthenticatorResponseTime;

}  // namespace device