blob: b190321f9048ca147a30d200c199286c0faf8a81 [file] [log] [blame]
// 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 "chromeos/services/multidevice_setup/host_verifier_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/macros.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/components/multidevice/software_feature.h"
#include "chromeos/components/multidevice/software_feature_state.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/multidevice_setup/fake_host_backend_delegate.h"
#include "chromeos/services/multidevice_setup/fake_host_verifier.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
// Parameterized test types, indicating the following test scenarios:
enum class TestType {
// Use v1 DeviceSync and host does not have an Instance ID.
kYesV1NoInstanceId,
// Use v1 DeviceSync and host has an Instance ID.
kYesV1YesInstanceId,
// Do not use v1 DeviceSync and host has an Instance ID.
kNoV1YesInstanceId
};
const int64_t kTestTimeMs = 1500000000000;
constexpr const multidevice::SoftwareFeature kPotentialHostSoftwareFeatures[] =
{multidevice::SoftwareFeature::kSmartLockHost,
multidevice::SoftwareFeature::kInstantTetheringHost,
multidevice::SoftwareFeature::kMessagesForWebHost};
const char kRetryTimestampPrefName[] =
"multidevice_setup.current_retry_timestamp_ms";
const char kLastUsedTimeDeltaMsPrefName[] =
"multidevice_setup.last_used_time_delta_ms";
const int64_t kFirstRetryDeltaMs = 10 * 60 * 1000;
const double kExponentialBackoffMultiplier = 1.5;
enum class HostState {
// A device has not been marked as a BetterTogether host.
kHostNotSet,
// A device has been marked as a BetterTogether host, but that device has not
// enabled any of its individual features yet.
kHostSetButFeaturesDisabled,
// A device has been marked as a BetterTogether host, and that device has
// enabled at least one of its individual features.
kHostSetAndFeaturesEnabled
};
} // namespace
class MultiDeviceSetupHostVerifierImplTest
: public ::testing::TestWithParam<TestType> {
protected:
MultiDeviceSetupHostVerifierImplTest()
: test_device_(multidevice::CreateRemoteDeviceRefForTest()) {}
~MultiDeviceSetupHostVerifierImplTest() override = default;
// testing::Test:
void SetUp() override {
SetDeviceSyncFeatureFlags();
if (!HasInstanceId())
GetMutableRemoteDevice(test_device_)->instance_id.clear();
fake_host_backend_delegate_ = std::make_unique<FakeHostBackendDelegate>();
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
test_pref_service_ =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
HostVerifierImpl::RegisterPrefs(test_pref_service_->registry());
test_clock_ = std::make_unique<base::SimpleTestClock>();
test_clock_->SetNow(base::Time::FromJavaTime(kTestTimeMs));
}
void TearDown() override {
if (fake_observer_)
host_verifier_->RemoveObserver(fake_observer_.get());
}
void CreateVerifier(HostState initial_host_state,
int64_t initial_timer_pref_value = 0,
int64_t initial_time_delta_pref_value = 0) {
SetHostState(initial_host_state);
test_pref_service_->SetInt64(kRetryTimestampPrefName,
initial_timer_pref_value);
test_pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName,
initial_time_delta_pref_value);
auto mock_retry_timer = std::make_unique<base::MockOneShotTimer>();
mock_retry_timer_ = mock_retry_timer.get();
auto mock_sync_timer = std::make_unique<base::MockOneShotTimer>();
mock_sync_timer_ = mock_sync_timer.get();
host_verifier_ = HostVerifierImpl::Factory::Create(
fake_host_backend_delegate_.get(), fake_device_sync_client_.get(),
test_pref_service_.get(), test_clock_.get(),
std::move(mock_retry_timer), std::move(mock_sync_timer));
fake_observer_ = std::make_unique<FakeHostVerifierObserver>();
host_verifier_->AddObserver(fake_observer_.get());
}
void RemoveTestDeviceCryptoData() {
GetMutableRemoteDevice(test_device_)->public_key.clear();
GetMutableRemoteDevice(test_device_)->beacon_seeds.clear();
GetMutableRemoteDevice(test_device_)->persistent_symmetric_key.clear();
}
void SetHostState(HostState host_state) {
for (const auto& feature : kPotentialHostSoftwareFeatures) {
GetMutableRemoteDevice(test_device_)->software_features[feature] =
host_state == HostState::kHostSetAndFeaturesEnabled
? multidevice::SoftwareFeatureState::kEnabled
: multidevice::SoftwareFeatureState::kSupported;
}
if (host_state == HostState::kHostNotSet)
fake_host_backend_delegate_->NotifyHostChangedOnBackend(base::nullopt);
else
fake_host_backend_delegate_->NotifyHostChangedOnBackend(test_device_);
fake_device_sync_client_->NotifyNewDevicesSynced();
}
void VerifyState(bool expected_is_verified,
size_t expected_num_verified_events,
int64_t expected_retry_timestamp_value,
int64_t expected_retry_delta_value) {
EXPECT_EQ(expected_is_verified, host_verifier_->IsHostVerified());
EXPECT_EQ(expected_num_verified_events,
fake_observer_->num_host_verifications());
EXPECT_EQ(expected_retry_timestamp_value,
test_pref_service_->GetInt64(kRetryTimestampPrefName));
EXPECT_EQ(expected_retry_delta_value,
test_pref_service_->GetInt64(kLastUsedTimeDeltaMsPrefName));
// If a retry timestamp is set, the timer should be running.
EXPECT_EQ(expected_retry_timestamp_value != 0,
mock_retry_timer_->IsRunning());
}
void InvokePendingDeviceNotificationCall(bool success) {
if (HasInstanceId()) {
// Verify input parameters to NotifyDevices().
EXPECT_EQ(std::vector<std::string>{test_device_.instance_id()},
fake_device_sync_client_->notify_devices_inputs_queue()
.front()
.device_instance_ids);
EXPECT_EQ(cryptauthv2::TargetService::DEVICE_SYNC,
fake_device_sync_client_->notify_devices_inputs_queue()
.front()
.target_service);
EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
fake_device_sync_client_->notify_devices_inputs_queue()
.front()
.feature);
fake_device_sync_client_->InvokePendingNotifyDevicesCallback(
success
? device_sync::mojom::NetworkRequestResult::kSuccess
: device_sync::mojom::NetworkRequestResult::kInternalServerError);
} else {
// Verify input parameters to FindEligibleDevices().
EXPECT_EQ(multidevice::SoftwareFeature::kBetterTogetherHost,
fake_device_sync_client_->find_eligible_devices_inputs_queue()
.front()
.software_feature);
fake_device_sync_client_->InvokePendingFindEligibleDevicesCallback(
success
? device_sync::mojom::NetworkRequestResult::kSuccess
: device_sync::mojom::NetworkRequestResult::kInternalServerError,
multidevice::RemoteDeviceRefList() /* eligible_devices */,
multidevice::RemoteDeviceRefList() /* ineligible_devices */);
}
}
void SimulateRetryTimePassing(const base::TimeDelta& delta,
bool simulate_timeout = false) {
test_clock_->Advance(delta);
if (simulate_timeout)
mock_retry_timer_->Fire();
}
void FireSyncTimerAndVerifySyncOccurred() {
EXPECT_TRUE(mock_sync_timer_->IsRunning());
mock_sync_timer_->Fire();
fake_device_sync_client_->InvokePendingForceSyncNowCallback(
true /* success */);
SetHostState(HostState::kHostSetAndFeaturesEnabled);
}
FakeHostBackendDelegate* fake_host_backend_delegate() {
return fake_host_backend_delegate_.get();
}
private:
bool HasInstanceId() {
switch (GetParam()) {
case TestType::kYesV1YesInstanceId:
FALLTHROUGH;
case TestType::kNoV1YesInstanceId:
return true;
case TestType::kYesV1NoInstanceId:
return false;
}
}
void SetDeviceSyncFeatureFlags() {
bool use_v1;
switch (GetParam()) {
case TestType::kYesV1YesInstanceId:
FALLTHROUGH;
case TestType::kYesV1NoInstanceId:
use_v1 = true;
break;
case TestType::kNoV1YesInstanceId:
use_v1 = false;
break;
}
std::vector<base::Feature> enabled_features;
std::vector<base::Feature> disabled_features;
// These flags have no direct effect; however, v2 Enrollment and v2
// DeviceSync are prerequisites for disabling v1 DeviceSync.
enabled_features.push_back(chromeos::features::kCryptAuthV2Enrollment);
enabled_features.push_back(chromeos::features::kCryptAuthV2DeviceSync);
if (use_v1) {
disabled_features.push_back(
chromeos::features::kDisableCryptAuthV1DeviceSync);
} else {
enabled_features.push_back(
chromeos::features::kDisableCryptAuthV1DeviceSync);
}
scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
multidevice::RemoteDeviceRef test_device_;
std::unique_ptr<FakeHostVerifierObserver> fake_observer_;
std::unique_ptr<FakeHostBackendDelegate> fake_host_backend_delegate_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
test_pref_service_;
std::unique_ptr<base::SimpleTestClock> test_clock_;
base::MockOneShotTimer* mock_retry_timer_ = nullptr;
base::MockOneShotTimer* mock_sync_timer_ = nullptr;
std::unique_ptr<HostVerifier> host_verifier_;
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupHostVerifierImplTest);
};
TEST_P(MultiDeviceSetupHostVerifierImplTest, StartWithoutHost_SetAndVerify) {
CreateVerifier(HostState::kHostNotSet);
SetHostState(HostState::kHostSetButFeaturesDisabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
SimulateRetryTimePassing(base::TimeDelta::FromMinutes(1));
SetHostState(HostState::kHostSetAndFeaturesEnabled);
VerifyState(true /* expected_is_verified */,
1u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithoutHost_DeviceNotificationFails) {
CreateVerifier(HostState::kHostNotSet);
SetHostState(HostState::kHostSetButFeaturesDisabled);
// If the device notification call fails, a retry should still be scheduled.
InvokePendingDeviceNotificationCall(false /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest, SyncAfterDeviceNotification) {
CreateVerifier(HostState::kHostNotSet);
SetHostState(HostState::kHostSetButFeaturesDisabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
FireSyncTimerAndVerifySyncOccurred();
VerifyState(true /* expected_is_verified */,
1u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest, StartWithoutHost_Retry) {
CreateVerifier(HostState::kHostNotSet);
SetHostState(HostState::kHostSetButFeaturesDisabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
// Simulate enough time pasing to time out and retry.
SimulateRetryTimePassing(
base::TimeDelta::FromMilliseconds(kFirstRetryDeltaMs),
true /* simulate_timeout */);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_timestamp_value */,
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_delta_value */);
// Simulate the next retry timeout passing.
SimulateRetryTimePassing(
base::TimeDelta::FromMilliseconds(kFirstRetryDeltaMs *
kExponentialBackoffMultiplier),
true /* simulate_timeout */);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier *
kExponentialBackoffMultiplier
/* expected_retry_timestamp_value */,
kFirstRetryDeltaMs * kExponentialBackoffMultiplier *
kExponentialBackoffMultiplier
/* expected_retry_delta_value */);
// Succeed.
SetHostState(HostState::kHostSetAndFeaturesEnabled);
VerifyState(true /* expected_is_verified */,
1u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithUnverifiedHost_NoInitialPrefs) {
CreateVerifier(HostState::kHostSetButFeaturesDisabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithUnverifiedHost_InitialPrefs_HasNotPassedRetryTime) {
// Simulate starting up the device to find that the retry timer is in 5
// minutes.
CreateVerifier(HostState::kHostSetButFeaturesDisabled,
kTestTimeMs + base::TimeDelta::FromMinutes(5).InMilliseconds()
/* initial_timer_pref_value */,
kFirstRetryDeltaMs /* initial_time_delta_pref_value */);
SimulateRetryTimePassing(base::TimeDelta::FromMinutes(5),
true /* simulate_timeout */);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
kTestTimeMs + base::TimeDelta::FromMinutes(5).InMilliseconds() +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_timestamp_value */,
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithUnverifiedHost_InitialPrefs_AlreadyPassedRetryTime) {
// Simulate starting up the device to find that the retry timer had already
// fired 5 minutes ago.
CreateVerifier(HostState::kHostSetButFeaturesDisabled,
kTestTimeMs - base::TimeDelta::FromMinutes(5).InMilliseconds()
/* initial_timer_pref_value */,
kFirstRetryDeltaMs /* initial_time_delta_pref_value */);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
kTestTimeMs - base::TimeDelta::FromMinutes(5).InMilliseconds() +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_timestamp_value */,
kFirstRetryDeltaMs * kExponentialBackoffMultiplier
/* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithUnverifiedHost_InitialPrefs_AlreadyPassedMultipleRetryTimes) {
// Simulate starting up the device to find that the retry timer had already
// fired 20 minutes ago.
CreateVerifier(HostState::kHostSetButFeaturesDisabled,
kTestTimeMs - base::TimeDelta::FromMinutes(20).InMilliseconds()
/* initial_timer_pref_value */,
kFirstRetryDeltaMs /* initial_time_delta_pref_value */);
// Because the first delta is 10 minutes, the second delta is 10 * 1.5 = 15
// minutes. In this case, that means that *two* previous timeouts were missed,
// so the third one should be scheduled.
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
kTestTimeMs - base::TimeDelta::FromMinutes(20).InMilliseconds() +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier +
kFirstRetryDeltaMs * kExponentialBackoffMultiplier *
kExponentialBackoffMultiplier
/* expected_retry_timestamp_value */,
kFirstRetryDeltaMs * kExponentialBackoffMultiplier *
kExponentialBackoffMultiplier
/* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithVerifiedHost_HostChanges) {
CreateVerifier(HostState::kHostSetAndFeaturesEnabled);
VerifyState(true /* expected_is_verified */,
0u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
SetHostState(HostState::kHostNotSet);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
SetHostState(HostState::kHostSetButFeaturesDisabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest,
StartWithVerifiedHost_PendingRemoval) {
CreateVerifier(HostState::kHostSetAndFeaturesEnabled);
VerifyState(true /* expected_is_verified */,
0u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
fake_host_backend_delegate()->AttemptToSetMultiDeviceHostOnBackend(
base::nullopt /* host_device */);
VerifyState(false /* expected_is_verified */,
0u /* expected_num_verified_events */,
0 /* expected_retry_timestamp_value */,
0 /* expected_retry_delta_value */);
}
TEST_P(MultiDeviceSetupHostVerifierImplTest, HostMissingCryptoData) {
// Remove the host device's public key, persistent symmetric key, and beacon
// seeds. Without any of these, the host is not considered verified.
RemoveTestDeviceCryptoData();
CreateVerifier(HostState::kHostSetAndFeaturesEnabled);
InvokePendingDeviceNotificationCall(true /* success */);
VerifyState(
false /* expected_is_verified */, 0u /* expected_num_verified_events */,
kTestTimeMs + kFirstRetryDeltaMs /* expected_retry_timestamp_value */,
kFirstRetryDeltaMs /* expected_retry_delta_value */);
}
// Runs tests for the following scenarios.
// - Use v1 DeviceSync and host does not have an Instance ID.
// - Use v1 DeviceSync and host has an Instance ID.
// - Do not use v1 DeviceSync and host has an Instance ID.
// TODO(https://crbug.com/1019206): Remove when v1 DeviceSync is disabled, when
// all devices should have an Instance ID.
INSTANTIATE_TEST_SUITE_P(All,
MultiDeviceSetupHostVerifierImplTest,
::testing::Values(TestType::kYesV1NoInstanceId,
TestType::kYesV1YesInstanceId,
TestType::kNoV1YesInstanceId));
} // namespace multidevice_setup
} // namespace chromeos