blob: e6f22b0f9dd50ab747842a099d657bd248cb350f [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/service/glue/sync_engine_impl.h"
#include <utility>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/sync/base/features.h"
#include "components/sync/engine/data_type_activation_response.h"
#include "components/sync/engine/events/protocol_event.h"
#include "components/sync/engine/nigori/nigori.h"
#include "components/sync/engine/polling_constants.h"
#include "components/sync/engine/sync_engine_host.h"
#include "components/sync/engine/sync_string_conversions.h"
#include "components/sync/invalidations/sync_invalidations_service.h"
#include "components/sync/service/active_devices_provider.h"
#include "components/sync/service/glue/sync_engine_backend.h"
#include "components/sync/service/glue/sync_transport_data_prefs.h"
namespace syncer {
namespace {
// Reads from prefs into a struct, to be posted across sequences.
SyncEngineBackend::RestoredLocalTransportData
RestoreLocalTransportDataFromPrefs(const SyncTransportDataPrefs& prefs) {
SyncEngineBackend::RestoredLocalTransportData result;
result.cache_guid = prefs.GetCacheGuid();
result.birthday = prefs.GetBirthday();
result.bag_of_chips = prefs.GetBagOfChips();
result.poll_interval = prefs.GetPollInterval();
if (result.poll_interval.is_zero()) {
result.poll_interval = kDefaultPollInterval;
}
return result;
}
enum class SyncTransportDataStartupState {
kValidData,
kEmptyCacheGuid,
kEmptyBirthday,
kGaiaIdMismatch,
};
std::string GenerateCacheGUID() {
// Generate a GUID with 128 bits of randomness.
constexpr int kGuidBytes = 128 / 8;
return base::Base64Encode(base::RandBytesAsVector(kGuidBytes));
}
SyncTransportDataStartupState ValidateSyncTransportData(
const SyncTransportDataPrefs& prefs,
const CoreAccountInfo& core_account_info) {
// If the cache GUID is empty, it most probably is because local sync data
// has been fully cleared. Let's treat this as invalid to make sure all prefs
// are cleared and a new random cache GUID generated.
if (prefs.GetCacheGuid().empty()) {
return SyncTransportDataStartupState::kEmptyCacheGuid;
}
// If cache GUID is initialized but the birthday isn't, it means the first
// sync cycle never completed (OnEngineInitialized()). This should be a rare
// case and theoretically harmless to resume, but as safety precaution, its
// simpler to regenerate the cache GUID and start from scratch, to avoid
// protocol violations (fetching updates requires that the request either has
// a birthday, or there should be no progress marker).
if (prefs.GetBirthday().empty()) {
return SyncTransportDataStartupState::kEmptyBirthday;
}
// Make sure the previously-syncing account (gaia ID) is equal to the current
// one (otherwise the data may be corrupt). Note that, for local sync, the
// authenticated account is always empty.
if (prefs.GetCurrentSyncingGaiaId() != core_account_info.gaia) {
// Note that an empty last-syncing-GaiaID is fine and expected if the user
// signed out and back in again.
if (!prefs.GetCurrentSyncingGaiaId().empty()) {
DLOG(WARNING) << "Found mismatching gaia ID in sync preferences";
return SyncTransportDataStartupState::kGaiaIdMismatch;
}
}
// All good: local sync data looks initialized and valid.
return SyncTransportDataStartupState::kValidData;
}
} // namespace
SyncEngineImpl::SyncEngineImpl(
const std::string& name,
SyncInvalidationsService* sync_invalidations_service,
std::unique_ptr<ActiveDevicesProvider> active_devices_provider,
std::unique_ptr<SyncTransportDataPrefs> prefs,
const base::FilePath& sync_data_folder,
scoped_refptr<base::SequencedTaskRunner> sync_task_runner)
: sync_task_runner_(std::move(sync_task_runner)),
name_(name),
prefs_(std::move(prefs)),
sync_invalidations_service_(sync_invalidations_service),
active_devices_provider_(std::move(active_devices_provider)),
engine_created_time_for_metrics_(base::TimeTicks::Now()) {
DCHECK(prefs_);
DCHECK(sync_invalidations_service_);
backend_ = base::MakeRefCounted<SyncEngineBackend>(
name_, sync_data_folder, weak_ptr_factory_.GetWeakPtr());
sync_invalidations_service_->AddTokenObserver(this);
}
SyncEngineImpl::~SyncEngineImpl() {
DCHECK(!backend_ && !host_) << "Must call Shutdown before destructor.";
}
void SyncEngineImpl::Initialize(InitParams params) {
DCHECK(params.host);
host_ = params.host;
const SyncTransportDataStartupState state =
ValidateSyncTransportData(*prefs_, params.authenticated_account_info);
if (state != SyncTransportDataStartupState::kValidData) {
// The local data is either uninitialized or corrupt, so let's throw
// everything away and start from scratch with a new cache GUID, which also
// cascades into datatypes throwing away their dangling sync metadata due to
// cache GUID mismatches.
prefs_->ClearForCurrentAccount();
prefs_->SetCacheGuid(GenerateCacheGUID());
prefs_->SetCurrentSyncingGaiaId(params.authenticated_account_info.gaia);
}
cached_cache_guid_ = prefs_->GetCacheGuid();
cached_birthday_ = prefs_->GetBirthday();
// `params.host` is not needed on the backend thread, so we null it out here
// to avoid accidentally using it on the wrong thread.
params.host = nullptr;
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoInitialize, backend_,
std::move(params),
RestoreLocalTransportDataFromPrefs(*prefs_)));
}
bool SyncEngineImpl::IsInitialized() const {
return initialized_;
}
void SyncEngineImpl::TriggerRefresh(const DataTypeSet& types) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoRefreshTypes, backend_, types));
}
void SyncEngineImpl::UpdateCredentials(const SyncCredentials& credentials) {
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoUpdateCredentials,
backend_, credentials));
}
void SyncEngineImpl::InvalidateCredentials() {
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoInvalidateCredentials, backend_));
}
std::string SyncEngineImpl::GetCacheGuid() const {
// The cached cache GUID should usually be identical to the one stored in
// prefs, but in some cases (when an account got removed from the device) the
// one in prefs may have been cleared.
return cached_cache_guid_;
}
std::string SyncEngineImpl::GetBirthday() const {
// The cached birthday should usually be identical to the one stored in
// prefs, but in some cases (when an account got removed from the device) the
// one in prefs may have been cleared.
return cached_birthday_;
}
base::Time SyncEngineImpl::GetLastSyncedTimeForDebugging() const {
return prefs_->GetLastSyncedTime();
}
void SyncEngineImpl::StartConfiguration() {
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoStartConfiguration, backend_));
}
void SyncEngineImpl::StartSyncingWithServer() {
DVLOG(1) << name_ << ": SyncEngineImpl::StartSyncingWithServer called.";
base::Time last_poll_time = prefs_->GetLastPollTime();
// If there's no known last poll time, that means this is the initial Sync
// startup. Treat it as if a poll just happened.
if (last_poll_time.is_null()) {
last_poll_time = base::Time::Now();
// Note: Persisting this is important to ensure that polling correctly
// resumes after a browser restart, even if no poll request happens during
// this run.
prefs_->SetLastPollTime(last_poll_time);
}
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoStartSyncing, backend_,
last_poll_time));
}
void SyncEngineImpl::StartHandlingInvalidations() {
// Sync invalidation service must be subscribed to data types by this time.
// Without that, incoming invalidations would be filtered out.
DCHECK(sync_invalidations_service_->GetInterestedDataTypes().has_value());
// Adding a listener several times is safe. Replays the last incoming messages
// received so far.
sync_invalidations_service_->AddListener(this);
// UpdateStandaloneInvalidationsState() must be called after AddListener(),
// the invalidations should not be considered as initialized until any
// outstanding FCM messages are handled.
// TODO(crbug.com/40260679): this logic is quite fragile and should be
// revisited.
UpdateStandaloneInvalidationsState();
}
void SyncEngineImpl::SetEncryptionPassphrase(
const std::string& passphrase,
const KeyDerivationParams& key_derivation_params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoSetEncryptionPassphrase,
backend_, passphrase, key_derivation_params));
}
void SyncEngineImpl::SetExplicitPassphraseDecryptionKey(
std::unique_ptr<Nigori> key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoSetExplicitPassphraseDecryptionKey,
backend_, std::move(key)));
}
void SyncEngineImpl::AddTrustedVaultDecryptionKeys(
const std::vector<std::vector<uint8_t>>& keys,
base::OnceClosure done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoAddTrustedVaultDecryptionKeys,
backend_, keys),
std::move(done_cb));
}
void SyncEngineImpl::StopSyncingForShutdown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Stop getting messages from the sync thread.
weak_ptr_factory_.InvalidateWeakPtrs();
// Immediately stop sending messages to the host.
host_ = nullptr;
backend_->ShutdownOnUIThread();
}
void SyncEngineImpl::Shutdown(ShutdownReason reason) {
// StopSyncingForShutdown() (which nulls out `host_`) should be
// called first.
DCHECK(!host_);
// It's safe to call RemoveListener even if AddListener wasn't called
// before.
DCHECK(sync_invalidations_service_);
sync_invalidations_service_->RemoveListener(this);
sync_invalidations_service_->RemoveTokenObserver(this);
sync_invalidations_service_ = nullptr;
last_enabled_types_.Clear();
active_devices_provider_->SetActiveDevicesChangedCallback(
base::RepeatingClosure());
data_type_connector_.reset();
// Shut down and destroy SyncManager.
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoShutdown, backend_, reason));
// Ensure that `backend_` destroyed inside Sync sequence, not inside current
// one.
sync_task_runner_->ReleaseSoon(FROM_HERE, std::move(backend_));
if (reason == ShutdownReason::DISABLE_SYNC_AND_CLEAR_DATA) {
prefs_->ClearCurrentSyncingGaiaId();
}
}
void SyncEngineImpl::ConfigureDataTypes(ConfigureParams params) {
DCHECK(Difference(params.to_download, ProtocolTypes()).empty());
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoConfigureSyncer, backend_,
std::move(params)));
}
void SyncEngineImpl::ConnectDataType(
DataType type,
std::unique_ptr<DataTypeActivationResponse> activation_response) {
DCHECK(ProtocolTypes().Has(type));
data_type_connector_->ConnectDataType(type, std::move(activation_response));
}
void SyncEngineImpl::DisconnectDataType(DataType type) {
data_type_connector_->DisconnectDataType(type);
}
const SyncStatus& SyncEngineImpl::GetDetailedStatus() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsInitialized());
return cached_status_;
}
void SyncEngineImpl::HasUnsyncedItemsForTest(
base::OnceCallback<void(bool)> cb) const {
DCHECK(IsInitialized());
sync_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::HasUnsyncedItemsForTest, backend_),
std::move(cb));
}
void SyncEngineImpl::GetThrottledDataTypesForTest(
base::OnceCallback<void(DataTypeSet)> cb) const {
DCHECK(IsInitialized());
// Instead of reading directly from `cached_status_.throttled_types`, issue
// a round trip to the backend sequence, in case there is an ongoing cycle
// that could update the throttled types.
sync_task_runner_->PostTaskAndReply(
FROM_HERE, base::DoNothing(),
base::BindOnce(
[](base::WeakPtr<SyncEngineImpl> engine,
base::OnceCallback<void(DataTypeSet)> cb) {
std::move(cb).Run(engine->cached_status_.throttled_types);
},
weak_ptr_factory_.GetMutableWeakPtr(), std::move(cb)));
}
void SyncEngineImpl::RequestBufferedProtocolEventsAndEnableForwarding() {
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&SyncEngineBackend::SendBufferedProtocolEventsAndEnableForwarding,
backend_));
}
void SyncEngineImpl::DisableProtocolEventForwarding() {
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DisableProtocolEventForwarding,
backend_));
}
void SyncEngineImpl::FinishConfigureDataTypesOnFrontendLoop(
const DataTypeSet enabled_types,
base::OnceClosure ready_task) {
last_enabled_types_ = enabled_types;
std::move(ready_task).Run();
}
void SyncEngineImpl::HandleInitializationSuccessOnFrontendLoop(
std::unique_ptr<DataTypeConnector> data_type_connector,
const std::string& birthday,
const std::string& bag_of_chips) {
TRACE_EVENT0("sync",
"SyncEngineImpl::HandleInitializationSuccessOnFrontendLoop");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
data_type_connector_ = std::move(data_type_connector);
initialized_ = true;
active_devices_provider_->SetActiveDevicesChangedCallback(base::BindRepeating(
&SyncEngineImpl::OnActiveDevicesChanged, weak_ptr_factory_.GetWeakPtr()));
// Initialize active devices count.
OnActiveDevicesChanged();
// Save initialization data to preferences.
prefs_->SetBirthday(birthday);
cached_birthday_ = prefs_->GetBirthday();
prefs_->SetBagOfChips(bag_of_chips);
// The very first time the backend initializes is effectively the first time
// we can say we successfully "synced". This gets determined based on whether
// there used to be local transport metadata or not.
bool is_first_time_sync_configure = false;
// NOTE: Keep this logic consistent with how
// SyncEngineFactoryImpl::HasTransportDataIncludingFirstSync()
// determines whether transport data exists.
if (prefs_->GetLastSyncedTime().is_null()) {
is_first_time_sync_configure = true;
UpdateLastSyncedTime();
}
host_->OnEngineInitialized(/*success=*/true, is_first_time_sync_configure);
}
void SyncEngineImpl::HandleInitializationFailureOnFrontendLoop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
host_->OnEngineInitialized(
/*success=*/false,
/*is_first_time_sync_configure=*/false);
}
void SyncEngineImpl::HandleSyncCycleCompletedOnFrontendLoop(
const SyncCycleSnapshot& snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Process any changes to the datatypes we're syncing.
// TODO(sync): add support for removing types.
if (!IsInitialized()) {
return;
}
UpdateLastSyncedTime();
if (!snapshot.poll_finish_time().is_null()) {
prefs_->SetLastPollTime(snapshot.poll_finish_time());
}
DCHECK(!snapshot.poll_interval().is_zero());
prefs_->SetPollInterval(snapshot.poll_interval());
prefs_->SetBagOfChips(snapshot.bag_of_chips());
host_->OnSyncCycleCompleted(snapshot);
}
void SyncEngineImpl::HandleActionableProtocolErrorEventOnFrontendLoop(
const SyncProtocolError& sync_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
host_->OnActionableProtocolError(sync_error);
}
void SyncEngineImpl::HandleMigrationRequestedOnFrontendLoop(DataTypeSet types) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
host_->OnMigrationNeededForTypes(types);
}
void SyncEngineImpl::OnInvalidatorStateChange(bool enabled) {
sync_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SyncEngineBackend::DoOnInvalidatorStateChange,
backend_, enabled));
}
void SyncEngineImpl::HandleConnectionStatusChangeOnFrontendLoop(
ConnectionStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "Connection status changed: " << ConnectionStatusToString(status);
host_->OnConnectionStatusChange(status);
}
void SyncEngineImpl::HandleProtocolEventOnFrontendLoop(
std::unique_ptr<ProtocolEvent> event) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
host_->OnProtocolEvent(*event);
}
void SyncEngineImpl::HandleSyncStatusChanged(const SyncStatus& status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const bool backed_off_types_changed =
(status.backed_off_types != cached_status_.backed_off_types);
const bool invalidation_status_changed =
(status.notifications_enabled != cached_status_.notifications_enabled);
const bool has_new_invalidated_data_types =
!cached_status_.invalidated_data_types.HasAll(
status.invalidated_data_types);
cached_status_ = status;
if (backed_off_types_changed) {
host_->OnBackedOffTypesChanged();
}
if (invalidation_status_changed) {
if (status.notifications_enabled && !invalidations_enabled_reported_) {
// Record the time since the engine was created until invalidations are
// initialized.
base::UmaHistogramMediumTimes(
"Sync.InvalidationsInitializationTime",
base::TimeTicks::Now() - engine_created_time_for_metrics_);
invalidations_enabled_reported_ = true;
}
host_->OnInvalidationStatusChanged();
}
if (has_new_invalidated_data_types) {
// Notify about any new data types having pending invalidations. When there
// are less such data types, this basically means that sync cycle has been
// finished, and `host_` will be notified via OnSyncCycleCompleted(), so
// there is no point in duplicating it.
host_->OnNewInvalidatedDataTypes();
}
}
void SyncEngineImpl::OnCookieJarChanged(bool account_mismatch,
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoOnCookieJarChanged, backend_,
account_mismatch, std::move(callback)));
}
bool SyncEngineImpl::IsNextPollTimeInThePast() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Time last_poll_time = prefs_->GetLastPollTime();
base::TimeDelta poll_interval = prefs_->GetPollInterval();
if (last_poll_time.is_null() || poll_interval.is_zero()) {
// It's likely the first startup so the very first poll interval is just
// starting.
return false;
}
base::Time now = base::Time::Now();
return now >= last_poll_time + poll_interval;
}
void SyncEngineImpl::ClearNigoriDataForMigration() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(backend_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoClearNigoriDataForMigration,
backend_));
}
void SyncEngineImpl::GetNigoriNodeForDebugging(AllNodesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(backend_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::GetNigoriNodeForDebugging, backend_,
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
void SyncEngineImpl::RecordNigoriMemoryUsageAndCountsHistograms() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&SyncEngineBackend::RecordNigoriMemoryUsageAndCountsHistograms,
backend_));
}
void SyncEngineImpl::OnInvalidationReceived(const std::string& payload) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::optional<DataTypeSet> interested_data_types =
sync_invalidations_service_->GetInterestedDataTypes();
// Interested data types must be initialized before handling invalidations to
// prevent missing incoming invalidations which were received during
// configuration.
DCHECK(interested_data_types.has_value());
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoOnStandaloneInvalidationReceived,
backend_, payload, *interested_data_types));
}
void SyncEngineImpl::OnFCMRegistrationTokenChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdateStandaloneInvalidationsState();
}
// static
std::string SyncEngineImpl::GenerateCacheGUIDForTest() {
return GenerateCacheGUID();
}
void SyncEngineImpl::OnCookieJarChangedDoneOnFrontendLoop(
base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(callback).Run();
}
void SyncEngineImpl::OnActiveDevicesChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
sync_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SyncEngineBackend::DoOnActiveDevicesChanged, backend_,
active_devices_provider_->CalculateInvalidationInfo(
cached_status_.cache_guid)));
}
void SyncEngineImpl::UpdateLastSyncedTime() {
prefs_->SetLastSyncedTime(base::Time::Now());
}
void SyncEngineImpl::UpdateStandaloneInvalidationsState() {
DCHECK(sync_invalidations_service_);
// Wait for FCM registration token and until the engine actually starts
// listening for invalidations (and processed the incoming messages if there
// are any).
if (!sync_invalidations_service_->GetFCMRegistrationToken().has_value() ||
!sync_invalidations_service_->HasListener(this)) {
OnInvalidatorStateChange(/*enabled=*/false);
return;
}
// This code should not be called when the token is empty (which means that
// sync standalone invalidations are disabled).
DCHECK_NE(sync_invalidations_service_->GetFCMRegistrationToken().value(), "");
// TODO(crbug.com/40266819): wait for FCM token to be committed before change
// the state to enabled.
OnInvalidatorStateChange(/*enabled=*/true);
}
} // namespace syncer