diff options
Diffstat (limited to 'sync/internal_api/sync_manager.cc')
-rw-r--r-- | sync/internal_api/sync_manager.cc | 2575 |
1 files changed, 2575 insertions, 0 deletions
diff --git a/sync/internal_api/sync_manager.cc b/sync/internal_api/sync_manager.cc new file mode 100644 index 0000000..ebe870e --- /dev/null +++ b/sync/internal_api/sync_manager.cc @@ -0,0 +1,2575 @@ +// Copyright (c) 2012 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 "sync/internal_api/sync_manager.h" + +#include <string> + +#include "base/base64.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/json/json_writer.h" +#include "base/memory/ref_counted.h" +#include "base/metrics/histogram.h" +#include "base/observer_list.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "net/base/network_change_notifier.h" +#include "sync/engine/net/server_connection_manager.h" +#include "sync/engine/nigori_util.h" +#include "sync/engine/polling_constants.h" +#include "sync/engine/sync_scheduler.h" +#include "sync/engine/syncer_types.h" +#include "sync/internal_api/all_status.h" +#include "sync/internal_api/base_node.h" +#include "sync/internal_api/change_reorder_buffer.h" +#include "sync/internal_api/configure_reason.h" +#include "sync/internal_api/debug_info_event_listener.h" +#include "sync/internal_api/js_mutation_event_observer.h" +#include "sync/internal_api/js_sync_manager_observer.h" +#include "sync/internal_api/read_node.h" +#include "sync/internal_api/read_transaction.h" +#include "sync/internal_api/syncapi_internal.h" +#include "sync/internal_api/syncapi_server_connection_manager.h" +#include "sync/internal_api/user_share.h" +#include "sync/internal_api/write_node.h" +#include "sync/internal_api/write_transaction.h" +#include "sync/js/js_arg_list.h" +#include "sync/js/js_backend.h" +#include "sync/js/js_event_details.h" +#include "sync/js/js_event_handler.h" +#include "sync/js/js_reply_handler.h" +#include "sync/notifier/sync_notifier.h" +#include "sync/notifier/sync_notifier_observer.h" +#include "sync/protocol/encryption.pb.h" +#include "sync/protocol/proto_value_conversions.h" +#include "sync/protocol/sync.pb.h" +#include "sync/syncable/directory_change_delegate.h" +#include "sync/syncable/model_type.h" +#include "sync/syncable/model_type_payload_map.h" +#include "sync/syncable/syncable.h" +#include "sync/util/cryptographer.h" +#include "sync/util/get_session_name.h" +#include "sync/util/time.h" + +using base::TimeDelta; +using browser_sync::AllStatus; +using browser_sync::Cryptographer; +using browser_sync::Encryptor; +using browser_sync::JsArgList; +using browser_sync::JsBackend; +using browser_sync::JsEventDetails; +using browser_sync::JsEventHandler; +using browser_sync::JsEventHandler; +using browser_sync::JsReplyHandler; +using browser_sync::JsMutationEventObserver; +using browser_sync::JsSyncManagerObserver; +using browser_sync::ModelSafeWorkerRegistrar; +using browser_sync::kNigoriTag; +using browser_sync::KeyParams; +using browser_sync::ModelSafeRoutingInfo; +using browser_sync::ReportUnrecoverableErrorFunction; +using browser_sync::ServerConnectionEvent; +using browser_sync::ServerConnectionEventListener; +using browser_sync::SyncEngineEvent; +using browser_sync::SyncEngineEventListener; +using browser_sync::SyncScheduler; +using browser_sync::Syncer; +using browser_sync::UnrecoverableErrorHandler; +using browser_sync::WeakHandle; +using browser_sync::sessions::SyncSessionContext; +using syncable::ImmutableWriteTransactionInfo; +using syncable::ModelType; +using syncable::ModelTypeSet; +using syncable::SPECIFICS; +using sync_pb::GetUpdatesCallerInfo; + +namespace { + +// Delays for syncer nudges. +static const int kSyncRefreshDelayMsec = 500; +static const int kSyncSchedulerDelayMsec = 250; + +GetUpdatesCallerInfo::GetUpdatesSource GetSourceFromReason( + sync_api::ConfigureReason reason) { + switch (reason) { + case sync_api::CONFIGURE_REASON_RECONFIGURATION: + return GetUpdatesCallerInfo::RECONFIGURATION; + case sync_api::CONFIGURE_REASON_MIGRATION: + return GetUpdatesCallerInfo::MIGRATION; + case sync_api::CONFIGURE_REASON_NEW_CLIENT: + return GetUpdatesCallerInfo::NEW_CLIENT; + case sync_api::CONFIGURE_REASON_NEWLY_ENABLED_DATA_TYPE: + return GetUpdatesCallerInfo::NEWLY_SUPPORTED_DATATYPE; + default: + NOTREACHED(); + } + + return GetUpdatesCallerInfo::UNKNOWN; +} + +// The maximum number of times we will automatically overwrite the nigori node +// because the encryption keys don't match (per chrome instantiation). +static const int kNigoriOverwriteLimit = 10; + +} // namespace + +namespace sync_api { + +const int SyncManager::kDefaultNudgeDelayMilliseconds = 200; +const int SyncManager::kPreferencesNudgeDelayMilliseconds = 2000; + +// Maximum count and size for traffic recorder. +const unsigned int kMaxMessagesToRecord = 10; +const unsigned int kMaxMessageSizeToRecord = 5 * 1024; + +////////////////////////////////////////////////////////////////////////// +// SyncManager's implementation: SyncManager::SyncInternal +class SyncManager::SyncInternal + : public net::NetworkChangeNotifier::IPAddressObserver, + public browser_sync::Cryptographer::Observer, + public sync_notifier::SyncNotifierObserver, + public JsBackend, + public SyncEngineEventListener, + public ServerConnectionEventListener, + public syncable::DirectoryChangeDelegate { + public: + explicit SyncInternal(const std::string& name) + : name_(name), + weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), + enable_sync_tabs_for_other_clients_(false), + registrar_(NULL), + change_delegate_(NULL), + initialized_(false), + testing_mode_(NON_TEST), + observing_ip_address_changes_(false), + traffic_recorder_(kMaxMessagesToRecord, kMaxMessageSizeToRecord), + encryptor_(NULL), + unrecoverable_error_handler_(NULL), + report_unrecoverable_error_function_(NULL), + created_on_loop_(MessageLoop::current()), + nigori_overwrite_count_(0) { + // Pre-fill |notification_info_map_|. + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + notification_info_map_.insert( + std::make_pair(syncable::ModelTypeFromInt(i), NotificationInfo())); + } + + // Bind message handlers. + BindJsMessageHandler( + "getNotificationState", + &SyncManager::SyncInternal::GetNotificationState); + BindJsMessageHandler( + "getNotificationInfo", + &SyncManager::SyncInternal::GetNotificationInfo); + BindJsMessageHandler( + "getRootNodeDetails", + &SyncManager::SyncInternal::GetRootNodeDetails); + BindJsMessageHandler( + "getNodeSummariesById", + &SyncManager::SyncInternal::GetNodeSummariesById); + BindJsMessageHandler( + "getNodeDetailsById", + &SyncManager::SyncInternal::GetNodeDetailsById); + BindJsMessageHandler( + "getAllNodes", + &SyncManager::SyncInternal::GetAllNodes); + BindJsMessageHandler( + "getChildNodeIds", + &SyncManager::SyncInternal::GetChildNodeIds); + BindJsMessageHandler( + "getClientServerTraffic", + &SyncManager::SyncInternal::GetClientServerTraffic); + } + + virtual ~SyncInternal() { + CHECK(!initialized_); + } + + bool Init(const FilePath& database_location, + const WeakHandle<JsEventHandler>& event_handler, + const std::string& sync_server_and_path, + int port, + bool use_ssl, + const scoped_refptr<base::TaskRunner>& blocking_task_runner, + HttpPostProviderFactory* post_factory, + ModelSafeWorkerRegistrar* model_safe_worker_registrar, + browser_sync::ExtensionsActivityMonitor* + extensions_activity_monitor, + ChangeDelegate* change_delegate, + const std::string& user_agent, + const SyncCredentials& credentials, + bool enable_sync_tabs_for_other_clients, + sync_notifier::SyncNotifier* sync_notifier, + const std::string& restored_key_for_bootstrapping, + TestingMode testing_mode, + Encryptor* encryptor, + UnrecoverableErrorHandler* unrecoverable_error_handler, + ReportUnrecoverableErrorFunction + report_unrecoverable_error_function); + + // Sign into sync with given credentials. + // We do not verify the tokens given. After this call, the tokens are set + // and the sync DB is open. True if successful, false if something + // went wrong. + bool SignIn(const SyncCredentials& credentials); + + // Update tokens that we're using in Sync. Email must stay the same. + void UpdateCredentials(const SyncCredentials& credentials); + + // Called when the user disables or enables a sync type. + void UpdateEnabledTypes(); + + // Conditionally sets the flag in the Nigori node which instructs other + // clients to start syncing tabs. + void MaybeSetSyncTabsInNigoriNode(ModelTypeSet enabled_types); + + // Tell the sync engine to start the syncing process. + void StartSyncingNormally(); + + // Whether or not the Nigori node is encrypted using an explicit passphrase. + bool IsUsingExplicitPassphrase(); + + // Update the Cryptographer from the current nigori node and write back any + // necessary changes to the nigori node. We also detect missing encryption + // keys and write them into the nigori node. + // Also updates or adds the device information into the nigori node. + // Note: opens a transaction and can trigger an ON_PASSPHRASE_REQUIRED, so + // should only be called after syncapi is fully initialized. + // Calls the callback argument with true if cryptographer is ready, false + // otherwise. + void UpdateCryptographerAndNigori( + const std::string& chrome_version, + const base::Closure& done_callback); + + // Stores the current set of encryption keys (if the cryptographer is ready) + // and encrypted types into the nigori node. + void UpdateNigoriEncryptionState(Cryptographer* cryptographer, + WriteNode* nigori_node); + + // Updates the nigori node with any new encrypted types and then + // encrypts the nodes for those new data types as well as other + // nodes that should be encrypted but aren't. Triggers + // OnPassphraseRequired if the cryptographer isn't ready. + void RefreshEncryption(); + + // Re-encrypts the encrypted data types using the passed passphrase, and sets + // a flag in the nigori node specifying whether the current passphrase is + // explicit (custom passphrase) or non-explicit (GAIA). If the existing + // encryption passphrase is "explicit", the data cannot be re-encrypted and + // SetEncryptionPassphrase will do nothing. + // If !is_explicit and there are pending keys, we will attempt to decrypt them + // using this passphrase. If this fails, we will save this encryption key to + // be applied later after the pending keys are resolved. + // Calls FinishSetPassphrase at the end, which notifies observers of the + // result of the set passphrase operation, updates the nigori node, and does + // re-encryption. + void SetEncryptionPassphrase(const std::string& passphrase, bool is_explicit); + + // Provides a passphrase for decrypting the user's existing sync data. Calls + // FinishSetPassphrase at the end, which notifies observers of the result of + // the set passphrase operation, updates the nigori node, and does + // re-encryption. + void SetDecryptionPassphrase(const std::string& passphrase); + + // The final step of SetEncryptionPassphrase and SetDecryptionPassphrase that + // notifies observers of the result of the set passphrase operation, updates + // the nigori node, and does re-encryption. + // |success|: true if the operation was successful and false otherwise. If + // success == false, we send an OnPassphraseRequired notification. + // |bootstrap_token|: used to inform observers if the cryptographer's + // bootstrap token was updated. + // |is_explicit|: used to differentiate between a custom passphrase (true) and + // a GAIA passphrase that is implicitly used for encryption + // (false). + // |trans| and |nigori_node|: used to access data in the cryptographer. + void FinishSetPassphrase( + bool success, + const std::string& bootstrap_token, + bool is_explicit, + WriteTransaction* trans, + WriteNode* nigori_node); + + // Call periodically from a database-safe thread to persist recent changes + // to the syncapi model. + void SaveChanges(); + + // DirectoryChangeDelegate implementation. + // This listener is called upon completion of a syncable transaction, and + // builds the list of sync-engine initiated changes that will be forwarded to + // the SyncManager's Observers. + virtual void HandleTransactionCompleteChangeEvent( + ModelTypeSet models_with_changes) OVERRIDE; + virtual ModelTypeSet HandleTransactionEndingChangeEvent( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) OVERRIDE; + virtual void HandleCalculateChangesChangeEventFromSyncApi( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) OVERRIDE; + virtual void HandleCalculateChangesChangeEventFromSyncer( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) OVERRIDE; + + // Open the directory named with username_for_share + bool OpenDirectory(); + + // Cryptographer::Observer implementation. + virtual void OnEncryptedTypesChanged( + syncable::ModelTypeSet encrypted_types, + bool encrypt_everything) OVERRIDE; + + // SyncNotifierObserver implementation. + virtual void OnNotificationStateChange( + bool notifications_enabled) OVERRIDE; + + virtual void OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads, + sync_notifier::IncomingNotificationSource source) OVERRIDE; + + virtual void StoreState(const std::string& cookie) OVERRIDE; + + void AddObserver(SyncManager::Observer* observer); + void RemoveObserver(SyncManager::Observer* observer); + + // Accessors for the private members. + syncable::Directory* directory() { return share_.directory.get(); } + SyncAPIServerConnectionManager* connection_manager() { + return connection_manager_.get(); + } + SyncScheduler* scheduler() const { return scheduler_.get(); } + UserShare* GetUserShare() { + DCHECK(initialized_); + return &share_; + } + + // Return the currently active (validated) username for use with syncable + // types. + const std::string& username_for_share() const { + return share_.name; + } + + Status GetStatus(); + + void RequestNudge(const tracked_objects::Location& nudge_location); + + void RequestNudgeForDataTypes( + const tracked_objects::Location& nudge_location, + ModelTypeSet type); + + TimeDelta GetNudgeDelayTimeDelta(const ModelType& model_type); + + void NotifyCryptographerState(Cryptographer* cryptographer); + + // See SyncManager::Shutdown* for information. + void StopSyncingForShutdown(const base::Closure& callback); + void ShutdownOnSyncThread(); + + // If this is a deletion for a password, sets the legacy + // ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets + // |buffer|'s specifics field to contain the unencrypted data. + void SetExtraChangeRecordData(int64 id, + syncable::ModelType type, + ChangeReorderBuffer* buffer, + Cryptographer* cryptographer, + const syncable::EntryKernel& original, + bool existed_before, + bool exists_now); + + // Called only by our NetworkChangeNotifier. + virtual void OnIPAddressChanged() OVERRIDE; + + bool InitialSyncEndedForAllEnabledTypes() { + syncable::ModelTypeSet types; + ModelSafeRoutingInfo enabled_types; + registrar_->GetModelSafeRoutingInfo(&enabled_types); + for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin(); + i != enabled_types.end(); ++i) { + types.Put(i->first); + } + + return InitialSyncEndedForTypes(types, &share_); + } + + // SyncEngineEventListener implementation. + virtual void OnSyncEngineEvent(const SyncEngineEvent& event) OVERRIDE; + + // ServerConnectionEventListener implementation. + virtual void OnServerConnectionEvent( + const ServerConnectionEvent& event) OVERRIDE; + + // JsBackend implementation. + virtual void SetJsEventHandler( + const WeakHandle<JsEventHandler>& event_handler) OVERRIDE; + virtual void ProcessJsMessage( + const std::string& name, const JsArgList& args, + const WeakHandle<JsReplyHandler>& reply_handler) OVERRIDE; + + private: + struct NotificationInfo { + int total_count; + std::string payload; + + NotificationInfo() : total_count(0) {} + + ~NotificationInfo() {} + + // Returned pointer owned by the caller. + DictionaryValue* ToValue() const { + DictionaryValue* value = new DictionaryValue(); + value->SetInteger("totalCount", total_count); + value->SetString("payload", payload); + return value; + } + }; + + typedef std::map<syncable::ModelType, NotificationInfo> NotificationInfoMap; + typedef JsArgList + (SyncManager::SyncInternal::*UnboundJsMessageHandler)(const JsArgList&); + typedef base::Callback<JsArgList(const JsArgList&)> JsMessageHandler; + typedef std::map<std::string, JsMessageHandler> JsMessageHandlerMap; + + // Internal callback of UpdateCryptographerAndNigoriCallback. + void UpdateCryptographerAndNigoriCallback( + const std::string& chrome_version, + const base::Closure& done_callback, + const std::string& session_name); + + // Determine if the parents or predecessors differ between the old and new + // versions of an entry stored in |a| and |b|. Note that a node's index may + // change without its NEXT_ID changing if the node at NEXT_ID also moved (but + // the relative order is unchanged). To handle such cases, we rely on the + // caller to treat a position update on any sibling as updating the positions + // of all siblings. + static bool VisiblePositionsDiffer( + const syncable::EntryKernelMutation& mutation) { + const syncable::EntryKernel& a = mutation.original; + const syncable::EntryKernel& b = mutation.mutated; + // If the datatype isn't one where the browser model cares about position, + // don't bother notifying that data model of position-only changes. + if (!ShouldMaintainPosition( + syncable::GetModelTypeFromSpecifics(b.ref(SPECIFICS)))) + return false; + if (a.ref(syncable::NEXT_ID) != b.ref(syncable::NEXT_ID)) + return true; + if (a.ref(syncable::PARENT_ID) != b.ref(syncable::PARENT_ID)) + return true; + return false; + } + + // Determine if any of the fields made visible to clients of the Sync API + // differ between the versions of an entry stored in |a| and |b|. A return + // value of false means that it should be OK to ignore this change. + static bool VisiblePropertiesDiffer( + const syncable::EntryKernelMutation& mutation, + Cryptographer* cryptographer) { + const syncable::EntryKernel& a = mutation.original; + const syncable::EntryKernel& b = mutation.mutated; + const sync_pb::EntitySpecifics& a_specifics = a.ref(SPECIFICS); + const sync_pb::EntitySpecifics& b_specifics = b.ref(SPECIFICS); + DCHECK_EQ(syncable::GetModelTypeFromSpecifics(a_specifics), + syncable::GetModelTypeFromSpecifics(b_specifics)); + syncable::ModelType model_type = + syncable::GetModelTypeFromSpecifics(b_specifics); + // Suppress updates to items that aren't tracked by any browser model. + if (model_type < syncable::FIRST_REAL_MODEL_TYPE || + !a.ref(syncable::UNIQUE_SERVER_TAG).empty()) { + return false; + } + if (a.ref(syncable::IS_DIR) != b.ref(syncable::IS_DIR)) + return true; + if (!AreSpecificsEqual(cryptographer, + a.ref(syncable::SPECIFICS), + b.ref(syncable::SPECIFICS))) { + return true; + } + // We only care if the name has changed if neither specifics is encrypted + // (encrypted nodes blow away the NON_UNIQUE_NAME). + if (!a_specifics.has_encrypted() && !b_specifics.has_encrypted() && + a.ref(syncable::NON_UNIQUE_NAME) != b.ref(syncable::NON_UNIQUE_NAME)) + return true; + if (VisiblePositionsDiffer(mutation)) + return true; + return false; + } + + bool ChangeBuffersAreEmpty() { + for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { + if (!change_buffers_[i].IsEmpty()) + return false; + } + return true; + } + + void ReEncryptEverything(WriteTransaction* trans); + + // Called for every notification. This updates the notification statistics + // to be displayed in about:sync. + void UpdateNotificationInfo( + const syncable::ModelTypePayloadMap& type_payloads); + + // Checks for server reachabilty and requests a nudge. + void OnIPAddressChangedImpl(); + + // Helper function used only by the constructor. + void BindJsMessageHandler( + const std::string& name, UnboundJsMessageHandler unbound_message_handler); + + // Returned pointer is owned by the caller. + static DictionaryValue* NotificationInfoToValue( + const NotificationInfoMap& notification_info); + + // JS message handlers. + JsArgList GetNotificationState(const JsArgList& args); + JsArgList GetNotificationInfo(const JsArgList& args); + JsArgList GetRootNodeDetails(const JsArgList& args); + JsArgList GetAllNodes(const JsArgList& args); + JsArgList GetNodeSummariesById(const JsArgList& args); + JsArgList GetNodeDetailsById(const JsArgList& args); + JsArgList GetChildNodeIds(const JsArgList& args); + JsArgList GetClientServerTraffic(const JsArgList& args); + + FilePath database_path_; + + const std::string name_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory<SyncInternal> weak_ptr_factory_; + + // Thread-safe handle used by + // HandleCalculateChangesChangeEventFromSyncApi(), which can be + // called from any thread. Valid only between between calls to + // Init() and Shutdown(). + // + // TODO(akalin): Ideally, we wouldn't need to store this; instead, + // we'd have another worker class which implements + // HandleCalculateChangesChangeEventFromSyncApi() and we'd pass it a + // WeakHandle when we construct it. + WeakHandle<SyncInternal> weak_handle_this_; + + // |blocking_task_runner| is a TaskRunner to be used for tasks that + // may block on disk I/O. + scoped_refptr<base::TaskRunner> blocking_task_runner_; + + // We give a handle to share_ to clients of the API for use when constructing + // any transaction type. + UserShare share_; + + // This can be called from any thread, but only between calls to + // OpenDirectory() and ShutdownOnSyncThread(). + browser_sync::WeakHandle<SyncManager::ChangeObserver> change_observer_; + + ObserverList<SyncManager::Observer> observers_; + + // The ServerConnectionManager used to abstract communication between the + // client (the Syncer) and the sync server. + scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; + + // The scheduler that runs the Syncer. Needs to be explicitly + // Start()ed. + scoped_ptr<SyncScheduler> scheduler_; + + bool enable_sync_tabs_for_other_clients_; + + // The SyncNotifier which notifies us when updates need to be downloaded. + scoped_ptr<sync_notifier::SyncNotifier> sync_notifier_; + + // A multi-purpose status watch object that aggregates stats from various + // sync components. + AllStatus allstatus_; + + // Each element of this array is a store of change records produced by + // HandleChangeEvent during the CALCULATE_CHANGES step. The changes are + // segregated by model type, and are stored here to be processed and + // forwarded to the observer slightly later, at the TRANSACTION_ENDING + // step by HandleTransactionEndingChangeEvent. The list is cleared in the + // TRANSACTION_COMPLETE step by HandleTransactionCompleteChangeEvent. + ChangeReorderBuffer change_buffers_[syncable::MODEL_TYPE_COUNT]; + + // The entity that provides us with information about which types to sync. + // The instance is shared between the SyncManager and the Syncer. + ModelSafeWorkerRegistrar* registrar_; + + SyncManager::ChangeDelegate* change_delegate_; + + // Set to true once Init has been called. + bool initialized_; + + // Controls the disabling of certain SyncManager features. + // Can be used to disable communication with the server and the use of an + // on-disk file for maintaining syncer state. + // TODO(117836): Clean up implementation of SyncManager unit tests. + TestingMode testing_mode_; + + bool observing_ip_address_changes_; + + // Map used to store the notification info to be displayed in + // about:sync page. + NotificationInfoMap notification_info_map_; + + // These are for interacting with chrome://sync-internals. + JsMessageHandlerMap js_message_handlers_; + WeakHandle<JsEventHandler> js_event_handler_; + JsSyncManagerObserver js_sync_manager_observer_; + JsMutationEventObserver js_mutation_event_observer_; + + // This is for keeping track of client events to send to the server. + DebugInfoEventListener debug_info_event_listener_; + + browser_sync::TrafficRecorder traffic_recorder_; + + Encryptor* encryptor_; + UnrecoverableErrorHandler* unrecoverable_error_handler_; + ReportUnrecoverableErrorFunction report_unrecoverable_error_function_; + + MessageLoop* const created_on_loop_; + + // The number of times we've automatically (i.e. not via SetPassphrase or + // conflict resolver) updated the nigori's encryption keys in this chrome + // instantiation. + int nigori_overwrite_count_; +}; + +// A class to calculate nudge delays for types. +class NudgeStrategy { + public: + static TimeDelta GetNudgeDelayTimeDelta(const ModelType& model_type, + SyncManager::SyncInternal* core) { + NudgeDelayStrategy delay_type = GetNudgeDelayStrategy(model_type); + return GetNudgeDelayTimeDeltaFromType(delay_type, + model_type, + core); + } + + private: + // Possible types of nudge delay for datatypes. + // Note: These are just hints. If a sync happens then all dirty entries + // would be committed as part of the sync. + enum NudgeDelayStrategy { + // Sync right away. + IMMEDIATE, + + // Sync this change while syncing another change. + ACCOMPANY_ONLY, + + // The datatype does not use one of the predefined wait times but defines + // its own wait time logic for nudge. + CUSTOM, + }; + + static NudgeDelayStrategy GetNudgeDelayStrategy(const ModelType& type) { + switch (type) { + case syncable::AUTOFILL: + return ACCOMPANY_ONLY; + case syncable::PREFERENCES: + case syncable::SESSIONS: + return CUSTOM; + default: + return IMMEDIATE; + } + } + + static TimeDelta GetNudgeDelayTimeDeltaFromType( + const NudgeDelayStrategy& delay_type, const ModelType& model_type, + const SyncManager::SyncInternal* core) { + CHECK(core); + TimeDelta delay = TimeDelta::FromMilliseconds( + SyncManager::kDefaultNudgeDelayMilliseconds); + switch (delay_type) { + case IMMEDIATE: + delay = TimeDelta::FromMilliseconds( + SyncManager::kDefaultNudgeDelayMilliseconds); + break; + case ACCOMPANY_ONLY: + delay = TimeDelta::FromSeconds( + browser_sync::kDefaultShortPollIntervalSeconds); + break; + case CUSTOM: + switch (model_type) { + case syncable::PREFERENCES: + delay = TimeDelta::FromMilliseconds( + SyncManager::kPreferencesNudgeDelayMilliseconds); + break; + case syncable::SESSIONS: + delay = core->scheduler()->sessions_commit_delay(); + break; + default: + NOTREACHED(); + } + break; + default: + NOTREACHED(); + } + return delay; + } +}; + +SyncManager::ChangeDelegate::~ChangeDelegate() {} + +SyncManager::ChangeObserver::~ChangeObserver() {} + +SyncManager::Observer::~Observer() {} + +SyncManager::SyncManager(const std::string& name) + : data_(new SyncInternal(name)) {} + +SyncManager::Status::Status() + : notifications_enabled(false), + notifications_received(0), + unsynced_count(0), + encryption_conflicts(0), + hierarchy_conflicts(0), + simple_conflicts(0), + server_conflicts(0), + committed_count(0), + syncing(false), + initial_sync_ended(false), + updates_available(0), + updates_received(0), + reflected_updates_received(0), + tombstone_updates_received(0), + num_local_overwrites_total(0), + num_server_overwrites_total(0), + nonempty_get_updates(0), + empty_get_updates(0), + sync_cycles_with_commits(0), + sync_cycles_without_commits(0), + useless_sync_cycles(0), + useful_sync_cycles(0), + cryptographer_ready(false), + crypto_has_pending_keys(false) { +} + +SyncManager::Status::~Status() { +} + +bool SyncManager::Init( + const FilePath& database_location, + const WeakHandle<JsEventHandler>& event_handler, + const std::string& sync_server_and_path, + int sync_server_port, + bool use_ssl, + const scoped_refptr<base::TaskRunner>& blocking_task_runner, + HttpPostProviderFactory* post_factory, + ModelSafeWorkerRegistrar* registrar, + browser_sync::ExtensionsActivityMonitor* extensions_activity_monitor, + ChangeDelegate* change_delegate, + const std::string& user_agent, + const SyncCredentials& credentials, + bool enable_sync_tabs_for_other_clients, + sync_notifier::SyncNotifier* sync_notifier, + const std::string& restored_key_for_bootstrapping, + TestingMode testing_mode, + Encryptor* encryptor, + UnrecoverableErrorHandler* unrecoverable_error_handler, + ReportUnrecoverableErrorFunction report_unrecoverable_error_function) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(post_factory); + DVLOG(1) << "SyncManager starting Init..."; + std::string server_string(sync_server_and_path); + return data_->Init(database_location, + event_handler, + server_string, + sync_server_port, + use_ssl, + blocking_task_runner, + post_factory, + registrar, + extensions_activity_monitor, + change_delegate, + user_agent, + credentials, + enable_sync_tabs_for_other_clients, + sync_notifier, + restored_key_for_bootstrapping, + testing_mode, + encryptor, + unrecoverable_error_handler, + report_unrecoverable_error_function); +} + +void SyncManager::UpdateCredentials(const SyncCredentials& credentials) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->UpdateCredentials(credentials); +} + +void SyncManager::UpdateEnabledTypes() { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->UpdateEnabledTypes(); +} + +void SyncManager::MaybeSetSyncTabsInNigoriNode( + ModelTypeSet enabled_types) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->MaybeSetSyncTabsInNigoriNode(enabled_types); +} + +void SyncManager::ThrowUnrecoverableError() { + DCHECK(thread_checker_.CalledOnValidThread()); + ReadTransaction trans(FROM_HERE, GetUserShare()); + trans.GetWrappedTrans()->OnUnrecoverableError( + FROM_HERE, "Simulating unrecoverable error for testing purposes."); +} + +bool SyncManager::InitialSyncEndedForAllEnabledTypes() { + return data_->InitialSyncEndedForAllEnabledTypes(); +} + +void SyncManager::StartSyncingNormally() { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->StartSyncingNormally(); +} + +void SyncManager::SetEncryptionPassphrase(const std::string& passphrase, + bool is_explicit) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->SetEncryptionPassphrase(passphrase, is_explicit); +} + +void SyncManager::SetDecryptionPassphrase(const std::string& passphrase) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->SetDecryptionPassphrase(passphrase); +} + +void SyncManager::EnableEncryptEverything() { + DCHECK(thread_checker_.CalledOnValidThread()); + { + // Update the cryptographer to know we're now encrypting everything. + WriteTransaction trans(FROM_HERE, GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + // Only set encrypt everything if we know we can encrypt. This allows the + // user to cancel encryption if they have forgotten their passphrase. + if (cryptographer->is_ready()) + cryptographer->set_encrypt_everything(); + } + + // Reads from cryptographer so will automatically encrypt all + // datatypes and update the nigori node as necessary. Will trigger + // OnPassphraseRequired if necessary. + data_->RefreshEncryption(); +} + +bool SyncManager::EncryptEverythingEnabledForTest() const { + ReadTransaction trans(FROM_HERE, GetUserShare()); + return trans.GetCryptographer()->encrypt_everything(); +} + +bool SyncManager::IsUsingExplicitPassphrase() { + return data_ && data_->IsUsingExplicitPassphrase(); +} + +void SyncManager::RequestCleanupDisabledTypes() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (data_->scheduler()) + data_->scheduler()->ScheduleCleanupDisabledTypes(); +} + +void SyncManager::RequestClearServerData() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (data_->scheduler()) + data_->scheduler()->ScheduleClearUserData(); +} + +void SyncManager::RequestConfig( + ModelTypeSet types, ConfigureReason reason) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!data_->scheduler()) { + LOG(INFO) + << "SyncManager::RequestConfig: bailing out because scheduler is " + << "null"; + return; + } + StartConfigurationMode(base::Closure()); + data_->scheduler()->ScheduleConfig(types, GetSourceFromReason(reason)); +} + +void SyncManager::StartConfigurationMode(const base::Closure& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!data_->scheduler()) { + LOG(INFO) + << "SyncManager::StartConfigurationMode: could not start " + << "configuration mode because because scheduler is null"; + return; + } + data_->scheduler()->Start( + browser_sync::SyncScheduler::CONFIGURATION_MODE, callback); +} + +bool SyncManager::SyncInternal::Init( + const FilePath& database_location, + const WeakHandle<JsEventHandler>& event_handler, + const std::string& sync_server_and_path, + int port, + bool use_ssl, + const scoped_refptr<base::TaskRunner>& blocking_task_runner, + HttpPostProviderFactory* post_factory, + ModelSafeWorkerRegistrar* model_safe_worker_registrar, + browser_sync::ExtensionsActivityMonitor* extensions_activity_monitor, + ChangeDelegate* change_delegate, + const std::string& user_agent, + const SyncCredentials& credentials, + bool enable_sync_tabs_for_other_clients, + sync_notifier::SyncNotifier* sync_notifier, + const std::string& restored_key_for_bootstrapping, + TestingMode testing_mode, + Encryptor* encryptor, + UnrecoverableErrorHandler* unrecoverable_error_handler, + ReportUnrecoverableErrorFunction report_unrecoverable_error_function) { + CHECK(!initialized_); + + DCHECK(thread_checker_.CalledOnValidThread()); + + DVLOG(1) << "Starting SyncInternal initialization."; + + weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); + + blocking_task_runner_ = blocking_task_runner; + + registrar_ = model_safe_worker_registrar; + change_delegate_ = change_delegate; + testing_mode_ = testing_mode; + + enable_sync_tabs_for_other_clients_ = enable_sync_tabs_for_other_clients; + + sync_notifier_.reset(sync_notifier); + + AddObserver(&js_sync_manager_observer_); + SetJsEventHandler(event_handler); + + AddObserver(&debug_info_event_listener_); + + database_path_ = database_location.Append( + syncable::Directory::kSyncDatabaseFilename); + encryptor_ = encryptor; + unrecoverable_error_handler_ = unrecoverable_error_handler; + report_unrecoverable_error_function_ = report_unrecoverable_error_function; + share_.directory.reset( + new syncable::Directory(encryptor_, + unrecoverable_error_handler_, + report_unrecoverable_error_function_)); + + connection_manager_.reset(new SyncAPIServerConnectionManager( + sync_server_and_path, port, use_ssl, user_agent, post_factory)); + + net::NetworkChangeNotifier::AddIPAddressObserver(this); + observing_ip_address_changes_ = true; + + connection_manager()->AddListener(this); + + + // Test mode does not use a syncer context or syncer thread. + if (testing_mode_ == NON_TEST) { + // Build a SyncSessionContext and store the worker in it. + DVLOG(1) << "Sync is bringing up SyncSessionContext."; + std::vector<SyncEngineEventListener*> listeners; + listeners.push_back(&allstatus_); + listeners.push_back(this); + SyncSessionContext* context = new SyncSessionContext( + connection_manager_.get(), + directory(), + model_safe_worker_registrar, + extensions_activity_monitor, + listeners, + &debug_info_event_listener_, + &traffic_recorder_); + context->set_account_name(credentials.email); + // The SyncScheduler takes ownership of |context|. + scheduler_.reset(new SyncScheduler(name_, context, new Syncer())); + } + + bool signed_in = SignIn(credentials); + + if (signed_in) { + if (scheduler()) { + scheduler()->Start( + browser_sync::SyncScheduler::CONFIGURATION_MODE, base::Closure()); + } + + initialized_ = true; + + // Cryptographer should only be accessed while holding a + // transaction. Grabbing the user share for the transaction + // checks the initialization state, so this must come after + // |initialized_| is set to true. + ReadTransaction trans(FROM_HERE, GetUserShare()); + trans.GetCryptographer()->Bootstrap(restored_key_for_bootstrapping); + trans.GetCryptographer()->AddObserver(this); + } + + // Notify that initialization is complete. Note: This should be the last to + // execute if |signed_in| is false. Reason being in that case we would + // post a task to shutdown sync. But if this function posts any other tasks + // on the UI thread and if shutdown wins then that tasks would execute on + // a freed pointer. This is because UI thread is not shut down. + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnInitializationComplete( + MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), + signed_in)); + + if (!signed_in && testing_mode_ == NON_TEST) + return false; + + sync_notifier_->AddObserver(this); + + return signed_in; +} + +void SyncManager::SyncInternal::UpdateCryptographerAndNigori( + const std::string& chrome_version, + const base::Closure& done_callback) { + DCHECK(initialized_); + browser_sync::GetSessionName( + blocking_task_runner_, + base::Bind( + &SyncManager::SyncInternal::UpdateCryptographerAndNigoriCallback, + weak_ptr_factory_.GetWeakPtr(), + chrome_version, + done_callback)); +} + +void SyncManager::SyncInternal::UpdateNigoriEncryptionState( + Cryptographer* cryptographer, + WriteNode* nigori_node) { + DCHECK(nigori_node); + sync_pb::NigoriSpecifics nigori = nigori_node->GetNigoriSpecifics(); + + if (cryptographer->is_ready() && + nigori_overwrite_count_ < kNigoriOverwriteLimit) { + // Does not modify the encrypted blob if the unencrypted data already + // matches what is about to be written. + sync_pb::EncryptedData original_keys = nigori.encrypted(); + if (!cryptographer->GetKeys(nigori.mutable_encrypted())) + NOTREACHED(); + + if (nigori.encrypted().SerializeAsString() != + original_keys.SerializeAsString()) { + // We've updated the nigori node's encryption keys. In order to prevent + // a possible looping of two clients constantly overwriting each other, + // we limit the absolute number of overwrites per client instantiation. + nigori_overwrite_count_++; + UMA_HISTOGRAM_COUNTS("Sync.AutoNigoriOverwrites", + nigori_overwrite_count_); + } + + // Note: we don't try to set using_explicit_passphrase here since if that + // is lost the user can always set it again. The main point is to preserve + // the encryption keys so all data remains decryptable. + } + cryptographer->UpdateNigoriFromEncryptedTypes(&nigori); + + // If nothing has changed, this is a no-op. + nigori_node->SetNigoriSpecifics(nigori); +} + +void SyncManager::SyncInternal::UpdateCryptographerAndNigoriCallback( + const std::string& chrome_version, + const base::Closure& done_callback, + const std::string& session_name) { + if (!directory()->initial_sync_ended_for_type(syncable::NIGORI)) { + done_callback.Run(); // Should only happen during first time sync. + return; + } + + bool success = false; + { + WriteTransaction trans(FROM_HERE, GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + WriteNode node(&trans); + + if (node.InitByTagLookup(kNigoriTag) == sync_api::BaseNode::INIT_OK) { + sync_pb::NigoriSpecifics nigori(node.GetNigoriSpecifics()); + Cryptographer::UpdateResult result = cryptographer->Update(nigori); + if (result == Cryptographer::NEEDS_PASSPHRASE) { + sync_pb::EncryptedData pending_keys; + if (cryptographer->has_pending_keys()) + pending_keys = cryptographer->GetPendingKeys(); + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_DECRYPTION, + pending_keys)); + } + + + // Add or update device information. + bool contains_this_device = false; + for (int i = 0; i < nigori.device_information_size(); ++i) { + const sync_pb::DeviceInformation& device_information = + nigori.device_information(i); + if (device_information.cache_guid() == directory()->cache_guid()) { + // Update the version number in case it changed due to an update. + if (device_information.chrome_version() != chrome_version) { + sync_pb::DeviceInformation* mutable_device_information = + nigori.mutable_device_information(i); + mutable_device_information->set_chrome_version( + chrome_version); + } + contains_this_device = true; + } + } + + if (!contains_this_device) { + sync_pb::DeviceInformation* device_information = + nigori.add_device_information(); + device_information->set_cache_guid(directory()->cache_guid()); +#if defined(OS_CHROMEOS) + device_information->set_platform("ChromeOS"); +#elif defined(OS_LINUX) + device_information->set_platform("Linux"); +#elif defined(OS_MACOSX) + device_information->set_platform("Mac"); +#elif defined(OS_WIN) + device_information->set_platform("Windows"); +#endif + device_information->set_name(session_name); + device_information->set_chrome_version(chrome_version); + } + // Disabled to avoid nigori races. TODO(zea): re-enable. crbug.com/122837 + // node.SetNigoriSpecifics(nigori); + + // Make sure the nigori node has the up to date encryption info. + UpdateNigoriEncryptionState(cryptographer, &node); + + NotifyCryptographerState(cryptographer); + allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); + + success = cryptographer->is_ready(); + } else { + NOTREACHED(); + } + } + + if (success) + RefreshEncryption(); + done_callback.Run(); +} + +void SyncManager::SyncInternal::NotifyCryptographerState( + Cryptographer * cryptographer) { + // TODO(lipalani): Explore the possibility of hooking this up to + // SyncManager::Observer and making |AllStatus| a listener for that. + allstatus_.SetCryptographerReady(cryptographer->is_ready()); + allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); + debug_info_event_listener_.SetCryptographerReady(cryptographer->is_ready()); + debug_info_event_listener_.SetCrytographerHasPendingKeys( + cryptographer->has_pending_keys()); +} + +void SyncManager::SyncInternal::StartSyncingNormally() { + // Start the sync scheduler. + if (scheduler()) // NULL during certain unittests. + scheduler()->Start(SyncScheduler::NORMAL_MODE, base::Closure()); +} + +bool SyncManager::SyncInternal::OpenDirectory() { + DCHECK(!initialized_) << "Should only happen once"; + + // Set before Open(). + change_observer_ = + browser_sync::MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr()); + WeakHandle<syncable::TransactionObserver> transaction_observer( + browser_sync::MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr())); + + syncable::DirOpenResult open_result = syncable::NOT_INITIALIZED; + if (testing_mode_ == TEST_IN_MEMORY) { + open_result = directory()->OpenInMemoryForTest( + username_for_share(), this, transaction_observer); + } else { + open_result = directory()->Open( + database_path_, username_for_share(), this, transaction_observer); + } + if (open_result != syncable::OPENED) { + LOG(ERROR) << "Could not open share for:" << username_for_share(); + return false; + } + + connection_manager()->set_client_id(directory()->cache_guid()); + return true; +} + +bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(share_.name.empty()); + share_.name = credentials.email; + + DVLOG(1) << "Signing in user: " << username_for_share(); + if (!OpenDirectory()) + return false; + + // Retrieve and set the sync notifier state. This should be done + // only after OpenDirectory is called. + std::string unique_id = directory()->cache_guid(); + std::string state = directory()->GetNotificationState(); + DVLOG(1) << "Read notification unique ID: " << unique_id; + if (VLOG_IS_ON(1)) { + std::string encoded_state; + base::Base64Encode(state, &encoded_state); + DVLOG(1) << "Read notification state: " << encoded_state; + } + allstatus_.SetUniqueId(unique_id); + sync_notifier_->SetUniqueId(unique_id); + sync_notifier_->SetState(state); + + UpdateCredentials(credentials); + UpdateEnabledTypes(); + return true; +} + +void SyncManager::SyncInternal::UpdateCredentials( + const SyncCredentials& credentials) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(credentials.email, share_.name); + DCHECK(!credentials.email.empty()); + DCHECK(!credentials.sync_token.empty()); + + observing_ip_address_changes_ = true; + if (connection_manager()->set_auth_token(credentials.sync_token)) { + sync_notifier_->UpdateCredentials( + credentials.email, credentials.sync_token); + if (testing_mode_ == NON_TEST && initialized_) { + if (scheduler()) + scheduler()->OnCredentialsUpdated(); + } + } +} + +void SyncManager::SyncInternal::UpdateEnabledTypes() { + DCHECK(thread_checker_.CalledOnValidThread()); + ModelSafeRoutingInfo routes; + registrar_->GetModelSafeRoutingInfo(&routes); + const ModelTypeSet enabled_types = GetRoutingInfoTypes(routes); + sync_notifier_->UpdateEnabledTypes(enabled_types); + if (enable_sync_tabs_for_other_clients_) + MaybeSetSyncTabsInNigoriNode(enabled_types); +} + +void SyncManager::SyncInternal::MaybeSetSyncTabsInNigoriNode( + const ModelTypeSet enabled_types) { + // The initialized_ check is to ensure that we don't CHECK in GetUserShare + // when this is called on start-up. It's ok to ignore that case, since + // presumably this would've run when the user originally enabled sessions. + if (initialized_ && enabled_types.Has(syncable::SESSIONS)) { + WriteTransaction trans(FROM_HERE, GetUserShare()); + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + LOG(WARNING) << "Unable to set 'sync_tabs' bit because Nigori node not " + << "found."; + return; + } + + sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics()); + specifics.set_sync_tabs(true); + node.SetNigoriSpecifics(specifics); + } +} + +void SyncManager::SyncInternal::SetEncryptionPassphrase( + const std::string& passphrase, + bool is_explicit) { + // We do not accept empty passphrases. + if (passphrase.empty()) { + NOTREACHED() << "Cannot encrypt with an empty passphrase."; + return; + } + + // All accesses to the cryptographer are protected by a transaction. + WriteTransaction trans(FROM_HERE, GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + KeyParams key_params = {"localhost", "dummy", passphrase}; + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. + NOTREACHED(); + return; + } + + bool nigori_has_explicit_passphrase = + node.GetNigoriSpecifics().using_explicit_passphrase(); + std::string bootstrap_token; + sync_pb::EncryptedData pending_keys; + if (cryptographer->has_pending_keys()) + pending_keys = cryptographer->GetPendingKeys(); + bool success = false; + + + // There are six cases to handle here: + // 1. The user has no pending keys and is setting their current GAIA password + // as the encryption passphrase. This happens either during first time sync + // with a clean profile, or after re-authenticating on a profile that was + // already signed in with the cryptographer ready. + // 2. The user has no pending keys, and is overwriting an (already provided) + // implicit passphrase with an explicit (custom) passphrase. + // 3. The user has pending keys for an explicit passphrase that is somehow set + // to their current GAIA passphrase. + // 4. The user has pending keys encrypted with their current GAIA passphrase + // and the caller passes in the current GAIA passphrase. + // 5. The user has pending keys encrypted with an older GAIA passphrase + // and the caller passes in the current GAIA passphrase. + // 6. The user has previously done encryption with an explicit passphrase. + // Furthermore, we enforce the fact that the bootstrap encryption token will + // always be derived from the newest GAIA password if the account is using + // an implicit passphrase (even if the data is encrypted with an old GAIA + // password). If the account is using an explicit (custom) passphrase, the + // bootstrap token will be derived from the most recently provided explicit + // passphrase (that was able to decrypt the data). + if (!nigori_has_explicit_passphrase) { + if (!cryptographer->has_pending_keys()) { + if (cryptographer->AddKey(key_params)) { + // Case 1 and 2. We set a new GAIA passphrase when there are no pending + // keys (1), or overwriting an implicit passphrase with a new explicit + // one (2) when there are no pending keys. + DVLOG(1) << "Setting " << (is_explicit ? "explicit" : "implicit" ) + << " passphrase for encryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + NOTREACHED() << "Failed to add key to cryptographer."; + success = false; + } + } else { // cryptographer->has_pending_keys() == true + if (is_explicit) { + // This can only happen if the nigori node is updated with a new + // implicit passphrase while a client is attempting to set a new custom + // passphrase (race condition). + DVLOG(1) << "Failing because an implicit passphrase is already set."; + success = false; + } else { // is_explicit == false + if (cryptographer->DecryptPendingKeys(key_params)) { + // Case 4. We successfully decrypted with the implicit GAIA passphrase + // passed in. + DVLOG(1) << "Implicit internal passphrase accepted for decryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + // Case 5. Encryption was done with an old GAIA password, but we were + // provided with the current GAIA password. We need to generate a new + // bootstrap token to preserve it. We build a temporary cryptographer + // to allow us to extract these params without polluting our current + // cryptographer. + DVLOG(1) << "Implicit internal passphrase failed to decrypt, adding " + << "anyways as default passphrase and persisting via " + << "bootstrap token."; + Cryptographer temp_cryptographer(encryptor_); + temp_cryptographer.AddKey(key_params); + temp_cryptographer.GetBootstrapToken(&bootstrap_token); + // We then set the new passphrase as the default passphrase of the + // real cryptographer, even though we have pending keys. This is safe, + // as although Cryptographer::is_initialized() will now be true, + // is_ready() will remain false due to having pending keys. + cryptographer->AddKey(key_params); + success = false; + } + } // is_explicit + } // cryptographer->has_pending_keys() + } else { // nigori_has_explicit_passphrase == true + // Case 6. We do not want to override a previously set explicit passphrase, + // so we return a failure. + DVLOG(1) << "Failing because an explicit passphrase is already set."; + success = false; + } + + DVLOG_IF(1, !success) + << "Failure in SetEncryptionPassphrase; notifying and returning."; + DVLOG_IF(1, success) + << "Successfully set encryption passphrase; updating nigori and " + "reencrypting."; + + FinishSetPassphrase( + success, bootstrap_token, is_explicit, &trans, &node); +} + +void SyncManager::SyncInternal::SetDecryptionPassphrase( + const std::string& passphrase) { + // We do not accept empty passphrases. + if (passphrase.empty()) { + NOTREACHED() << "Cannot decrypt with an empty passphrase."; + return; + } + + // All accesses to the cryptographer are protected by a transaction. + WriteTransaction trans(FROM_HERE, GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + KeyParams key_params = {"localhost", "dummy", passphrase}; + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. + NOTREACHED(); + return; + } + + if (!cryptographer->has_pending_keys()) { + // Note that this *can* happen in a rare situation where data is + // re-encrypted on another client while a SetDecryptionPassphrase() call is + // in-flight on this client. It is rare enough that we choose to do nothing. + NOTREACHED() << "Attempt to set decryption passphrase failed because there " + << "were no pending keys."; + return; + } + + bool nigori_has_explicit_passphrase = + node.GetNigoriSpecifics().using_explicit_passphrase(); + std::string bootstrap_token; + sync_pb::EncryptedData pending_keys; + pending_keys = cryptographer->GetPendingKeys(); + bool success = false; + + // There are three cases to handle here: + // 7. We're using the current GAIA password to decrypt the pending keys. This + // happens when signing in to an account with a previously set implicit + // passphrase, where the data is already encrypted with the newest GAIA + // password. + // 8. The user is providing an old GAIA password to decrypt the pending keys. + // In this case, the user is using an implicit passphrase, but has changed + // their password since they last encrypted their data, and therefore + // their current GAIA password was unable to decrypt the data. This will + // happen when the user is setting up a new profile with a previously + // encrypted account (after changing passwords). + // 9. The user is providing a previously set explicit passphrase to decrypt + // the pending keys. + if (!nigori_has_explicit_passphrase) { + if (cryptographer->is_initialized()) { + // We only want to change the default encryption key to the pending + // one if the pending keybag already contains the current default. + // This covers the case where a different client re-encrypted + // everything with a newer gaia passphrase (and hence the keybag + // contains keys from all previously used gaia passphrases). + // Otherwise, we're in a situation where the pending keys are + // encrypted with an old gaia passphrase, while the default is the + // current gaia passphrase. In that case, we preserve the default. + Cryptographer temp_cryptographer(encryptor_); + temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys()); + if (temp_cryptographer.DecryptPendingKeys(key_params)) { + // Check to see if the pending bag of keys contains the current + // default key. + sync_pb::EncryptedData encrypted; + cryptographer->GetKeys(&encrypted); + if (temp_cryptographer.CanDecrypt(encrypted)) { + DVLOG(1) << "Implicit user provided passphrase accepted for " + << "decryption, overwriting default."; + // Case 7. The pending keybag contains the current default. Go ahead + // and update the cryptographer, letting the default change. + cryptographer->DecryptPendingKeys(key_params); + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + // Case 8. The pending keybag does not contain the current default + // encryption key. We decrypt the pending keys here, and in + // FinishSetPassphrase, re-encrypt everything with the current GAIA + // passphrase instead of the passphrase just provided by the user. + DVLOG(1) << "Implicit user provided passphrase accepted for " + << "decryption, restoring implicit internal passphrase " + << "as default."; + std::string bootstrap_token_from_current_key; + cryptographer->GetBootstrapToken( + &bootstrap_token_from_current_key); + cryptographer->DecryptPendingKeys(key_params); + // Overwrite the default from the pending keys. + cryptographer->AddKeyFromBootstrapToken( + bootstrap_token_from_current_key); + success = true; + } + } else { // !temp_cryptographer.DecryptPendingKeys(..) + DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; + success = false; + } // temp_cryptographer.DecryptPendingKeys(...) + } else { // cryptographer->is_initialized() == false + if (cryptographer->DecryptPendingKeys(key_params)) { + // This can happpen in two cases: + // - First time sync on android, where we'll never have a + // !user_provided passphrase. + // - This is a restart for a client that lost their bootstrap token. + // In both cases, we should go ahead and initialize the cryptographer + // and persist the new bootstrap token. + // + // Note: at this point, we cannot distinguish between cases 7 and 8 + // above. This user provided passphrase could be the current or the + // old. But, as long as we persist the token, there's nothing more + // we can do. + cryptographer->GetBootstrapToken(&bootstrap_token); + DVLOG(1) << "Implicit user provided passphrase accepted, initializing" + << " cryptographer."; + success = true; + } else { + DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; + success = false; + } + } // cryptographer->is_initialized() + } else { // nigori_has_explicit_passphrase == true + // Case 9. Encryption was done with an explicit passphrase, and we decrypt + // with the passphrase provided by the user. + if (cryptographer->DecryptPendingKeys(key_params)) { + DVLOG(1) << "Explicit passphrase accepted for decryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + DVLOG(1) << "Explicit passphrase failed to decrypt."; + success = false; + } + } // nigori_has_explicit_passphrase + + DVLOG_IF(1, !success) + << "Failure in SetDecryptionPassphrase; notifying and returning."; + DVLOG_IF(1, success) + << "Successfully set decryption passphrase; updating nigori and " + "reencrypting."; + + FinishSetPassphrase(success, + bootstrap_token, + nigori_has_explicit_passphrase, + &trans, + &node); +} + +void SyncManager::SyncInternal::FinishSetPassphrase( + bool success, + const std::string& bootstrap_token, + bool is_explicit, + WriteTransaction* trans, + WriteNode* nigori_node) { + Cryptographer* cryptographer = trans->GetCryptographer(); + NotifyCryptographerState(cryptographer); + + // It's possible we need to change the bootstrap token even if we failed to + // set the passphrase (for example if we need to preserve the new GAIA + // passphrase). + if (!bootstrap_token.empty()) { + DVLOG(1) << "Bootstrap token updated."; + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnBootstrapTokenUpdated(bootstrap_token)); + } + + if (!success) { + if (cryptographer->is_ready()) { + LOG(ERROR) << "Attempt to change passphrase failed while cryptographer " + << "was ready."; + } else if (cryptographer->has_pending_keys()) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_DECRYPTION, + cryptographer->GetPendingKeys())); + } else { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_ENCRYPTION, + sync_pb::EncryptedData())); + } + return; + } + + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseAccepted()); + DCHECK(cryptographer->is_ready()); + + // TODO(tim): Bug 58231. It would be nice if setting a passphrase didn't + // require messing with the Nigori node, because we can't set a passphrase + // until download conditions are met vs Cryptographer init. It seems like + // it's safe to defer this work. + sync_pb::NigoriSpecifics specifics(nigori_node->GetNigoriSpecifics()); + // Does not modify specifics.encrypted() if the original decrypted data was + // the same. + if (!cryptographer->GetKeys(specifics.mutable_encrypted())) { + NOTREACHED(); + return; + } + specifics.set_using_explicit_passphrase(is_explicit); + nigori_node->SetNigoriSpecifics(specifics); + + // Does nothing if everything is already encrypted or the cryptographer has + // pending keys. + ReEncryptEverything(trans); +} + +bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() { + ReadTransaction trans(FROM_HERE, &share_); + ReadNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. + NOTREACHED(); + return false; + } + + return node.GetNigoriSpecifics().using_explicit_passphrase(); +} + +void SyncManager::SyncInternal::RefreshEncryption() { + DCHECK(initialized_); + + WriteTransaction trans(FROM_HERE, GetUserShare()); + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + NOTREACHED() << "Unable to set encrypted datatypes because Nigori node not " + << "found."; + return; + } + + Cryptographer* cryptographer = trans.GetCryptographer(); + + if (!cryptographer->is_ready()) { + DVLOG(1) << "Attempting to encrypt datatypes when cryptographer not " + << "initialized, prompting for passphrase."; + // TODO(zea): this isn't really decryption, but that's the only way we have + // to prompt the user for a passsphrase. See http://crbug.com/91379. + sync_pb::EncryptedData pending_keys; + if (cryptographer->has_pending_keys()) + pending_keys = cryptographer->GetPendingKeys(); + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_DECRYPTION, + pending_keys)); + return; + } + + UpdateNigoriEncryptionState(cryptographer, &node); + + allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); + + // We reencrypt everything regardless of whether the set of encrypted + // types changed to ensure that any stray unencrypted entries are overwritten. + ReEncryptEverything(&trans); +} + +void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { + Cryptographer* cryptographer = trans->GetCryptographer(); + if (!cryptographer || !cryptographer->is_ready()) + return; + syncable::ModelTypeSet encrypted_types = GetEncryptedTypes(trans); + ModelSafeRoutingInfo routes; + registrar_->GetModelSafeRoutingInfo(&routes); + std::string tag; + for (syncable::ModelTypeSet::Iterator iter = encrypted_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == syncable::PASSWORDS || + iter.Get() == syncable::NIGORI || + routes.count(iter.Get()) == 0) + continue; + ReadNode type_root(trans); + tag = syncable::ModelTypeToRootTag(iter.Get()); + if (type_root.InitByTagLookup(tag) != sync_api::BaseNode::INIT_OK) { + // This can happen when we enable a datatype for the first time on restart + // (for example when we upgrade) and therefore haven't done the initial + // download for that type at the time we RefreshEncryption. There's + // nothing we can do for now, so just move on to the next type. + continue; + } + + // Iterate through all children of this datatype. + std::queue<int64> to_visit; + int64 child_id = type_root.GetFirstChildId(); + to_visit.push(child_id); + while (!to_visit.empty()) { + child_id = to_visit.front(); + to_visit.pop(); + if (child_id == kInvalidId) + continue; + + WriteNode child(trans); + if (child.InitByIdLookup(child_id) != sync_api::BaseNode::INIT_OK) { + NOTREACHED(); + continue; + } + if (child.GetIsFolder()) { + to_visit.push(child.GetFirstChildId()); + } + if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) { + // Rewrite the specifics of the node with encrypted data if necessary + // (only rewrite the non-unique folders). + child.ResetFromSpecifics(); + } + to_visit.push(child.GetSuccessorId()); + } + } + + if (routes.count(syncable::PASSWORDS) > 0) { + // Passwords are encrypted with their own legacy scheme. + ReadNode passwords_root(trans); + std::string passwords_tag = + syncable::ModelTypeToRootTag(syncable::PASSWORDS); + // It's possible we'll have the password routing info and not the password + // root if we attempted to set a passphrase before passwords was enabled. + if (passwords_root.InitByTagLookup(passwords_tag) == + sync_api::BaseNode::INIT_OK) { + int64 child_id = passwords_root.GetFirstChildId(); + while (child_id != kInvalidId) { + WriteNode child(trans); + if (child.InitByIdLookup(child_id) != sync_api::BaseNode::INIT_OK) { + NOTREACHED(); + return; + } + child.SetPasswordSpecifics(child.GetPasswordSpecifics()); + child_id = child.GetSuccessorId(); + } + } + } + + // NOTE: We notify from within a transaction. + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, OnEncryptionComplete()); +} + +SyncManager::~SyncManager() { + DCHECK(thread_checker_.CalledOnValidThread()); + delete data_; +} + +void SyncManager::AddObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->AddObserver(observer); +} + +void SyncManager::RemoveObserver(Observer* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->RemoveObserver(observer); +} + +void SyncManager::StopSyncingForShutdown(const base::Closure& callback) { + data_->StopSyncingForShutdown(callback); +} + +void SyncManager::SyncInternal::StopSyncingForShutdown( + const base::Closure& callback) { + DVLOG(2) << "StopSyncingForShutdown"; + if (scheduler()) // May be null in tests. + scheduler()->RequestStop(callback); + else + created_on_loop_->PostTask(FROM_HERE, callback); + + if (connection_manager_.get()) + connection_manager_->TerminateAllIO(); +} + +void SyncManager::ShutdownOnSyncThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->ShutdownOnSyncThread(); +} + +void SyncManager::SyncInternal::ShutdownOnSyncThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Prevent any in-flight method calls from running. Also + // invalidates |weak_handle_this_| and |change_observer_|. + weak_ptr_factory_.InvalidateWeakPtrs(); + js_mutation_event_observer_.InvalidateWeakPtrs(); + + scheduler_.reset(); + + SetJsEventHandler(WeakHandle<JsEventHandler>()); + RemoveObserver(&js_sync_manager_observer_); + + RemoveObserver(&debug_info_event_listener_); + + if (sync_notifier_.get()) { + sync_notifier_->RemoveObserver(this); + } + sync_notifier_.reset(); + + if (connection_manager_.get()) { + connection_manager_->RemoveListener(this); + } + connection_manager_.reset(); + + net::NetworkChangeNotifier::RemoveIPAddressObserver(this); + observing_ip_address_changes_ = false; + + if (initialized_ && directory()) { + { + // Cryptographer should only be accessed while holding a + // transaction. + ReadTransaction trans(FROM_HERE, GetUserShare()); + trans.GetCryptographer()->RemoveObserver(this); + } + directory()->SaveChanges(); + } + + share_.directory.reset(); + + change_delegate_ = NULL; + registrar_ = NULL; + + initialized_ = false; + + // We reset these here, since only now we know they will not be + // accessed from other threads (since we shut down everything). + change_observer_.Reset(); + weak_handle_this_.Reset(); +} + +void SyncManager::SyncInternal::OnIPAddressChanged() { + DVLOG(1) << "IP address change detected"; + if (!observing_ip_address_changes_) { + DVLOG(1) << "IP address change dropped."; + return; + } + + OnIPAddressChangedImpl(); +} + +void SyncManager::SyncInternal::OnIPAddressChangedImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (scheduler()) + scheduler()->OnConnectionStatusChange(); +} + +void SyncManager::SyncInternal::OnServerConnectionEvent( + const ServerConnectionEvent& event) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (event.connection_code == + browser_sync::HttpResponse::SERVER_CONNECTION_OK) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnConnectionStatusChange(CONNECTION_OK)); + } + + if (event.connection_code == browser_sync::HttpResponse::SYNC_AUTH_ERROR) { + observing_ip_address_changes_ = false; + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnConnectionStatusChange(CONNECTION_AUTH_ERROR)); + } + + if (event.connection_code == + browser_sync::HttpResponse::SYNC_SERVER_ERROR) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnConnectionStatusChange(CONNECTION_SERVER_ERROR)); + } +} + +void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( + ModelTypeSet models_with_changes) { + // This notification happens immediately after the transaction mutex is + // released. This allows work to be performed without blocking other threads + // from acquiring a transaction. + if (!change_delegate_) + return; + + // Call commit. + for (ModelTypeSet::Iterator it = models_with_changes.First(); + it.Good(); it.Inc()) { + change_delegate_->OnChangesComplete(it.Get()); + change_observer_.Call( + FROM_HERE, &SyncManager::ChangeObserver::OnChangesComplete, it.Get()); + } +} + +ModelTypeSet + SyncManager::SyncInternal::HandleTransactionEndingChangeEvent( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) { + // This notification happens immediately before a syncable WriteTransaction + // falls out of scope. It happens while the channel mutex is still held, + // and while the transaction mutex is held, so it cannot be re-entrant. + if (!change_delegate_ || ChangeBuffersAreEmpty()) + return ModelTypeSet(); + + // This will continue the WriteTransaction using a read only wrapper. + // This is the last chance for read to occur in the WriteTransaction + // that's closing. This special ReadTransaction will not close the + // underlying transaction. + ReadTransaction read_trans(GetUserShare(), trans); + + ModelTypeSet models_with_changes; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + const syncable::ModelType type = syncable::ModelTypeFromInt(i); + if (change_buffers_[type].IsEmpty()) + continue; + + ImmutableChangeRecordList ordered_changes; + // TODO(akalin): Propagate up the error further (see + // http://crbug.com/100907). + CHECK(change_buffers_[type].GetAllChangesInTreeOrder(&read_trans, + &ordered_changes)); + if (!ordered_changes.Get().empty()) { + change_delegate_-> + OnChangesApplied(type, &read_trans, ordered_changes); + change_observer_.Call(FROM_HERE, + &SyncManager::ChangeObserver::OnChangesApplied, + type, write_transaction_info.Get().id, ordered_changes); + models_with_changes.Put(type); + } + change_buffers_[i].Clear(); + } + return models_with_changes; +} + +void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) { + if (!scheduler()) { + return; + } + + // We have been notified about a user action changing a sync model. + LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << + "CALCULATE_CHANGES called with unapplied old changes."; + + // The mutated model type, or UNSPECIFIED if nothing was mutated. + syncable::ModelTypeSet mutated_model_types; + + const syncable::ImmutableEntryKernelMutationMap& mutations = + write_transaction_info.Get().mutations; + for (syncable::EntryKernelMutationMap::const_iterator it = + mutations.Get().begin(); it != mutations.Get().end(); ++it) { + if (!it->second.mutated.ref(syncable::IS_UNSYNCED)) { + continue; + } + + syncable::ModelType model_type = + syncable::GetModelTypeFromSpecifics( + it->second.mutated.ref(SPECIFICS)); + if (model_type < syncable::FIRST_REAL_MODEL_TYPE) { + NOTREACHED() << "Permanent or underspecified item changed via syncapi."; + continue; + } + + // Found real mutation. + if (model_type != syncable::UNSPECIFIED) { + mutated_model_types.Put(model_type); + } + } + + // Nudge if necessary. + if (!mutated_model_types.Empty()) { + if (weak_handle_this_.IsInitialized()) { + weak_handle_this_.Call(FROM_HERE, + &SyncInternal::RequestNudgeForDataTypes, + FROM_HERE, + mutated_model_types); + } else { + NOTREACHED(); + } + } +} + +void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, + syncable::ModelType type, ChangeReorderBuffer* buffer, + Cryptographer* cryptographer, const syncable::EntryKernel& original, + bool existed_before, bool exists_now) { + // If this is a deletion and the datatype was encrypted, we need to decrypt it + // and attach it to the buffer. + if (!exists_now && existed_before) { + sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS)); + if (type == syncable::PASSWORDS) { + // Passwords must use their own legacy ExtraPasswordChangeRecordData. + scoped_ptr<sync_pb::PasswordSpecificsData> data( + DecryptPasswordSpecifics(original_specifics, cryptographer)); + if (!data.get()) { + NOTREACHED(); + return; + } + buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); + } else if (original_specifics.has_encrypted()) { + // All other datatypes can just create a new unencrypted specifics and + // attach it. + const sync_pb::EncryptedData& encrypted = original_specifics.encrypted(); + if (!cryptographer->Decrypt(encrypted, &original_specifics)) { + NOTREACHED(); + return; + } + } + buffer->SetSpecificsForId(id, original_specifics); + } +} + +void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( + const ImmutableWriteTransactionInfo& write_transaction_info, + syncable::BaseTransaction* trans) { + // We only expect one notification per sync step, so change_buffers_ should + // contain no pending entries. + LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << + "CALCULATE_CHANGES called with unapplied old changes."; + + Cryptographer* crypto = directory()->GetCryptographer(trans); + const syncable::ImmutableEntryKernelMutationMap& mutations = + write_transaction_info.Get().mutations; + for (syncable::EntryKernelMutationMap::const_iterator it = + mutations.Get().begin(); it != mutations.Get().end(); ++it) { + bool existed_before = !it->second.original.ref(syncable::IS_DEL); + bool exists_now = !it->second.mutated.ref(syncable::IS_DEL); + + // Omit items that aren't associated with a model. + syncable::ModelType type = + syncable::GetModelTypeFromSpecifics( + it->second.mutated.ref(SPECIFICS)); + if (type < syncable::FIRST_REAL_MODEL_TYPE) + continue; + + int64 handle = it->first; + if (exists_now && !existed_before) + change_buffers_[type].PushAddedItem(handle); + else if (!exists_now && existed_before) + change_buffers_[type].PushDeletedItem(handle); + else if (exists_now && existed_before && + VisiblePropertiesDiffer(it->second, crypto)) { + change_buffers_[type].PushUpdatedItem( + handle, VisiblePositionsDiffer(it->second)); + } + + SetExtraChangeRecordData(handle, type, &change_buffers_[type], crypto, + it->second.original, existed_before, exists_now); + } +} + +SyncManager::Status SyncManager::SyncInternal::GetStatus() { + return allstatus_.status(); +} + +void SyncManager::SyncInternal::RequestNudge( + const tracked_objects::Location& location) { + if (scheduler()) { + scheduler()->ScheduleNudge( + TimeDelta::FromMilliseconds(0), browser_sync::NUDGE_SOURCE_LOCAL, + ModelTypeSet(), location); + } +} + +TimeDelta SyncManager::SyncInternal::GetNudgeDelayTimeDelta( + const ModelType& model_type) { + return NudgeStrategy::GetNudgeDelayTimeDelta(model_type, this); +} + +void SyncManager::SyncInternal::RequestNudgeForDataTypes( + const tracked_objects::Location& nudge_location, + ModelTypeSet types) { + if (!scheduler()) { + NOTREACHED(); + return; + } + + debug_info_event_listener_.OnNudgeFromDatatype(types.First().Get()); + + // TODO(lipalani) : Calculate the nudge delay based on all types. + base::TimeDelta nudge_delay = NudgeStrategy::GetNudgeDelayTimeDelta( + types.First().Get(), + this); + scheduler()->ScheduleNudge(nudge_delay, + browser_sync::NUDGE_SOURCE_LOCAL, + types, + nudge_location); +} + +void SyncManager::SyncInternal::OnSyncEngineEvent( + const SyncEngineEvent& event) { + DCHECK(thread_checker_.CalledOnValidThread()); + // Only send an event if this is due to a cycle ending and this cycle + // concludes a canonical "sync" process; that is, based on what is known + // locally we are "all happy" and up-to-date. There may be new changes on + // the server, but we'll get them on a subsequent sync. + // + // Notifications are sent at the end of every sync cycle, regardless of + // whether we should sync again. + if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) { + ModelSafeRoutingInfo enabled_types; + registrar_->GetModelSafeRoutingInfo(&enabled_types); + { + // Check to see if we need to notify the frontend that we have newly + // encrypted types or that we require a passphrase. + ReadTransaction trans(FROM_HERE, GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + // If we've completed a sync cycle and the cryptographer isn't ready + // yet, prompt the user for a passphrase. + if (cryptographer->has_pending_keys()) { + DVLOG(1) << "OnPassPhraseRequired Sent"; + sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys(); + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_DECRYPTION, + pending_keys)); + } else if (!cryptographer->is_ready() && + event.snapshot->initial_sync_ended.Has(syncable::NIGORI)) { + DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not " + << "ready"; + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnPassphraseRequired(sync_api::REASON_ENCRYPTION, + sync_pb::EncryptedData())); + } + + NotifyCryptographerState(cryptographer); + allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); + } + + if (!initialized_) { + LOG(INFO) << "OnSyncCycleCompleted not sent because sync api is not " + << "initialized"; + return; + } + + if (!event.snapshot->has_more_to_sync) { + // To account for a nigori node arriving with stale/bad data, we ensure + // that the nigori node is up to date at the end of each cycle. + WriteTransaction trans(FROM_HERE, GetUserShare()); + WriteNode nigori_node(&trans); + if (nigori_node.InitByTagLookup(kNigoriTag) == + sync_api::BaseNode::INIT_OK) { + Cryptographer* cryptographer = trans.GetCryptographer(); + UpdateNigoriEncryptionState(cryptographer, &nigori_node); + } + + DVLOG(1) << "Sending OnSyncCycleCompleted"; + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnSyncCycleCompleted(event.snapshot)); + } + + // This is here for tests, which are still using p2p notifications. + // + // TODO(chron): Consider changing this back to track has_more_to_sync + // only notify peers if a successful commit has occurred. + bool is_notifiable_commit = + (event.snapshot->syncer_status.num_successful_commits > 0); + if (is_notifiable_commit) { + if (sync_notifier_.get()) { + const ModelTypeSet changed_types = + syncable::ModelTypePayloadMapToEnumSet( + event.snapshot->source.types); + sync_notifier_->SendNotification(changed_types); + } else { + DVLOG(1) << "Not sending notification: sync_notifier_ is NULL"; + } + } + } + + if (event.what_happened == SyncEngineEvent::STOP_SYNCING_PERMANENTLY) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnStopSyncingPermanently()); + return; + } + + if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_SUCCEEDED) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnClearServerDataSucceeded()); + return; + } + + if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_FAILED) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnClearServerDataFailed()); + return; + } + + if (event.what_happened == SyncEngineEvent::UPDATED_TOKEN) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnUpdatedToken(event.updated_token)); + return; + } + + if (event.what_happened == SyncEngineEvent::ACTIONABLE_ERROR) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnActionableError( + event.snapshot->errors.sync_protocol_error)); + return; + } + +} + +void SyncManager::SyncInternal::SetJsEventHandler( + const WeakHandle<JsEventHandler>& event_handler) { + js_event_handler_ = event_handler; + js_sync_manager_observer_.SetJsEventHandler(js_event_handler_); + js_mutation_event_observer_.SetJsEventHandler(js_event_handler_); +} + +void SyncManager::SyncInternal::ProcessJsMessage( + const std::string& name, const JsArgList& args, + const WeakHandle<JsReplyHandler>& reply_handler) { + if (!initialized_) { + NOTREACHED(); + return; + } + + if (!reply_handler.IsInitialized()) { + DVLOG(1) << "Uninitialized reply handler; dropping unknown message " + << name << " with args " << args.ToString(); + return; + } + + JsMessageHandler js_message_handler = js_message_handlers_[name]; + if (js_message_handler.is_null()) { + DVLOG(1) << "Dropping unknown message " << name + << " with args " << args.ToString(); + return; + } + + reply_handler.Call(FROM_HERE, + &JsReplyHandler::HandleJsReply, + name, js_message_handler.Run(args)); +} + +void SyncManager::SyncInternal::BindJsMessageHandler( + const std::string& name, + UnboundJsMessageHandler unbound_message_handler) { + js_message_handlers_[name] = + base::Bind(unbound_message_handler, base::Unretained(this)); +} + +DictionaryValue* SyncManager::SyncInternal::NotificationInfoToValue( + const NotificationInfoMap& notification_info) { + DictionaryValue* value = new DictionaryValue(); + + for (NotificationInfoMap::const_iterator it = notification_info.begin(); + it != notification_info.end(); ++it) { + const std::string& model_type_str = + syncable::ModelTypeToString(it->first); + value->Set(model_type_str, it->second.ToValue()); + } + + return value; +} + +JsArgList SyncManager::SyncInternal::GetNotificationState( + const JsArgList& args) { + bool notifications_enabled = allstatus_.status().notifications_enabled; + ListValue return_args; + return_args.Append(Value::CreateBooleanValue(notifications_enabled)); + return JsArgList(&return_args); +} + +JsArgList SyncManager::SyncInternal::GetNotificationInfo( + const JsArgList& args) { + ListValue return_args; + return_args.Append(NotificationInfoToValue(notification_info_map_)); + return JsArgList(&return_args); +} + +JsArgList SyncManager::SyncInternal::GetRootNodeDetails( + const JsArgList& args) { + ReadTransaction trans(FROM_HERE, GetUserShare()); + ReadNode root(&trans); + root.InitByRootLookup(); + ListValue return_args; + return_args.Append(root.GetDetailsAsValue()); + return JsArgList(&return_args); +} + +JsArgList SyncManager::SyncInternal::GetClientServerTraffic( + const JsArgList& args) { + ListValue return_args; + ListValue* value = traffic_recorder_.ToValue(); + if (value != NULL) + return_args.Append(value); + return JsArgList(&return_args); +} + +namespace { + +int64 GetId(const ListValue& ids, int i) { + std::string id_str; + if (!ids.GetString(i, &id_str)) { + return kInvalidId; + } + int64 id = kInvalidId; + if (!base::StringToInt64(id_str, &id)) { + return kInvalidId; + } + return id; +} + +JsArgList GetNodeInfoById(const JsArgList& args, + UserShare* user_share, + DictionaryValue* (BaseNode::*info_getter)() const) { + CHECK(info_getter); + ListValue return_args; + ListValue* node_summaries = new ListValue(); + return_args.Append(node_summaries); + ListValue* id_list = NULL; + ReadTransaction trans(FROM_HERE, user_share); + if (args.Get().GetList(0, &id_list)) { + CHECK(id_list); + for (size_t i = 0; i < id_list->GetSize(); ++i) { + int64 id = GetId(*id_list, i); + if (id == kInvalidId) { + continue; + } + ReadNode node(&trans); + if (node.InitByIdLookup(id) != sync_api::BaseNode::INIT_OK) { + continue; + } + node_summaries->Append((node.*info_getter)()); + } + } + return JsArgList(&return_args); +} + +} // namespace + +JsArgList SyncManager::SyncInternal::GetNodeSummariesById( + const JsArgList& args) { + return GetNodeInfoById(args, GetUserShare(), &BaseNode::GetSummaryAsValue); +} + +JsArgList SyncManager::SyncInternal::GetNodeDetailsById( + const JsArgList& args) { + return GetNodeInfoById(args, GetUserShare(), &BaseNode::GetDetailsAsValue); +} + +JsArgList SyncManager::SyncInternal::GetAllNodes( + const JsArgList& args) { + ListValue return_args; + ListValue* result = new ListValue(); + return_args.Append(result); + + ReadTransaction trans(FROM_HERE, GetUserShare()); + std::vector<const syncable::EntryKernel*> entry_kernels; + trans.GetDirectory()->GetAllEntryKernels(trans.GetWrappedTrans(), + &entry_kernels); + + for (std::vector<const syncable::EntryKernel*>::const_iterator it = + entry_kernels.begin(); it != entry_kernels.end(); ++it) { + result->Append((*it)->ToValue()); + } + + return JsArgList(&return_args); +} + +JsArgList SyncManager::SyncInternal::GetChildNodeIds( + const JsArgList& args) { + ListValue return_args; + ListValue* child_ids = new ListValue(); + return_args.Append(child_ids); + int64 id = GetId(args.Get(), 0); + if (id != kInvalidId) { + ReadTransaction trans(FROM_HERE, GetUserShare()); + syncable::Directory::ChildHandles child_handles; + trans.GetDirectory()->GetChildHandlesByHandle(trans.GetWrappedTrans(), + id, &child_handles); + for (syncable::Directory::ChildHandles::const_iterator it = + child_handles.begin(); it != child_handles.end(); ++it) { + child_ids->Append(Value::CreateStringValue( + base::Int64ToString(*it))); + } + } + return JsArgList(&return_args); +} + +void SyncManager::SyncInternal::OnEncryptedTypesChanged( + syncable::ModelTypeSet encrypted_types, + bool encrypt_everything) { + // NOTE: We're in a transaction. + FOR_EACH_OBSERVER( + SyncManager::Observer, observers_, + OnEncryptedTypesChanged(encrypted_types, encrypt_everything)); +} + +void SyncManager::SyncInternal::OnNotificationStateChange( + bool notifications_enabled) { + DVLOG(1) << "P2P: Notifications enabled = " + << (notifications_enabled ? "true" : "false"); + allstatus_.SetNotificationsEnabled(notifications_enabled); + if (scheduler()) { + scheduler()->set_notifications_enabled(notifications_enabled); + } + if (js_event_handler_.IsInitialized()) { + DictionaryValue details; + details.Set("enabled", Value::CreateBooleanValue(notifications_enabled)); + js_event_handler_.Call(FROM_HERE, + &JsEventHandler::HandleJsEvent, + "onNotificationStateChange", + JsEventDetails(&details)); + } +} + +void SyncManager::SyncInternal::UpdateNotificationInfo( + const syncable::ModelTypePayloadMap& type_payloads) { + for (syncable::ModelTypePayloadMap::const_iterator it = type_payloads.begin(); + it != type_payloads.end(); ++it) { + NotificationInfo* info = ¬ification_info_map_[it->first]; + info->total_count++; + info->payload = it->second; + } +} + +void SyncManager::SyncInternal::OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads, + sync_notifier::IncomingNotificationSource source) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (source == sync_notifier::LOCAL_NOTIFICATION) { + if (scheduler()) { + scheduler()->ScheduleNudgeWithPayloads( + TimeDelta::FromMilliseconds(kSyncRefreshDelayMsec), + browser_sync::NUDGE_SOURCE_LOCAL_REFRESH, + type_payloads, FROM_HERE); + } + } else if (!type_payloads.empty()) { + if (scheduler()) { + scheduler()->ScheduleNudgeWithPayloads( + TimeDelta::FromMilliseconds(kSyncSchedulerDelayMsec), + browser_sync::NUDGE_SOURCE_NOTIFICATION, + type_payloads, FROM_HERE); + } + allstatus_.IncrementNotificationsReceived(); + UpdateNotificationInfo(type_payloads); + debug_info_event_listener_.OnIncomingNotification(type_payloads); + } else { + LOG(WARNING) << "Sync received notification without any type information."; + } + + if (js_event_handler_.IsInitialized()) { + DictionaryValue details; + ListValue* changed_types = new ListValue(); + details.Set("changedTypes", changed_types); + for (syncable::ModelTypePayloadMap::const_iterator + it = type_payloads.begin(); + it != type_payloads.end(); ++it) { + const std::string& model_type_str = + syncable::ModelTypeToString(it->first); + changed_types->Append(Value::CreateStringValue(model_type_str)); + } + details.SetString("source", (source == sync_notifier::LOCAL_NOTIFICATION) ? + "LOCAL_NOTIFICATION" : "REMOTE_NOTIFICATION"); + js_event_handler_.Call(FROM_HERE, + &JsEventHandler::HandleJsEvent, + "onIncomingNotification", + JsEventDetails(&details)); + } +} + +void SyncManager::SyncInternal::StoreState( + const std::string& state) { + if (!directory()) { + LOG(ERROR) << "Could not write notification state"; + // TODO(akalin): Propagate result callback all the way to this + // function and call it with "false" to signal failure. + return; + } + if (VLOG_IS_ON(1)) { + std::string encoded_state; + base::Base64Encode(state, &encoded_state); + DVLOG(1) << "Writing notification state: " << encoded_state; + } + directory()->SetNotificationState(state); + directory()->SaveChanges(); +} + +void SyncManager::SyncInternal::AddObserver( + SyncManager::Observer* observer) { + observers_.AddObserver(observer); +} + +void SyncManager::SyncInternal::RemoveObserver( + SyncManager::Observer* observer) { + observers_.RemoveObserver(observer); +} + +SyncManager::Status SyncManager::GetDetailedStatus() const { + return data_->GetStatus(); +} + +void SyncManager::SaveChanges() { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->SaveChanges(); +} + +void SyncManager::SyncInternal::SaveChanges() { + directory()->SaveChanges(); +} + +UserShare* SyncManager::GetUserShare() const { + return data_->GetUserShare(); +} + +void SyncManager::RefreshNigori(const std::string& chrome_version, + const base::Closure& done_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->UpdateCryptographerAndNigori( + chrome_version, + done_callback); +} + +TimeDelta SyncManager::GetNudgeDelayTimeDelta( + const ModelType& model_type) { + return data_->GetNudgeDelayTimeDelta(model_type); +} + +syncable::ModelTypeSet SyncManager::GetEncryptedDataTypesForTest() const { + ReadTransaction trans(FROM_HERE, GetUserShare()); + return GetEncryptedTypes(&trans); +} + +bool SyncManager::ReceivedExperimentalTypes(syncable::ModelTypeSet* to_add) + const { + ReadTransaction trans(FROM_HERE, GetUserShare()); + ReadNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != sync_api::BaseNode::INIT_OK) { + DVLOG(1) << "Couldn't find Nigori node."; + return false; + } + if (node.GetNigoriSpecifics().sync_tabs()) { + to_add->Put(syncable::SESSIONS); + return true; + } + return false; +} + +bool SyncManager::HasUnsyncedItems() const { + sync_api::ReadTransaction trans(FROM_HERE, GetUserShare()); + return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); +} + +void SyncManager::TriggerOnNotificationStateChangeForTest( + bool notifications_enabled) { + DCHECK(thread_checker_.CalledOnValidThread()); + data_->OnNotificationStateChange(notifications_enabled); +} + +void SyncManager::TriggerOnIncomingNotificationForTest( + ModelTypeSet model_types) { + DCHECK(thread_checker_.CalledOnValidThread()); + syncable::ModelTypePayloadMap model_types_with_payloads = + syncable::ModelTypePayloadMapFromEnumSet(model_types, + std::string()); + + data_->OnIncomingNotification(model_types_with_payloads, + sync_notifier::REMOTE_NOTIFICATION); +} + +const char* ConnectionStatusToString(ConnectionStatus status) { + switch (status) { + case CONNECTION_OK: + return "CONNECTION_OK"; + case CONNECTION_AUTH_ERROR: + return "CONNECTION_AUTH_ERROR"; + case CONNECTION_SERVER_ERROR: + return "CONNECTION_SERVER_ERROR"; + default: + NOTREACHED(); + return "INVALID_CONNECTION_STATUS"; + } +} + +// Helper function that converts a PassphraseRequiredReason value to a string. +const char* PassphraseRequiredReasonToString( + PassphraseRequiredReason reason) { + switch (reason) { + case REASON_PASSPHRASE_NOT_REQUIRED: + return "REASON_PASSPHRASE_NOT_REQUIRED"; + case REASON_ENCRYPTION: + return "REASON_ENCRYPTION"; + case REASON_DECRYPTION: + return "REASON_DECRYPTION"; + default: + NOTREACHED(); + return "INVALID_REASON"; + } +} + +// Helper function to determine if initial sync had ended for types. +bool InitialSyncEndedForTypes(syncable::ModelTypeSet types, + sync_api::UserShare* share) { + for (syncable::ModelTypeSet::Iterator i = types.First(); + i.Good(); i.Inc()) { + if (!share->directory->initial_sync_ended_for_type(i.Get())) + return false; + } + return true; +} + +syncable::ModelTypeSet GetTypesWithEmptyProgressMarkerToken( + syncable::ModelTypeSet types, + sync_api::UserShare* share) { + syncable::ModelTypeSet result; + for (syncable::ModelTypeSet::Iterator i = types.First(); + i.Good(); i.Inc()) { + sync_pb::DataTypeProgressMarker marker; + share->directory->GetDownloadProgress(i.Get(), &marker); + + if (marker.token().empty()) + result.Put(i.Get()); + + } + return result; +} + +} // namespace sync_api |