123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- // Copyright 2018 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 "ash/services/multidevice_setup/host_verifier_impl.h"
- #include <utility>
- #include "ash/components/multidevice/logging/logging.h"
- #include "ash/components/multidevice/software_feature.h"
- #include "ash/constants/ash_features.h"
- #include "ash/services/device_sync/proto/cryptauth_common.pb.h"
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/time/time.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- namespace ash {
- namespace multidevice_setup {
- namespace {
- // Software features which, when enabled, represent a verified host.
- constexpr const multidevice::SoftwareFeature kPotentialHostFeatures[] = {
- multidevice::SoftwareFeature::kSmartLockHost,
- multidevice::SoftwareFeature::kInstantTetheringHost,
- multidevice::SoftwareFeature::kMessagesForWebHost};
- // Name of the preference containing the time (in milliseconds since Unix
- // epoch) at which a verification attempt should be retried. If the preference
- // value is kTimestampNotSet, no retry is scheduled.
- const char kRetryTimestampPrefName[] =
- "multidevice_setup.current_retry_timestamp_ms";
- // Value set for the kRetryTimestampPrefName preference when no retry attempt is
- // underway (i.e., verification is complete or there is no current host).
- const int64_t kTimestampNotSet = 0;
- // Name of the preference containing the time delta (in ms) between the
- // timestamp present in the kRetryTimestampPrefName preference and the attempt
- // before that one. If the value of kRetryTimestampPrefName is kTimestampNotSet,
- // the value at this preference is meaningless.
- const char kLastUsedTimeDeltaMsPrefName[] =
- "multidevice_setup.last_used_time_delta_ms";
- // Delta to set for the first retry.
- constexpr const base::TimeDelta kFirstRetryDelta = base::Minutes(10);
- // Delta for the time between a successful FindEligibleDevices call and a
- // request to sync devices.
- constexpr const base::TimeDelta kSyncDelay = base::Seconds(5);
- // The multiplier for increasing the backoff timer between retries.
- const double kExponentialBackoffMultiplier = 1.5;
- } // namespace
- // static
- HostVerifierImpl::Factory* HostVerifierImpl::Factory::test_factory_ = nullptr;
- // static
- std::unique_ptr<HostVerifier> HostVerifierImpl::Factory::Create(
- HostBackendDelegate* host_backend_delegate,
- device_sync::DeviceSyncClient* device_sync_client,
- PrefService* pref_service,
- base::Clock* clock,
- std::unique_ptr<base::OneShotTimer> retry_timer,
- std::unique_ptr<base::OneShotTimer> sync_timer) {
- if (test_factory_) {
- return test_factory_->CreateInstance(
- host_backend_delegate, device_sync_client, pref_service, clock,
- std::move(retry_timer), std::move(sync_timer));
- }
- return base::WrapUnique(new HostVerifierImpl(
- host_backend_delegate, device_sync_client, pref_service, clock,
- std::move(retry_timer), std::move(sync_timer)));
- }
- // static
- void HostVerifierImpl::Factory::SetFactoryForTesting(Factory* test_factory) {
- test_factory_ = test_factory;
- }
- HostVerifierImpl::Factory::~Factory() = default;
- // static
- void HostVerifierImpl::RegisterPrefs(PrefRegistrySimple* registry) {
- registry->RegisterInt64Pref(kRetryTimestampPrefName, kTimestampNotSet);
- registry->RegisterInt64Pref(kLastUsedTimeDeltaMsPrefName, 0);
- }
- HostVerifierImpl::HostVerifierImpl(
- HostBackendDelegate* host_backend_delegate,
- device_sync::DeviceSyncClient* device_sync_client,
- PrefService* pref_service,
- base::Clock* clock,
- std::unique_ptr<base::OneShotTimer> retry_timer,
- std::unique_ptr<base::OneShotTimer> sync_timer)
- : host_backend_delegate_(host_backend_delegate),
- device_sync_client_(device_sync_client),
- pref_service_(pref_service),
- clock_(clock),
- retry_timer_(std::move(retry_timer)),
- sync_timer_(std::move(sync_timer)) {
- host_backend_delegate_->AddObserver(this);
- device_sync_client_->AddObserver(this);
- UpdateRetryState();
- }
- HostVerifierImpl::~HostVerifierImpl() {
- host_backend_delegate_->RemoveObserver(this);
- device_sync_client_->RemoveObserver(this);
- }
- bool HostVerifierImpl::IsHostVerified() {
- absl::optional<multidevice::RemoteDeviceRef> current_host =
- host_backend_delegate_->GetMultiDeviceHostFromBackend();
- if (!current_host)
- return false;
- // If a host exists on the back-end but there is a pending request to remove
- // that host, the device pending removal is no longer considered verified.
- if (host_backend_delegate_->HasPendingHostRequest() &&
- !host_backend_delegate_->GetPendingHostRequest()) {
- return false;
- }
- // The host is not considered verified if it does not have the data needed for
- // secure communication via Bluetooth. These values could be missing if v2
- // DeviceSync data was not decrypted, for instance.
- bool has_crypto_data = !current_host->public_key().empty() &&
- !current_host->persistent_symmetric_key().empty() &&
- !current_host->beacon_seeds().empty();
- base::UmaHistogramBoolean(
- "MultiDevice.Setup.HostVerifier.DoesHostHaveCryptoData", has_crypto_data);
- if (!has_crypto_data) {
- return false;
- }
- // If one or more potential host sofware features is enabled, the host is
- // considered verified.
- for (const auto& software_feature : kPotentialHostFeatures) {
- if (current_host->GetSoftwareFeatureState(software_feature) ==
- multidevice::SoftwareFeatureState::kEnabled) {
- return true;
- }
- }
- return false;
- }
- void HostVerifierImpl::PerformAttemptVerificationNow() {
- AttemptHostVerification();
- }
- void HostVerifierImpl::OnHostChangedOnBackend() {
- UpdateRetryState();
- }
- void HostVerifierImpl::OnNewDevicesSynced() {
- UpdateRetryState();
- }
- void HostVerifierImpl::UpdateRetryState() {
- // If there is no host, verification is not applicable.
- if (!host_backend_delegate_->GetMultiDeviceHostFromBackend()) {
- StopRetryTimerAndClearPrefs();
- return;
- }
- // If there is a host and it is verified, verification is no longer necessary.
- if (IsHostVerified()) {
- sync_timer_->Stop();
- bool was_retry_timer_running = retry_timer_->IsRunning();
- StopRetryTimerAndClearPrefs();
- if (was_retry_timer_running)
- NotifyHostVerified();
- return;
- }
- // If |retry_timer_| is running, an ongoing retry attempt is in progress.
- if (retry_timer_->IsRunning())
- return;
- int64_t timestamp_from_prefs =
- pref_service_->GetInt64(kRetryTimestampPrefName);
- // If no retry timer was set, set the timer to the initial value and attempt
- // to verify now.
- if (timestamp_from_prefs == kTimestampNotSet) {
- AttemptVerificationWithInitialTimeout();
- return;
- }
- base::Time retry_time_from_prefs =
- base::Time::FromJavaTime(timestamp_from_prefs);
- // If a timeout value was set but has not yet occurred, start the timer.
- if (clock_->Now() < retry_time_from_prefs) {
- StartRetryTimer(retry_time_from_prefs);
- return;
- }
- AttemptVerificationAfterInitialTimeout(retry_time_from_prefs);
- }
- void HostVerifierImpl::StopRetryTimerAndClearPrefs() {
- retry_timer_->Stop();
- pref_service_->SetInt64(kRetryTimestampPrefName, kTimestampNotSet);
- pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName, 0);
- }
- void HostVerifierImpl::AttemptVerificationWithInitialTimeout() {
- base::Time retry_time = clock_->Now() + kFirstRetryDelta;
- pref_service_->SetInt64(kRetryTimestampPrefName, retry_time.ToJavaTime());
- pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName,
- kFirstRetryDelta.InMilliseconds());
- StartRetryTimer(retry_time);
- AttemptHostVerification();
- }
- void HostVerifierImpl::AttemptVerificationAfterInitialTimeout(
- const base::Time& retry_time_from_prefs) {
- int64_t time_delta_ms = pref_service_->GetInt64(kLastUsedTimeDeltaMsPrefName);
- DCHECK(time_delta_ms > 0);
- base::Time retry_time = retry_time_from_prefs;
- while (clock_->Now() >= retry_time) {
- time_delta_ms *= kExponentialBackoffMultiplier;
- retry_time += base::Milliseconds(time_delta_ms);
- }
- pref_service_->SetInt64(kRetryTimestampPrefName, retry_time.ToJavaTime());
- pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName, time_delta_ms);
- StartRetryTimer(retry_time);
- AttemptHostVerification();
- }
- void HostVerifierImpl::StartRetryTimer(const base::Time& time_to_fire) {
- base::Time now = clock_->Now();
- DCHECK(now < time_to_fire);
- retry_timer_->Start(FROM_HERE, time_to_fire - now /* delay */,
- base::BindOnce(&HostVerifierImpl::UpdateRetryState,
- base::Unretained(this)));
- }
- void HostVerifierImpl::AttemptHostVerification() {
- absl::optional<multidevice::RemoteDeviceRef> current_host =
- host_backend_delegate_->GetMultiDeviceHostFromBackend();
- if (!current_host) {
- PA_LOG(WARNING) << "HostVerifierImpl::AttemptHostVerification(): Cannot "
- << "attempt verification because there is no active host.";
- return;
- }
- PA_LOG(VERBOSE) << "HostVerifierImpl::AttemptHostVerification(): Attempting "
- << "host verification now.";
- if (features::ShouldUseV1DeviceSync()) {
- if (current_host->instance_id().empty()) {
- device_sync_client_->FindEligibleDevices(
- multidevice::SoftwareFeature::kBetterTogetherHost,
- base::BindOnce(&HostVerifierImpl::OnFindEligibleDevicesResult,
- weak_ptr_factory_.GetWeakPtr()));
- } else {
- device_sync_client_->NotifyDevices(
- {current_host->instance_id()},
- cryptauthv2::TargetService::DEVICE_SYNC,
- multidevice::SoftwareFeature::kBetterTogetherHost,
- base::BindOnce(&HostVerifierImpl::OnNotifyDevicesFinished,
- weak_ptr_factory_.GetWeakPtr()));
- }
- } else {
- DCHECK(!current_host->instance_id().empty());
- device_sync_client_->NotifyDevices(
- {current_host->instance_id()}, cryptauthv2::TargetService::DEVICE_SYNC,
- multidevice::SoftwareFeature::kBetterTogetherHost,
- base::BindOnce(&HostVerifierImpl::OnNotifyDevicesFinished,
- weak_ptr_factory_.GetWeakPtr()));
- }
- }
- void HostVerifierImpl::OnFindEligibleDevicesResult(
- device_sync::mojom::NetworkRequestResult result,
- multidevice::RemoteDeviceRefList eligible_devices,
- multidevice::RemoteDeviceRefList ineligible_devices) {
- if (result != device_sync::mojom::NetworkRequestResult::kSuccess) {
- PA_LOG(WARNING) << "HostVerifierImpl::OnFindEligibleDevicesResult(): "
- << "FindEligibleDevices call failed. Retry is scheduled.";
- return;
- }
- // Now that the FindEligibleDevices call was sent successfully, the host phone
- // is expected to enable its supported features. This should trigger a push
- // message asking this Chromebook to sync these updated features, but in
- // practice it has been observed that the Chromebook sometimes does not
- // receive this message (see https://crbug.com/913816). Thus, schedule a sync
- // after the phone has had enough time to enable its features. Note that this
- // sync is canceled if the Chromebook does receive the push message.
- sync_timer_->Start(FROM_HERE, kSyncDelay,
- base::BindOnce(&HostVerifierImpl::OnSyncTimerFired,
- base::Unretained(this)));
- }
- void HostVerifierImpl::OnNotifyDevicesFinished(
- device_sync::mojom::NetworkRequestResult result) {
- if (result != device_sync::mojom::NetworkRequestResult::kSuccess) {
- PA_LOG(WARNING) << "HostVerifierImpl::OnNotifyDevicesFinished(): "
- << "NotifyDevices call failed. Retry is scheduled.";
- return;
- }
- // Now that the NotifyDevices call was sent successfully, the host phone is
- // expected to enable its supported features. This should trigger a push
- // message asking this Chromebook to sync these updated features, but in
- // practice it has been observed that the Chromebook sometimes does not
- // receive this message (see https://crbug.com/913816). Thus, schedule a sync
- // after the phone has had enough time to enable its features. Note that this
- // sync is canceled if the Chromebook does receive the push message.
- sync_timer_->Start(FROM_HERE, kSyncDelay,
- base::BindOnce(&HostVerifierImpl::OnSyncTimerFired,
- base::Unretained(this)));
- }
- void HostVerifierImpl::OnSyncTimerFired() {
- device_sync_client_->ForceSyncNow(base::DoNothing());
- }
- } // namespace multidevice_setup
- } // namespace ash
|