diff options
Diffstat (limited to 'sync/engine/model_type_sync_worker_impl_unittest.cc')
-rw-r--r-- | sync/engine/model_type_sync_worker_impl_unittest.cc | 493 |
1 files changed, 485 insertions, 8 deletions
diff --git a/sync/engine/model_type_sync_worker_impl_unittest.cc b/sync/engine/model_type_sync_worker_impl_unittest.cc index 0c1fa13..df2ed66 100644 --- a/sync/engine/model_type_sync_worker_impl_unittest.cc +++ b/sync/engine/model_type_sync_worker_impl_unittest.cc @@ -4,6 +4,7 @@ #include "sync/engine/model_type_sync_worker_impl.h" +#include "base/strings/stringprintf.h" #include "sync/engine/commit_contribution.h" #include "sync/engine/model_type_sync_proxy.h" #include "sync/internal_api/public/base/model_type.h" @@ -13,13 +14,18 @@ #include "sync/syncable/syncable_util.h" #include "sync/test/engine/mock_model_type_sync_proxy.h" #include "sync/test/engine/mock_nudge_handler.h" +#include "sync/test/engine/simple_cryptographer_provider.h" #include "sync/test/engine/single_type_mock_server.h" +#include "sync/test/fake_encryptor.h" #include "testing/gtest/include/gtest/gtest.h" static const std::string kTypeParentId = "PrefsRootNodeID"; static const syncer::ModelType kModelType = syncer::PREFERENCES; +// Special constant value taken from cryptographer.cc. +const char kNigoriKeyName[] = "nigori-key"; + namespace syncer { // Tests the ModelTypeSyncWorkerImpl. @@ -65,8 +71,23 @@ class ModelTypeSyncWorkerImplTest : public ::testing::Test { // committing items right away. void NormalInitialize(); - // Initialize with a custom initial DataTypeState. - void InitializeWithState(const DataTypeState& state); + // Initialize with some saved pending updates from the model thread. + void InitializeWithPendingUpdates( + const UpdateResponseDataList& initial_pending_updates); + + // Initialize with a custom initial DataTypeState and pending updates. + void InitializeWithState(const DataTypeState& state, + const UpdateResponseDataList& pending_updates); + + // Introduce a new key that the local cryptographer can't decrypt. + void NewForeignEncryptionKey(); + + // Update the local cryptographer with all relevant keys. + void UpdateLocalCryptographer(); + + // Use the Nth nigori instance to encrypt incoming updates. + // The default value, zero, indicates no encryption. + void SetUpdateEncryptionFilter(int n); // Modifications on the model thread that get sent to the worker under test. void CommitRequest(const std::string& tag, const std::string& value); @@ -79,6 +100,13 @@ class ModelTypeSyncWorkerImplTest : public ::testing::Test { const std::string& value); void TriggerTombstoneFromServer(int64 version_offset, const std::string& tag); + // Delivers specified protos as updates. + // + // Does not update mock server state. Should be used as a last resort when + // writing test cases that require entities that don't fit the normal sync + // protocol. Try to use the other, higher level methods if possible. + void DeliverRawUpdates(const SyncEntityList& update_list); + // By default, this harness behaves as if all tasks posted to the model // thread are executed immediately. However, this is not necessarily true. // The model's TaskRunner has a queue, and the tasks we post to it could @@ -110,6 +138,7 @@ class ModelTypeSyncWorkerImplTest : public ::testing::Test { // be updated until the response is actually processed by the model thread. size_t GetNumModelThreadUpdateResponses() const; UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const; + UpdateResponseDataList GetNthModelThreadPendingUpdates(size_t n) const; DataTypeState GetNthModelThreadUpdateState(size_t n) const; // Reads the latest update response datas on the model thread. @@ -144,7 +173,39 @@ class ModelTypeSyncWorkerImplTest : public ::testing::Test { static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag, const std::string& value); + // Returns a set of KeyParams for the cryptographer. Each input 'n' value + // results in a different set of parameters. + static KeyParams GetNthKeyParams(int n); + + // Returns the name for the given Nigori. + // + // Uses some 'white-box' knowledge to mimic the names that a real sync client + // would generate. It's probably not necessary to do so, but it can't hurt. + static std::string GetNigoriName(const Nigori& nigori); + + // Modifies the input/output parameter |specifics| by encrypting it with + // a Nigori intialized with the specified KeyParams. + static void EncryptUpdate(const KeyParams& params, + sync_pb::EntitySpecifics* specifics); + private: + // An encryptor for our cryptographer. + FakeEncryptor fake_encryptor_; + + // The cryptographer itself. + Cryptographer cryptographer_; + + // A CryptographerProvider for the ModelTypeSyncWorkerImpl. + SimpleCryptographerProvider cryptographer_provider_; + + // The number of the most recent foreign encryption key known to our + // cryptographer. Note that not all of these will be decryptable. + int foreign_encryption_key_index_; + + // The number of the encryption key used to encrypt incoming updates. A zero + // value implies no encryption. + int update_encryption_filter_index_; + // The ModelTypeSyncWorkerImpl being tested. scoped_ptr<ModelTypeSyncWorkerImpl> worker_; @@ -163,7 +224,12 @@ class ModelTypeSyncWorkerImplTest : public ::testing::Test { }; ModelTypeSyncWorkerImplTest::ModelTypeSyncWorkerImplTest() - : mock_type_sync_proxy_(NULL), mock_server_(kModelType) { + : cryptographer_(&fake_encryptor_), + cryptographer_provider_(&cryptographer_), + foreign_encryption_key_index_(0), + update_encryption_filter_index_(0), + mock_type_sync_proxy_(NULL), + mock_server_(kModelType) { } ModelTypeSyncWorkerImplTest::~ModelTypeSyncWorkerImplTest() { @@ -175,10 +241,15 @@ void ModelTypeSyncWorkerImplTest::FirstInitialize() { GetSpecificsFieldNumberFromModelType(kModelType)); initial_state.next_client_id = 0; - InitializeWithState(initial_state); + InitializeWithState(initial_state, UpdateResponseDataList()); } void ModelTypeSyncWorkerImplTest::NormalInitialize() { + InitializeWithPendingUpdates(UpdateResponseDataList()); +} + +void ModelTypeSyncWorkerImplTest::InitializeWithPendingUpdates( + const UpdateResponseDataList& initial_pending_updates) { DataTypeState initial_state; initial_state.progress_marker.set_data_type_id( GetSpecificsFieldNumberFromModelType(kModelType)); @@ -188,21 +259,79 @@ void ModelTypeSyncWorkerImplTest::NormalInitialize() { initial_state.type_root_id = kTypeParentId; initial_state.initial_sync_done = true; - InitializeWithState(initial_state); + InitializeWithState(initial_state, initial_pending_updates); mock_nudge_handler_.ClearCounters(); } void ModelTypeSyncWorkerImplTest::InitializeWithState( - const DataTypeState& state) { + const DataTypeState& state, + const UpdateResponseDataList& initial_pending_updates) { DCHECK(!worker_); // We don't get to own this object. The |worker_| keeps a scoped_ptr to it. mock_type_sync_proxy_ = new MockModelTypeSyncProxy(); scoped_ptr<ModelTypeSyncProxy> proxy(mock_type_sync_proxy_); - worker_.reset(new ModelTypeSyncWorkerImpl( - kModelType, state, &mock_nudge_handler_, proxy.Pass())); + worker_.reset(new ModelTypeSyncWorkerImpl(kModelType, + state, + initial_pending_updates, + &cryptographer_provider_, + &mock_nudge_handler_, + proxy.Pass())); +} + +void ModelTypeSyncWorkerImplTest::NewForeignEncryptionKey() { + foreign_encryption_key_index_++; + + sync_pb::NigoriKeyBag bag; + + for (int i = 0; i <= foreign_encryption_key_index_; ++i) { + Nigori nigori; + KeyParams params = GetNthKeyParams(i); + nigori.InitByDerivation(params.hostname, params.username, params.password); + + sync_pb::NigoriKey* key = bag.add_key(); + + key->set_name(GetNigoriName(nigori)); + nigori.ExportKeys(key->mutable_user_key(), + key->mutable_encryption_key(), + key->mutable_mac_key()); + } + + // Re-create the last nigori from that loop. + Nigori last_nigori; + KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); + last_nigori.InitByDerivation( + params.hostname, params.username, params.password); + + // Serialize and encrypt the bag with the last nigori. + std::string serialized_bag; + bag.SerializeToString(&serialized_bag); + + sync_pb::EncryptedData encrypted; + encrypted.set_key_name(GetNigoriName(last_nigori)); + last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob()); + + // Update the cryptographer with new pending keys. + cryptographer_.SetPendingKeys(encrypted); + + // Update the worker with the latest encryption key name. + if (worker_) + worker_->SetEncryptionKeyName(encrypted.key_name()); +} + +void ModelTypeSyncWorkerImplTest::UpdateLocalCryptographer() { + KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); + bool success = cryptographer_.DecryptPendingKeys(params); + DCHECK(success); + + if (worker_) + worker_->OnCryptographerStateChanged(); +} + +void ModelTypeSyncWorkerImplTest::SetUpdateEncryptionFilter(int n) { + update_encryption_filter_index_ = n; } void ModelTypeSyncWorkerImplTest::CommitRequest(const std::string& name, @@ -243,6 +372,12 @@ void ModelTypeSyncWorkerImplTest::TriggerUpdateFromServer( const std::string& value) { sync_pb::SyncEntity entity = mock_server_.UpdateFromServer( version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value)); + + if (update_encryption_filter_index_ != 0) { + EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), + entity.mutable_specifics()); + } + SyncEntityList entity_list; entity_list.push_back(&entity); @@ -255,11 +390,27 @@ void ModelTypeSyncWorkerImplTest::TriggerUpdateFromServer( worker_->ApplyUpdates(&dummy_status); } +void ModelTypeSyncWorkerImplTest::DeliverRawUpdates( + const SyncEntityList& list) { + sessions::StatusController dummy_status; + worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), + mock_server_.GetContext(), + list, + &dummy_status); + worker_->ApplyUpdates(&dummy_status); +} + void ModelTypeSyncWorkerImplTest::TriggerTombstoneFromServer( int64 version_offset, const std::string& tag) { sync_pb::SyncEntity entity = mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag)); + + if (update_encryption_filter_index_ != 0) { + EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), + entity.mutable_specifics()); + } + SyncEntityList entity_list; entity_list.push_back(&entity); @@ -346,6 +497,12 @@ ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateResponse(size_t n) const { return mock_type_sync_proxy_->GetNthUpdateResponse(n); } +UpdateResponseDataList +ModelTypeSyncWorkerImplTest::GetNthModelThreadPendingUpdates(size_t n) const { + DCHECK_LT(n, GetNumModelThreadUpdateResponses()); + return mock_type_sync_proxy_->GetNthPendingUpdates(n); +} + DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateState( size_t n) const { DCHECK_LT(n, GetNumModelThreadUpdateResponses()); @@ -401,6 +558,7 @@ int ModelTypeSyncWorkerImplTest::GetNumInitialDownloadNudges() const { return mock_nudge_handler_.GetNumInitialDownloadNudges(); } +// static. std::string ModelTypeSyncWorkerImplTest::GenerateTagHash( const std::string& tag) { const std::string& client_tag_hash = @@ -408,6 +566,7 @@ std::string ModelTypeSyncWorkerImplTest::GenerateTagHash( return client_tag_hash; } +// static. sync_pb::EntitySpecifics ModelTypeSyncWorkerImplTest::GenerateSpecifics( const std::string& tag, const std::string& value) { @@ -417,6 +576,46 @@ sync_pb::EntitySpecifics ModelTypeSyncWorkerImplTest::GenerateSpecifics( return specifics; } +// static. +std::string ModelTypeSyncWorkerImplTest::GetNigoriName(const Nigori& nigori) { + std::string name; + if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) { + NOTREACHED(); + return std::string(); + } + + return name; +} + +// static. +KeyParams ModelTypeSyncWorkerImplTest::GetNthKeyParams(int n) { + KeyParams params; + params.hostname = std::string("localhost"); + params.username = std::string("userX"); + params.password = base::StringPrintf("pw%02d", n); + return params; +} + +// static. +void ModelTypeSyncWorkerImplTest::EncryptUpdate( + const KeyParams& params, + sync_pb::EntitySpecifics* specifics) { + Nigori nigori; + nigori.InitByDerivation(params.hostname, params.username, params.password); + + sync_pb::EntitySpecifics original_specifics = *specifics; + std::string plaintext; + original_specifics.SerializeToString(&plaintext); + + std::string encrypted; + nigori.Encrypt(plaintext, &encrypted); + + specifics->Clear(); + AddDefaultFieldValue(kModelType, specifics); + specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori)); + specifics->mutable_encrypted()->set_blob(encrypted); +} + // Requests a commit and verifies the messages sent to the client and server as // a result. // @@ -600,6 +799,7 @@ TEST_F(ModelTypeSyncWorkerImplTest, TwoNewItemsCommittedSeparately) { EXPECT_EQ(2U, GetNumModelThreadCommitResponses()); } +// Test normal update receipt code path. TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) { NormalInitialize(); @@ -625,4 +825,281 @@ TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) { EXPECT_EQ("value1", update.specifics.preference().value()); } +// Test commit of encrypted updates. +TEST_F(ModelTypeSyncWorkerImplTest, EncryptedCommit) { + NormalInitialize(); + + NewForeignEncryptionKey(); + UpdateLocalCryptographer(); + + // Normal commit request stuff. + CommitRequest("tag1", "value1"); + DoSuccessfulCommit(); + ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); + EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); + ASSERT_TRUE(HasCommitEntityOnServer("tag1")); + const sync_pb::SyncEntity& tag1_entity = + GetLatestCommitEntityOnServer("tag1"); + + EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); + + // The title should be overwritten. + EXPECT_EQ(tag1_entity.name(), "encrypted"); + + // The type should be set, but there should be no non-encrypted contents. + EXPECT_TRUE(tag1_entity.specifics().has_preference()); + EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); + EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); +} + +// Test items are not committed when encryption is required but unavailable. +TEST_F(ModelTypeSyncWorkerImplTest, EncryptionBlocksCommits) { + NormalInitialize(); + + CommitRequest("tag1", "value1"); + EXPECT_TRUE(WillCommit()); + + // We know encryption is in use on this account, but don't have the necessary + // encryption keys. The worker should refuse to commit. + NewForeignEncryptionKey(); + EXPECT_FALSE(WillCommit()); + + // Once the cryptographer is returned to a normal state, we should be able to + // commit again. + EXPECT_EQ(1, GetNumCommitNudges()); + UpdateLocalCryptographer(); + EXPECT_EQ(2, GetNumCommitNudges()); + EXPECT_TRUE(WillCommit()); + + // Verify the committed entity was properly encrypted. + DoSuccessfulCommit(); + ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); + EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); + ASSERT_TRUE(HasCommitEntityOnServer("tag1")); + const sync_pb::SyncEntity& tag1_entity = + GetLatestCommitEntityOnServer("tag1"); + EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); + EXPECT_EQ(tag1_entity.name(), "encrypted"); + EXPECT_TRUE(tag1_entity.specifics().has_preference()); + EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); + EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); +} + +// Test the receipt of decryptable entities. +TEST_F(ModelTypeSyncWorkerImplTest, ReceiveDecryptableEntities) { + NormalInitialize(); + + // Create a new Nigori and allow the cryptographer to decrypt it. + NewForeignEncryptionKey(); + UpdateLocalCryptographer(); + + // First, receive an unencrypted entry. + TriggerUpdateFromServer(10, "tag1", "value1"); + + // Test some basic properties regarding the update. + ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); + UpdateResponseData update1 = GetUpdateResponseOnModelThread("tag1"); + EXPECT_EQ("tag1", update1.specifics.preference().name()); + EXPECT_EQ("value1", update1.specifics.preference().value()); + EXPECT_TRUE(update1.encryption_key_name.empty()); + + // Set received updates to be encrypted using the new nigori. + SetUpdateEncryptionFilter(1); + + // This next update will be encrypted. + TriggerUpdateFromServer(10, "tag2", "value2"); + + // Test its basic features and the value of encryption_key_name. + ASSERT_TRUE(HasUpdateResponseOnModelThread("tag2")); + UpdateResponseData update2 = GetUpdateResponseOnModelThread("tag2"); + EXPECT_EQ("tag2", update2.specifics.preference().name()); + EXPECT_EQ("value2", update2.specifics.preference().value()); + EXPECT_FALSE(update2.encryption_key_name.empty()); +} + +// Receive updates that are initially undecryptable, then ensure they get +// delivered to the model thread when decryption becomes possible. +TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUndecryptableEntries) { + NormalInitialize(); + + // Set a new encryption key. The model thread will be notified of the new + // encryption key through a faked update response. + NewForeignEncryptionKey(); + EXPECT_EQ(1U, GetNumModelThreadUpdateResponses()); + + // Send an update using that new key. + SetUpdateEncryptionFilter(1); + TriggerUpdateFromServer(10, "tag1", "value1"); + + // At this point, the cryptographer does not have access to the key, so the + // updates will be undecryptable. They'll be transfered to the model thread + // for safe-keeping as pending updates. + ASSERT_EQ(2U, GetNumModelThreadUpdateResponses()); + UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(1); + EXPECT_EQ(0U, updates_list.size()); + UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(1); + EXPECT_EQ(1U, pending_updates.size()); + + // The update will be delivered as soon as decryption becomes possible. + UpdateLocalCryptographer(); + ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); + UpdateResponseData update = GetUpdateResponseOnModelThread("tag1"); + EXPECT_EQ("tag1", update.specifics.preference().name()); + EXPECT_EQ("value1", update.specifics.preference().value()); + EXPECT_FALSE(update.encryption_key_name.empty()); +} + +// Ensure that even encrypted updates can cause conflicts. +TEST_F(ModelTypeSyncWorkerImplTest, EncryptedUpdateOverridesPendingCommit) { + NormalInitialize(); + + // Prepeare to commit an item. + CommitRequest("tag1", "value1"); + EXPECT_TRUE(WillCommit()); + + // Receive an encrypted update for that item. + SetUpdateEncryptionFilter(1); + TriggerUpdateFromServer(10, "tag1", "value1"); + + // The pending commit state should be cleared. + EXPECT_FALSE(WillCommit()); + + // The encrypted update will be delivered to the model thread. + ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); + UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); + EXPECT_EQ(0U, updates_list.size()); + UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0); + EXPECT_EQ(1U, pending_updates.size()); +} + +// Test decryption of pending updates saved across a restart. +TEST_F(ModelTypeSyncWorkerImplTest, RestorePendingEntries) { + // Create a fake pending update. + UpdateResponseData update; + + update.client_tag_hash = GenerateTagHash("tag1"); + update.id = "SomeID"; + update.response_version = 100; + update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); + update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); + update.non_unique_name = "encrypted"; + update.deleted = false; + + update.specifics = GenerateSpecifics("tag1", "value1"); + EncryptUpdate(GetNthKeyParams(1), &(update.specifics)); + + // Inject the update during ModelTypeSyncWorker initialization. + UpdateResponseDataList saved_pending_updates; + saved_pending_updates.push_back(update); + InitializeWithPendingUpdates(saved_pending_updates); + + // Update will be undecryptable at first. + EXPECT_EQ(0U, GetNumModelThreadUpdateResponses()); + ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1")); + + // Update the cryptographer so it can decrypt that update. + NewForeignEncryptionKey(); + UpdateLocalCryptographer(); + + // Verify the item gets decrypted and sent back to the model thread. + ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); +} + +// Test decryption of pending updates saved across a restart. This test +// differs from the previous one in that the restored updates can be decrypted +// immediately after the ModelTypeSyncWorker is constructed. +TEST_F(ModelTypeSyncWorkerImplTest, RestoreApplicableEntries) { + // Update the cryptographer so it can decrypt that update. + NewForeignEncryptionKey(); + UpdateLocalCryptographer(); + + // Create a fake pending update. + UpdateResponseData update; + update.client_tag_hash = GenerateTagHash("tag1"); + update.id = "SomeID"; + update.response_version = 100; + update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); + update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); + update.non_unique_name = "encrypted"; + update.deleted = false; + + update.specifics = GenerateSpecifics("tag1", "value1"); + EncryptUpdate(GetNthKeyParams(1), &(update.specifics)); + + // Inject the update during ModelTypeSyncWorker initialization. + UpdateResponseDataList saved_pending_updates; + saved_pending_updates.push_back(update); + InitializeWithPendingUpdates(saved_pending_updates); + + // Verify the item gets decrypted and sent back to the model thread. + ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); +} + +// Test that undecryptable updates provide sufficient reason to not commit. +// +// This should be rare in practice. Usually the cryptographer will be in an +// unusable state when we receive undecryptable updates, and that alone will be +// enough to prevent all commits. +TEST_F(ModelTypeSyncWorkerImplTest, CommitBlockedByPending) { + NormalInitialize(); + + // Prepeare to commit an item. + CommitRequest("tag1", "value1"); + EXPECT_TRUE(WillCommit()); + + // Receive an encrypted update for that item. + SetUpdateEncryptionFilter(1); + TriggerUpdateFromServer(10, "tag1", "value1"); + + // The pending commit state should be cleared. + EXPECT_FALSE(WillCommit()); + + // The pending update will be delivered to the model thread. + HasUpdateResponseOnModelThread("tag1"); + + // Pretend the update arrived too late to prevent another commit request. + CommitRequest("tag1", "value2"); + + EXPECT_FALSE(WillCommit()); +} + +// Verify that corrupted encrypted updates don't cause crashes. +TEST_F(ModelTypeSyncWorkerImplTest, ReceiveCorruptEncryption) { + // Initialize the worker with basic encryption state. + NormalInitialize(); + NewForeignEncryptionKey(); + UpdateLocalCryptographer(); + + // Manually create an update. + sync_pb::SyncEntity entity; + entity.set_client_defined_unique_tag(GenerateTagHash("tag1")); + entity.set_id_string("SomeID"); + entity.set_version(1); + entity.set_ctime(1000); + entity.set_mtime(1001); + entity.set_name("encrypted"); + entity.set_deleted(false); + + // Encrypt it. + entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1")); + EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics()); + + // Replace a few bytes to corrupt it. + entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace( + 0, 4, "xyz!"); + + SyncEntityList entity_list; + entity_list.push_back(&entity); + + // If a corrupt update could trigger a crash, this is where it would happen. + DeliverRawUpdates(entity_list); + + EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1")); + + // Deliver a non-corrupt update to see if the everything still works. + SetUpdateEncryptionFilter(1); + TriggerUpdateFromServer(10, "tag1", "value1"); + EXPECT_TRUE(HasUpdateResponseOnModelThread("tag1")); +} + } // namespace syncer |