diff options
author | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-07 19:43:56 +0000 |
---|---|---|
committer | zea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-07 19:43:56 +0000 |
commit | 0eb4a12ea4740eedcbfeb6c939dca15353ffde13 (patch) | |
tree | be8f177aaabde0107af8cb77821ddabc656e50c0 /chrome/browser/sync/engine | |
parent | 4135ec77c12181afc0a941284395584f09fac2c0 (diff) | |
download | chromium_src-0eb4a12ea4740eedcbfeb6c939dca15353ffde13.zip chromium_src-0eb4a12ea4740eedcbfeb6c939dca15353ffde13.tar.gz chromium_src-0eb4a12ea4740eedcbfeb6c939dca15353ffde13.tar.bz2 |
[Sync] Don't commit items we know are not ready.
Items whose version does not match the server version or who require
(re)encryption are filtered out from the set of commit id's.
BUG=100660
TEST=sync_integration_tests, sync_unit_tests
Review URL: http://codereview.chromium.org/8400035
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@108893 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/engine')
-rw-r--r-- | chrome/browser/sync/engine/get_commit_ids_command.cc | 84 | ||||
-rw-r--r-- | chrome/browser/sync/engine/get_commit_ids_command.h | 17 | ||||
-rw-r--r-- | chrome/browser/sync/engine/syncer_unittest.cc | 228 |
3 files changed, 209 insertions, 120 deletions
diff --git a/chrome/browser/sync/engine/get_commit_ids_command.cc b/chrome/browser/sync/engine/get_commit_ids_command.cc index 89fc309..4bfc5e00 100644 --- a/chrome/browser/sync/engine/get_commit_ids_command.cc +++ b/chrome/browser/sync/engine/get_commit_ids_command.cc @@ -24,7 +24,8 @@ using sessions::SyncSession; using sessions::StatusController; GetCommitIdsCommand::GetCommitIdsCommand(int commit_batch_size) - : requested_commit_batch_size_(commit_batch_size) {} + : requested_commit_batch_size_(commit_batch_size), + passphrase_missing_(false) {} GetCommitIdsCommand::~GetCommitIdsCommand() {} @@ -35,17 +36,21 @@ void GetCommitIdsCommand::ExecuteImpl(SyncSession* session) { SyncerUtil::GetUnsyncedEntries(session->write_transaction(), &all_unsynced_handles); - Cryptographer *cryptographer = + Cryptographer* cryptographer = session->context()->directory_manager()->GetCryptographer( session->write_transaction()); if (cryptographer) { - FilterEntriesNeedingEncryption(cryptographer->GetEncryptedTypes(), - session->write_transaction(), - &all_unsynced_handles); - } + encrypted_types_ = cryptographer->GetEncryptedTypes(); + passphrase_missing_ = cryptographer->has_pending_keys(); + }; + // We filter out all unready entries from the set of unsynced handles to + // ensure we don't trigger useless sync cycles attempting to retry due to + // there being work to do. (see ScheduleNextSync in sync_scheduler) + FilterUnreadyEntries(session->write_transaction(), + &all_unsynced_handles); + StatusController* status = session->status_controller(); status->set_unsynced_handles(all_unsynced_handles); - BuildCommitIds(status->unsynced_handles(), session->write_transaction(), session->routing_info()); @@ -58,35 +63,62 @@ void GetCommitIdsCommand::ExecuteImpl(SyncSession* session) { status->set_commit_set(*ordered_commit_set_.get()); } -// We create a new list of unsynced handles which omits all handles to entries -// that require encryption but are written in plaintext. If any were found we -// overwrite |unsynced_handles| with this new list, else no change is made. -// Static. -void GetCommitIdsCommand::FilterEntriesNeedingEncryption( - const syncable::ModelTypeSet& encrypted_types, +namespace { + +// An entry ready for commit is defined as one not in conflict (SERVER_VERSION +// == BASE_VERSION || SERVER_VERSION == 0) and not requiring encryption +// (any entry containing an encrypted datatype while the cryptographer requires +// a passphrase is not ready for commit.) +bool IsEntryReadyForCommit(const syncable::ModelTypeSet& encrypted_types, + bool passphrase_missing, + const syncable::Entry& entry) { + if (!entry.Get(syncable::IS_UNSYNCED)) + return false; + + if (entry.Get(syncable::SERVER_VERSION) > 0 && + (entry.Get(syncable::SERVER_VERSION) > + entry.Get(syncable::BASE_VERSION))) { + // The local and server versions don't match. The item must be in + // conflict, so there's no point in attempting to commit. + DCHECK(entry.Get(syncable::IS_UNAPPLIED_UPDATE)); // In conflict. + // TODO(zea): switch this to DVLOG once it's clear bug 100660 is fixed. + VLOG(1) << "Excluding entry from commit due to version mismatch " + << entry; + return false; + } + + if (encrypted_types.count(entry.GetModelType()) > 0 && + (passphrase_missing || + syncable::EntryNeedsEncryption(encrypted_types, entry))) { + // This entry requires encryption but is not properly encrypted (possibly + // due to the cryptographer not being initialized or the user hasn't + // provided the most recent passphrase). + // TODO(zea): switch this to DVLOG once it's clear bug 100660 is fixed. + VLOG(1) << "Excluding entry from commit due to lack of encryption " + << entry; + return false; + } + + return true; +} + +} // namespace + +void GetCommitIdsCommand::FilterUnreadyEntries( syncable::BaseTransaction* trans, syncable::Directory::UnsyncedMetaHandles* unsynced_handles) { - bool removed_handles = false; syncable::Directory::UnsyncedMetaHandles::iterator iter; syncable::Directory::UnsyncedMetaHandles new_unsynced_handles; new_unsynced_handles.reserve(unsynced_handles->size()); - // TODO(zea): If this becomes a bottleneck, we should merge this loop into the - // AddCreatesAndMoves and AddDeletes loops. for (iter = unsynced_handles->begin(); iter != unsynced_handles->end(); ++iter) { syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter); - if (syncable::EntryNeedsEncryption(encrypted_types, entry)) { - // This entry requires encryption but is not encrypted (possibly due to - // the cryptographer not being initialized). Don't add it to our new list - // of unsynced handles. - removed_handles = true; - } else { + if (IsEntryReadyForCommit(encrypted_types_, passphrase_missing_, entry)) new_unsynced_handles.push_back(*iter); - } } - if (removed_handles) - *unsynced_handles = new_unsynced_handles; + if (new_unsynced_handles.size() != unsynced_handles->size()) + unsynced_handles->swap(new_unsynced_handles); } void GetCommitIdsCommand::AddUncommittedParentsAndTheirPredecessors( @@ -117,6 +149,8 @@ void GetCommitIdsCommand::AddUncommittedParentsAndTheirPredecessors( bool GetCommitIdsCommand::AddItem(syncable::Entry* item, OrderedCommitSet* result) { + if (!IsEntryReadyForCommit(encrypted_types_, passphrase_missing_, *item)) + return false; int64 item_handle = item->Get(syncable::META_HANDLE); if (result->HaveCommitItem(item_handle) || ordered_commit_set_->HaveCommitItem(item_handle)) { diff --git a/chrome/browser/sync/engine/get_commit_ids_command.h b/chrome/browser/sync/engine/get_commit_ids_command.h index 86024e1..8f41f2f 100644 --- a/chrome/browser/sync/engine/get_commit_ids_command.h +++ b/chrome/browser/sync/engine/get_commit_ids_command.h @@ -29,13 +29,6 @@ class GetCommitIdsCommand : public SyncerCommand { // SyncerCommand implementation. virtual void ExecuteImpl(sessions::SyncSession* session); - // Filter |unsynced_handles| to exclude all handles to entries that require - // encryption but are in plaintext. - static void FilterEntriesNeedingEncryption( - const syncable::ModelTypeSet& encrypted_types, - syncable::BaseTransaction* trans, - syncable::Directory::UnsyncedMetaHandles* unsynced_handles); - // Builds a vector of IDs that should be committed. void BuildCommitIds(const vector<int64>& unsynced_handles, syncable::WriteTransaction* write_transaction, @@ -114,6 +107,14 @@ class GetCommitIdsCommand : public SyncerCommand { }; private: + // Removes all entries not ready for commit from |unsynced_handles|. + // An entry is considered unready for commit if it's in conflict or requires + // (re)encryption. Any datatype requiring encryption while the cryptographer + // is missing a passphrase is considered unready for commit. + void FilterUnreadyEntries( + syncable::BaseTransaction* trans, + syncable::Directory::UnsyncedMetaHandles* unsynced_handles); + void AddUncommittedParentsAndTheirPredecessors( syncable::BaseTransaction* trans, syncable::Id parent_id, @@ -144,6 +145,8 @@ class GetCommitIdsCommand : public SyncerCommand { scoped_ptr<sessions::OrderedCommitSet> ordered_commit_set_; int requested_commit_batch_size_; + bool passphrase_missing_; + syncable::ModelTypeSet encrypted_types_; DISALLOW_COPY_AND_ASSIGN(GetCommitIdsCommand); }; diff --git a/chrome/browser/sync/engine/syncer_unittest.cc b/chrome/browser/sync/engine/syncer_unittest.cc index 241526c..9985120 100644 --- a/chrome/browser/sync/engine/syncer_unittest.cc +++ b/chrome/browser/sync/engine/syncer_unittest.cc @@ -551,54 +551,145 @@ TEST_F(SyncerTest, GetCommitIdsCommandTruncates) { DoTruncationTest(dir, unsynced_handle_view, expected_order); } -TEST_F(SyncerTest, GetCommitIdsFilterEntriesNeedingEncryption) { +TEST_F(SyncerTest, GetCommitIdsFiltersUnreadyEntries) { ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); ASSERT_TRUE(dir.good()); - int64 handle_x = CreateUnsyncedDirectory("X", ids_.MakeLocal("x")); - int64 handle_b = CreateUnsyncedDirectory("B", ids_.MakeLocal("b")); - int64 handle_c = CreateUnsyncedDirectory("C", ids_.MakeLocal("c")); - int64 handle_e = CreateUnsyncedDirectory("E", ids_.MakeLocal("e")); - int64 handle_f = CreateUnsyncedDirectory("F", ids_.MakeLocal("f")); + KeyParams key_params = {"localhost", "dummy", "foobar"}; sync_pb::EncryptedData encrypted; sync_pb::EntitySpecifics encrypted_bookmark; encrypted_bookmark.mutable_encrypted(); AddDefaultExtensionValue(syncable::BOOKMARKS, &encrypted_bookmark); + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); + mock_server_->AddUpdateDirectory(2, 0, "B", 10, 10); + mock_server_->AddUpdateDirectory(3, 0, "C", 10, 10); + mock_server_->AddUpdateDirectory(4, 0, "D", 10, 10); + SyncShareAsDelegate(); + // Server side change will put A in conflict. + mock_server_->AddUpdateDirectory(1, 0, "A", 20, 20); { + // Mark bookmarks as encrypted and set the cryptographer to have pending + // keys. WriteTransaction wtrans(FROM_HERE, UNITTEST, dir); - MutableEntry entry_x(&wtrans, GET_BY_HANDLE, handle_x); - MutableEntry entry_b(&wtrans, GET_BY_HANDLE, handle_b); - MutableEntry entry_c(&wtrans, GET_BY_HANDLE, handle_c); - MutableEntry entry_e(&wtrans, GET_BY_HANDLE, handle_e); - MutableEntry entry_f(&wtrans, GET_BY_HANDLE, handle_f); - entry_x.Put(SPECIFICS, encrypted_bookmark); - entry_x.Put(NON_UNIQUE_NAME, kEncryptedString); - entry_b.Put(SPECIFICS, DefaultBookmarkSpecifics()); - entry_c.Put(SPECIFICS, DefaultBookmarkSpecifics()); - entry_e.Put(SPECIFICS, encrypted_bookmark); - entry_e.Put(NON_UNIQUE_NAME, kEncryptedString); - entry_f.Put(SPECIFICS, DefaultPreferencesSpecifics()); - } - - syncable::ModelTypeSet encrypted_types; - encrypted_types.insert(syncable::BOOKMARKS); - syncable::Directory::UnsyncedMetaHandles unsynced_handles; - unsynced_handles.push_back(handle_x); - unsynced_handles.push_back(handle_b); - unsynced_handles.push_back(handle_c); - unsynced_handles.push_back(handle_e); - unsynced_handles.push_back(handle_f); - // The unencrypted bookmarks should be removed from the list. - syncable::Directory::UnsyncedMetaHandles expected_handles; - expected_handles.push_back(handle_x); // Was encrypted. - expected_handles.push_back(handle_e); // Was encrypted. - expected_handles.push_back(handle_f); // Does not require encryption. - { + browser_sync::Cryptographer other_cryptographer; + other_cryptographer.AddKey(key_params); + sync_pb::EntitySpecifics specifics; + sync_pb::NigoriSpecifics* nigori = + specifics.MutableExtension(sync_pb::nigori); + other_cryptographer.GetKeys(nigori->mutable_encrypted()); + nigori->set_encrypt_bookmarks(true); + syncdb_.manager()->GetCryptographer(&wtrans)->Update(*nigori); + + // In conflict but properly encrypted. + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); + ASSERT_TRUE(A.good()); + A.Put(IS_UNSYNCED, true); + A.Put(SPECIFICS, encrypted_bookmark); + A.Put(NON_UNIQUE_NAME, kEncryptedString); + // Not in conflict and properly encrypted. + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); + ASSERT_TRUE(B.good()); + B.Put(IS_UNSYNCED, true); + B.Put(SPECIFICS, encrypted_bookmark); + B.Put(NON_UNIQUE_NAME, kEncryptedString); + // Unencrypted specifics. + MutableEntry C(&wtrans, GET_BY_ID, ids_.FromNumber(3)); + ASSERT_TRUE(C.good()); + C.Put(IS_UNSYNCED, true); + C.Put(NON_UNIQUE_NAME, kEncryptedString); + // Unencrypted non_unique_name. + MutableEntry D(&wtrans, GET_BY_ID, ids_.FromNumber(4)); + ASSERT_TRUE(D.good()); + D.Put(IS_UNSYNCED, true); + D.Put(SPECIFICS, encrypted_bookmark); + D.Put(NON_UNIQUE_NAME, "not encrypted"); + } + SyncShareAsDelegate(); + { + // We remove any unready entries from the status controller's unsynced + // handles, so this should remain 0 even though the entries didn't commit. + ASSERT_EQ(0U, session_->status_controller()->unsynced_handles().size()); + // Nothing should have commited due to bookmarks being encrypted and + // the cryptographer having pending keys. A would have been resolved + // as a simple conflict, but still be unsynced until the next sync cycle. + ReadTransaction rtrans(FROM_HERE, dir); + Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); + ASSERT_TRUE(entryA.good()); + EXPECT_TRUE(entryA.Get(IS_UNSYNCED)); + Entry entryB(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(2)); + ASSERT_TRUE(entryB.good()); + EXPECT_TRUE(entryB.Get(IS_UNSYNCED)); + Entry entryC(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(3)); + ASSERT_TRUE(entryC.good()); + EXPECT_TRUE(entryC.Get(IS_UNSYNCED)); + Entry entryD(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(4)); + ASSERT_TRUE(entryD.good()); + EXPECT_TRUE(entryD.Get(IS_UNSYNCED)); + + // Resolve the pending keys. + syncdb_.manager()->GetCryptographer(&rtrans)->DecryptPendingKeys( + key_params); + } + SyncShareAsDelegate(); + { + ASSERT_EQ(0U, session_->status_controller()->unsynced_handles().size()); + // All properly encrypted and non-conflicting items should commit. "A" was + // conflicting, but last sync cycle resolved it as simple conflict, so on + // this sync cycle it committed succesfullly. + ReadTransaction rtrans(FROM_HERE, dir); + // Committed successfully. + Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); + ASSERT_TRUE(entryA.good()); + EXPECT_FALSE(entryA.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryA.Get(IS_UNAPPLIED_UPDATE)); + // Committed successfully. + Entry entryB(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(2)); + ASSERT_TRUE(entryB.good()); + EXPECT_FALSE(entryB.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryB.Get(IS_UNAPPLIED_UPDATE)); + // Was not properly encrypted. + Entry entryC(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(3)); + ASSERT_TRUE(entryC.good()); + EXPECT_TRUE(entryC.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryC.Get(IS_UNAPPLIED_UPDATE)); + // Was not properly encrypted. + Entry entryD(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(4)); + ASSERT_TRUE(entryD.good()); + EXPECT_TRUE(entryD.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryD.Get(IS_UNAPPLIED_UPDATE)); + } + { + // Fix the remaining items. WriteTransaction wtrans(FROM_HERE, UNITTEST, dir); - GetCommitIdsCommand::FilterEntriesNeedingEncryption( - encrypted_types, - &wtrans, - &unsynced_handles); - EXPECT_EQ(expected_handles, unsynced_handles); + MutableEntry C(&wtrans, GET_BY_ID, ids_.FromNumber(3)); + ASSERT_TRUE(C.good()); + C.Put(SPECIFICS, encrypted_bookmark); + C.Put(NON_UNIQUE_NAME, kEncryptedString); + MutableEntry D(&wtrans, GET_BY_ID, ids_.FromNumber(4)); + ASSERT_TRUE(D.good()); + D.Put(SPECIFICS, encrypted_bookmark); + D.Put(NON_UNIQUE_NAME, kEncryptedString); + } + SyncShareAsDelegate(); + { + ASSERT_EQ(0U, session_->status_controller()->unsynced_handles().size()); + // None should be unsynced anymore. + ReadTransaction rtrans(FROM_HERE, dir); + Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); + ASSERT_TRUE(entryA.good()); + EXPECT_FALSE(entryA.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryA.Get(IS_UNAPPLIED_UPDATE)); + Entry entryB(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(2)); + ASSERT_TRUE(entryB.good()); + EXPECT_FALSE(entryB.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryB.Get(IS_UNAPPLIED_UPDATE)); + Entry entryC(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(3)); + ASSERT_TRUE(entryC.good()); + EXPECT_FALSE(entryC.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryC.Get(IS_UNAPPLIED_UPDATE)); + Entry entryD(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(4)); + ASSERT_TRUE(entryD.good()); + EXPECT_FALSE(entryD.Get(IS_UNSYNCED)); + EXPECT_FALSE(entryD.Get(IS_UNAPPLIED_UPDATE)); } } @@ -2814,52 +2905,13 @@ TEST_F(SyncerTest, WeMovedSomethingIntoAFolderServerHasDeleted) { saw_syncer_event_ = false; } -class FolderMoveDeleteRenameTest : public SyncerTest { - public: - FolderMoveDeleteRenameTest() : done_(false) {} - - static const int64 bob_id_number = 1; - static const int64 fred_id_number = 2; - - void MoveBobIntoID2Runner() { - if (!done_) { - MoveBobIntoID2(); - done_ = true; - } - } - - protected: - void MoveBobIntoID2() { - ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); - CHECK(dir.good()); - - WriteTransaction trans(FROM_HERE, UNITTEST, dir); - Entry alice(&trans, GET_BY_ID, - TestIdFactory::FromNumber(fred_id_number)); - CHECK(alice.good()); - EXPECT_TRUE(!alice.Get(IS_DEL)); - EXPECT_TRUE(alice.Get(SYNCING)) << "Expected to be called mid-commit."; - MutableEntry bob(&trans, GET_BY_ID, - TestIdFactory::FromNumber(bob_id_number)); - CHECK(bob.good()); - bob.Put(IS_UNSYNCED, true); - - bob.Put(SYNCING, false); - bob.Put(PARENT_ID, alice.Get(ID)); - } - - bool done_; -}; - -TEST_F(FolderMoveDeleteRenameTest, +TEST_F(SyncerTest, WeMovedSomethingIntoAFolderServerHasDeletedAndWeRenamed) { ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); CHECK(dir.good()); - const syncable::Id bob_id = TestIdFactory::FromNumber( - FolderMoveDeleteRenameTest::bob_id_number); - const syncable::Id fred_id = TestIdFactory::FromNumber( - FolderMoveDeleteRenameTest::fred_id_number); + const syncable::Id bob_id = TestIdFactory::FromNumber(1); + const syncable::Id fred_id = TestIdFactory::FromNumber(2); mock_server_->AddUpdateDirectory(bob_id, TestIdFactory::root(), "bob", 1, 10); @@ -2873,18 +2925,18 @@ TEST_F(FolderMoveDeleteRenameTest, fred.Put(IS_UNSYNCED, true); fred.Put(SYNCING, false); fred.Put(NON_UNIQUE_NAME, "Alice"); + + // Move Bob within Fred (now Alice). + MutableEntry bob(&trans, GET_BY_ID, bob_id); + CHECK(bob.good()); + bob.Put(IS_UNSYNCED, true); + bob.Put(SYNCING, false); + bob.Put(PARENT_ID, fred_id); } mock_server_->AddUpdateDirectory(fred_id, TestIdFactory::root(), "fred", 2, 20); mock_server_->SetLastUpdateDeleted(); mock_server_->set_conflict_all_commits(true); - // This test is a little brittle. We want to move the item into the folder - // such that we think we're dealing with a simple conflict, but in reality - // it's actually a conflict set. - mock_server_->SetMidCommitCallback( - NewCallback<FolderMoveDeleteRenameTest>(this, - &FolderMoveDeleteRenameTest::MoveBobIntoID2Runner)); - SyncShareAsDelegate(); SyncShareAsDelegate(); SyncShareAsDelegate(); { |