text: // Copyright 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 "base/format_macros.h"
  #include "base/location.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/strings/stringprintf.h"
  #include "sync/engine/apply_control_data_updates.h"
  #include "sync/engine/syncer.h"
  #include "sync/engine/syncer_util.h"
  #include "sync/internal_api/public/test/test_entry_factory.h"
  #include "sync/protocol/nigori_specifics.pb.h"
  #include "sync/syncable/mutable_entry.h"
  #include "sync/syncable/nigori_util.h"
  #include "sync/syncable/syncable_read_transaction.h"
  #include "sync/syncable/syncable_util.h"
  #include "sync/syncable/syncable_write_transaction.h"
  #include "sync/test/engine/fake_model_worker.h"
  #include "sync/test/engine/syncer_command_test.h"
  #include "sync/test/engine/test_id_factory.h"
  #include "sync/test/fake_sync_encryption_handler.h"
  #include "sync/util/cryptographer.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace syncer {
  
  using syncable::MutableEntry;
  using syncable::UNITTEST;
  using syncable::Id;
  
  class ApplyControlDataUpdatesTest : public SyncerCommandTest {
   public:
   protected:
    ApplyControlDataUpdatesTest() {}
    virtual ~ApplyControlDataUpdatesTest() {}
  
    virtual void SetUp() {
      workers()->clear();
      mutable_routing_info()->clear();
      workers()->push_back(make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
      workers()->push_back(
          make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD)));
      (*mutable_routing_info())[NIGORI] = GROUP_PASSIVE;
      (*mutable_routing_info())[EXPERIMENTS] = GROUP_PASSIVE;
      SyncerCommandTest::SetUp();
      entry_factory_.reset(new TestEntryFactory(directory()));
  
      session()->mutable_status_controller()->set_updates_request_types(
          ControlTypes());
  
      syncable::ReadTransaction trans(FROM_HERE, directory());
    }
  
    TestIdFactory id_factory_;
    scoped_ptr<TestEntryFactory> entry_factory_;
   private:
    DISALLOW_COPY_AND_ASSIGN(ApplyControlDataUpdatesTest);
  };
  
  // Verify that applying a nigori node sets initial sync ended properly,
  // updates the set of encrypted types, and updates the cryptographer.
  TEST_F(ApplyControlDataUpdatesTest, NigoriUpdate) {
    // Storing the cryptographer separately is bad, but for this test we
    // know it's safe.
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types;
    encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
  
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(encrypted_types));
    }
  
    // Nigori node updates should update the Cryptographer.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    KeyParams params = {"localhost", "dummy", "foobar"};
    other_cryptographer.AddKey(params);
  
    sync_pb::EntitySpecifics specifics;
    sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
    other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
    nigori->set_encrypt_everything(true);
    entry_factory_->CreateUnappliedNewItem(
        ModelTypeToRootTag(NIGORI), specifics, true);
    EXPECT_FALSE(cryptographer->has_pending_keys());
  
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->has_pending_keys());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // Create some local unsynced and unencrypted data. Apply a nigori update that
  // turns on encryption for the unsynced data. Ensure we properly encrypt the
  // data as part of the nigori update. Apply another nigori update with no
  // changes. Ensure we ignore already-encrypted unsynced data and that nothing
  // breaks.
  TEST_F(ApplyControlDataUpdatesTest, EncryptUnsyncedChanges) {
    // Storing the cryptographer separately is bad, but for this test we
    // know it's safe.
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types;
    encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(encrypted_types));
  
      // With default encrypted_types, this should be true.
      EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_TRUE(handles.empty());
    }
  
    // Create unsynced bookmarks without encryption.
    // First item is a folder
    Id folder_id = id_factory_.NewLocalId();
    entry_factory_->CreateUnsyncedItem(folder_id, id_factory_.root(), "folder",
                                       true, BOOKMARKS, NULL);
    // Next five items are children of the folder
    size_t i;
    size_t batch_s = 5;
    for (i = 0; i < batch_s; ++i) {
      entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
                                         base::StringPrintf("Item %" PRIuS "", i),
                                         false, BOOKMARKS, NULL);
    }
    // Next five items are children of the root.
    for (; i < 2*batch_s; ++i) {
      entry_factory_->CreateUnsyncedItem(
          id_factory_.NewLocalId(), id_factory_.root(),
          base::StringPrintf("Item %" PRIuS "", i), false,
          BOOKMARKS, NULL);
    }
  
    KeyParams params = {"localhost", "dummy", "foobar"};
    cryptographer->AddKey(params);
    sync_pb::EntitySpecifics specifics;
    sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
    cryptographer->GetKeys(nigori->mutable_encryption_keybag());
    nigori->set_encrypt_everything(true);
    encrypted_types.Put(BOOKMARKS);
    entry_factory_->CreateUnappliedNewItem(
        ModelTypeToRootTag(NIGORI), specifics, true);
    EXPECT_FALSE(cryptographer->has_pending_keys());
    EXPECT_TRUE(cryptographer->is_ready());
  
    {
      // Ensure we have unsynced nodes that aren't properly encrypted.
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_EQ(2*batch_s+1, handles.size());
    }
  
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(cryptographer->has_pending_keys());
    EXPECT_TRUE(cryptographer->is_ready());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
  
      // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes
      // should be encrypted now.
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
      EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_EQ(2*batch_s+1, handles.size());
    }
  
    // Simulate another nigori update that doesn't change anything.
    {
      syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
      MutableEntry entry(&trans, syncable::GET_BY_SERVER_TAG,
                         ModelTypeToRootTag(NIGORI));
      ASSERT_TRUE(entry.good());
      entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision());
      entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
    }
  
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(cryptographer->has_pending_keys());
    EXPECT_TRUE(cryptographer->is_ready());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
  
      // All our changes should still be encrypted.
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
      EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_EQ(2*batch_s+1, handles.size());
    }
  }
  
  // Create some local unsynced and unencrypted changes. Receive a new nigori
  // node enabling their encryption but also introducing pending keys. Ensure
  // we apply the update properly without encrypting the unsynced changes or
  // breaking.
  TEST_F(ApplyControlDataUpdatesTest, CannotEncryptUnsyncedChanges) {
    // Storing the cryptographer separately is bad, but for this test we
    // know it's safe.
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types;
    encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(encrypted_types));
  
      // With default encrypted_types, this should be true.
      EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_TRUE(handles.empty());
    }
  
    // Create unsynced bookmarks without encryption.
    // First item is a folder
    Id folder_id = id_factory_.NewLocalId();
    entry_factory_->CreateUnsyncedItem(
        folder_id, id_factory_.root(), "folder", true,
        BOOKMARKS, NULL);
    // Next five items are children of the folder
    size_t i;
    size_t batch_s = 5;
    for (i = 0; i < batch_s; ++i) {
      entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
                                         base::StringPrintf("Item %" PRIuS "", i),
                                         false, BOOKMARKS, NULL);
    }
    // Next five items are children of the root.
    for (; i < 2*batch_s; ++i) {
      entry_factory_->CreateUnsyncedItem(
          id_factory_.NewLocalId(), id_factory_.root(),
          base::StringPrintf("Item %" PRIuS "", i), false,
          BOOKMARKS, NULL);
    }
  
    // We encrypt with new keys, triggering the local cryptographer to be unready
    // and unable to decrypt data (once updated).
    Cryptographer other_cryptographer(cryptographer->encryptor());
    KeyParams params = {"localhost", "dummy", "foobar"};
    other_cryptographer.AddKey(params);
    sync_pb::EntitySpecifics specifics;
    sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
    other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
    nigori->set_encrypt_everything(true);
    encrypted_types.Put(BOOKMARKS);
    entry_factory_->CreateUnappliedNewItem(
        ModelTypeToRootTag(NIGORI), specifics, true);
    EXPECT_FALSE(cryptographer->has_pending_keys());
  
    {
      // Ensure we have unsynced nodes that aren't properly encrypted.
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_EQ(2*batch_s+1, handles.size());
    }
  
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->has_pending_keys());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
  
      // Since we have pending keys, we would have failed to encrypt, but the
      // cryptographer should be updated.
      EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
      EXPECT_FALSE(cryptographer->is_ready());
      EXPECT_TRUE(cryptographer->has_pending_keys());
  
      Syncer::UnsyncedMetaHandles handles;
      GetUnsyncedEntries(&trans, &handles);
      EXPECT_EQ(2*batch_s+1, handles.size());
    }
  }
  
  // Verify we handle a nigori node conflict by merging encryption keys and
  // types, but preserve the custom passphrase state of the server.
  // Initial sync ended should be set.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictPendingKeysServerEncryptEverythingCustom) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams other_params = {"localhost", "dummy", "foobar"};
    KeyParams local_params = {"localhost", "dummy", "local"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up a temporary cryptographer to generate new keys with.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(other_params);
  
    // Create server specifics with pending keys, new encrypted types,
    // and a custom passphrase (unmigrated).
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(true);
    server_nigori->set_keybag_is_frozen(true);
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Initialize the local cryptographer with the local keys.
    cryptographer->AddKey(local_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with the local encryption keys and default encrypted
    // types.
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(false);
    local_nigori->set_keybag_is_frozen(true);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_FALSE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->is_initialized());
    EXPECT_TRUE(cryptographer->has_pending_keys());
    EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // Verify we handle a nigori node conflict by merging encryption keys and
  // types, but preserve the custom passphrase state of the server.
  // Initial sync ended should be set.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictPendingKeysLocalEncryptEverythingCustom) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams other_params = {"localhost", "dummy", "foobar"};
    KeyParams local_params = {"localhost", "dummy", "local"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up a temporary cryptographer to generate new keys with.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(other_params);
  
    // Create server specifics with pending keys, new encrypted types,
    // and a custom passphrase (unmigrated).
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(false);
    server_nigori->set_keybag_is_frozen(false);
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Initialize the local cryptographer with the local keys.
    cryptographer->AddKey(local_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with the local encryption keys and default encrypted
    // types.
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(true);
    local_nigori->set_keybag_is_frozen(true);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_FALSE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->is_initialized());
    EXPECT_TRUE(cryptographer->has_pending_keys());
    EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_FALSE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // If the conflicting nigori has a subset of the local keys, the conflict
  // resolution should preserve the full local keys. Initial sync ended should be
  // set.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictOldKeys) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams old_params = {"localhost", "dummy", "old"};
    KeyParams new_params = {"localhost", "dummy", "new"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up the cryptographer with old keys
    cryptographer->AddKey(old_params);
  
    // Create server specifics with old keys and new encrypted types.
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    cryptographer->GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(true);
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Add the new keys to the cryptogrpaher
    cryptographer->AddKey(new_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with the superset of keys.
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(false);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_TRUE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_FALSE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // If both nigoris are migrated, but we also set a custom passphrase locally,
  // the local nigori should be preserved.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictBothMigratedLocalCustom) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams old_params = {"localhost", "dummy", "old"};
    KeyParams new_params = {"localhost", "dummy", "new"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up the cryptographer with new keys
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(old_params);
  
    // Create server specifics with a migrated keystore passphrase type.
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(false);
    server_nigori->set_keybag_is_frozen(true);
    server_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
    server_nigori->mutable_keystore_decryptor_token();
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Add the new keys to the cryptographer.
    cryptographer->AddKey(old_params);
    cryptographer->AddKey(new_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with a migrated custom passphrase type
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(true);
    local_nigori->set_keybag_is_frozen(true);
    local_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_TRUE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
              entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                  nigori().passphrase_type());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // If both nigoris are migrated, but a custom passphrase with a new key was
  // set remotely, the remote nigori should be preserved.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictBothMigratedServerCustom) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams old_params = {"localhost", "dummy", "old"};
    KeyParams new_params = {"localhost", "dummy", "new"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up the cryptographer with both new keys and old keys.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(old_params);
    other_cryptographer.AddKey(new_params);
  
    // Create server specifics with a migrated custom passphrase type.
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(true);
    server_nigori->set_keybag_is_frozen(true);
    server_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Add the old keys to the cryptographer.
    cryptographer->AddKey(old_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with a migrated keystore passphrase type
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(false);
    local_nigori->set_keybag_is_frozen(true);
    local_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
    server_nigori->mutable_keystore_decryptor_token();
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_TRUE(cryptographer->is_initialized());
    EXPECT_TRUE(cryptographer->has_pending_keys());
    EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
              entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                  nigori().passphrase_type());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // If the local nigori is migrated but the server is not, preserve the local
  // nigori.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictLocalMigrated) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams old_params = {"localhost", "dummy", "old"};
    KeyParams new_params = {"localhost", "dummy", "new"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up the cryptographer with both new keys and old keys.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(old_params);
  
    // Create server specifics with an unmigrated implicit passphrase type.
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(true);
    server_nigori->set_keybag_is_frozen(false);
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Add the old keys to the cryptographer.
    cryptographer->AddKey(old_params);
    cryptographer->AddKey(new_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with a migrated custom passphrase type
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(true);
    local_nigori->set_keybag_is_frozen(true);
    local_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_TRUE(cryptographer->is_ready());
    EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().encrypt_everything());
    EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
              entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                  nigori().passphrase_type());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
          .Equals(ModelTypeSet::All()));
    }
  }
  
  // If the server nigori is migrated but the local is not, preserve the server
  // nigori.
  TEST_F(ApplyControlDataUpdatesTest,
         NigoriConflictServerMigrated) {
    Cryptographer* cryptographer;
    ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
    KeyParams old_params = {"localhost", "dummy", "old"};
    KeyParams new_params = {"localhost", "dummy", "new"};
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      EXPECT_TRUE(encrypted_types.Equals(
              directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
    }
  
    // Set up the cryptographer with both new keys and old keys.
    Cryptographer other_cryptographer(cryptographer->encryptor());
    other_cryptographer.AddKey(old_params);
  
    // Create server specifics with an migrated keystore passphrase type.
    sync_pb::EntitySpecifics server_specifics;
    sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
    other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
    server_nigori->set_encrypt_everything(false);
    server_nigori->set_keybag_is_frozen(true);
    server_nigori->set_passphrase_type(
        sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
    server_nigori->mutable_keystore_decryptor_token();
    int64 nigori_handle =
        entry_factory_->CreateUnappliedNewItem(kNigoriTag,
                                               server_specifics,
                                               true);
  
    // Add the old keys to the cryptographer.
    cryptographer->AddKey(old_params);
    cryptographer->AddKey(new_params);
    EXPECT_TRUE(cryptographer->is_ready());
  
    // Set up a local nigori with a migrated custom passphrase type
    sync_pb::EntitySpecifics local_specifics;
    sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
    cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
    local_nigori->set_encrypt_everything(false);
    local_nigori->set_keybag_is_frozen(false);
    ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
            nigori_handle, local_specifics));
    // Apply the update locally so that UpdateFromEncryptedTypes knows what state
    // to use.
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
      cryptographer = directory()->GetCryptographer(&trans);
      directory()->GetNigoriHandler()->ApplyNigoriUpdate(
          *local_nigori,
          &trans);
    }
  
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
    ApplyControlDataUpdates(session());
    EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
  
    EXPECT_TRUE(cryptographer->is_ready());
    // Note: we didn't overwrite the encryption keybag with the local keys. The
    // sync encryption handler will do that when it detects that the new
    // keybag is out of date (and update the keystore bootstrap if necessary).
    EXPECT_FALSE(cryptographer->CanDecryptUsingDefaultKey(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(cryptographer->CanDecrypt(
            entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                nigori().encryption_keybag()));
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().keybag_is_frozen());
    EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
        nigori().has_keystore_decryptor_token());
    EXPECT_EQ(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE,
              entry_factory_->GetLocalSpecificsForItem(nigori_handle).
                  nigori().passphrase_type());
    {
      syncable::ReadTransaction trans(FROM_HERE, directory());
    }
  }
  
  // Check that we can apply a simple control datatype node successfully.
  TEST_F(ApplyControlDataUpdatesTest, ControlApply) {
    std::string experiment_id = "experiment";
    sync_pb::EntitySpecifics specifics;
    specifics.mutable_experiments()->mutable_keystore_encryption()->
        set_enabled(true);
    int64 experiment_handle = entry_factory_->CreateUnappliedNewItem(
        experiment_id, specifics, false);
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(experiment_handle));
    EXPECT_TRUE(
        entry_factory_->GetLocalSpecificsForItem(experiment_handle).
            experiments().keystore_encryption().enabled());
  }
  
  // Verify that we apply top level folders before their children.
  TEST_F(ApplyControlDataUpdatesTest, ControlApplyParentBeforeChild) {
    std::string parent_id = "parent";
    std::string experiment_id = "experiment";
    sync_pb::EntitySpecifics specifics;
    specifics.mutable_experiments()->mutable_keystore_encryption()->
        set_enabled(true);
    int64 experiment_handle = entry_factory_->CreateUnappliedNewItemWithParent(
        experiment_id, specifics, parent_id);
    int64 parent_handle = entry_factory_->CreateUnappliedNewItem(
        parent_id, specifics, true);
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(parent_handle));
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(experiment_handle));
    EXPECT_TRUE(
        entry_factory_->GetLocalSpecificsForItem(experiment_handle).
            experiments().keystore_encryption().enabled());
  }
  
  // Verify that we handle control datatype conflicts by preserving the server
  // data.
  TEST_F(ApplyControlDataUpdatesTest, ControlConflict) {
    std::string experiment_id = "experiment";
    sync_pb::EntitySpecifics local_specifics, server_specifics;
    server_specifics.mutable_experiments()->mutable_keystore_encryption()->
        set_enabled(true);
    local_specifics.mutable_experiments()->mutable_keystore_encryption()->
        set_enabled(false);
    int64 experiment_handle = entry_factory_->CreateSyncedItem(
        experiment_id, EXPERIMENTS, false);
    entry_factory_->SetServerSpecificsForItem(experiment_handle,
                                              server_specifics);
    entry_factory_->SetLocalSpecificsForItem(experiment_handle,
                                             local_specifics);
    ApplyControlDataUpdates(session());
  
    EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(experiment_handle));
    EXPECT_TRUE(
        entry_factory_->GetLocalSpecificsForItem(experiment_handle).
            experiments().keystore_encryption().enabled());
  }
  
  }  // namespace syncer
  