1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sync/engine/model_type_sync_worker_impl.h" 6 7 #include "base/strings/stringprintf.h" 8 #include "sync/engine/commit_contribution.h" 9 #include "sync/engine/model_type_sync_proxy.h" 10 #include "sync/internal_api/public/base/model_type.h" 11 #include "sync/internal_api/public/non_blocking_sync_common.h" 12 #include "sync/protocol/sync.pb.h" 13 #include "sync/sessions/status_controller.h" 14 #include "sync/syncable/syncable_util.h" 15 #include "sync/test/engine/mock_model_type_sync_proxy.h" 16 #include "sync/test/engine/mock_nudge_handler.h" 17 #include "sync/test/engine/single_type_mock_server.h" 18 #include "sync/test/fake_encryptor.h" 19 20 #include "testing/gtest/include/gtest/gtest.h" 21 22 static const std::string kTypeParentId = "PrefsRootNodeID"; 23 static const syncer::ModelType kModelType = syncer::PREFERENCES; 24 25 // Special constant value taken from cryptographer.cc. 26 const char kNigoriKeyName[] = "nigori-key"; 27 28 namespace syncer { 29 30 // Tests the ModelTypeSyncWorkerImpl. 31 // 32 // This class passes messages between the model thread and sync server. 33 // As such, its code is subject to lots of different race conditions. This 34 // test harness lets us exhaustively test all possible races. We try to 35 // focus on just a few interesting cases. 36 // 37 // Inputs: 38 // - Initial data type state from the model thread. 39 // - Commit requests from the model thread. 40 // - Update responses from the server. 41 // - Commit responses from the server. 42 // - The cryptographer, if encryption is enabled. 43 // 44 // Outputs: 45 // - Commit requests to the server. 46 // - Commit responses to the model thread. 47 // - Update responses to the model thread. 48 // - Nudges to the sync scheduler. 49 // 50 // We use the MockModelTypeSyncProxy to stub out all communication 51 // with the model thread. That interface is synchronous, which makes it 52 // much easier to test races. 53 // 54 // The interface with the server is built around "pulling" data from this 55 // class, so we don't have to mock out any of it. We wrap it with some 56 // convenience functions to we can emulate server behavior. 57 class ModelTypeSyncWorkerImplTest : public ::testing::Test { 58 public: 59 ModelTypeSyncWorkerImplTest(); 60 virtual ~ModelTypeSyncWorkerImplTest(); 61 62 // One of these Initialize functions should be called at the beginning of 63 // each test. 64 65 // Initializes with no data type state. We will be unable to perform any 66 // significant server action until we receive an update response that 67 // contains the type root node for this type. 68 void FirstInitialize(); 69 70 // Initializes with some existing data type state. Allows us to start 71 // committing items right away. 72 void NormalInitialize(); 73 74 // Initialize with some saved pending updates from the model thread. 75 void InitializeWithPendingUpdates( 76 const UpdateResponseDataList& initial_pending_updates); 77 78 // Initialize with a custom initial DataTypeState and pending updates. 79 void InitializeWithState(const DataTypeState& state, 80 const UpdateResponseDataList& pending_updates); 81 82 // Introduce a new key that the local cryptographer can't decrypt. 83 void NewForeignEncryptionKey(); 84 85 // Update the local cryptographer with all relevant keys. 86 void UpdateLocalCryptographer(); 87 88 // Use the Nth nigori instance to encrypt incoming updates. 89 // The default value, zero, indicates no encryption. 90 void SetUpdateEncryptionFilter(int n); 91 92 // Modifications on the model thread that get sent to the worker under test. 93 void CommitRequest(const std::string& tag, const std::string& value); 94 void DeleteRequest(const std::string& tag); 95 96 // Pretends to receive update messages from the server. 97 void TriggerTypeRootUpdateFromServer(); 98 void TriggerUpdateFromServer(int64 version_offset, 99 const std::string& tag, 100 const std::string& value); 101 void TriggerTombstoneFromServer(int64 version_offset, const std::string& tag); 102 103 // Delivers specified protos as updates. 104 // 105 // Does not update mock server state. Should be used as a last resort when 106 // writing test cases that require entities that don't fit the normal sync 107 // protocol. Try to use the other, higher level methods if possible. 108 void DeliverRawUpdates(const SyncEntityList& update_list); 109 110 // By default, this harness behaves as if all tasks posted to the model 111 // thread are executed immediately. However, this is not necessarily true. 112 // The model's TaskRunner has a queue, and the tasks we post to it could 113 // linger there for a while. In the meantime, the model thread could 114 // continue posting tasks to the worker based on its stale state. 115 // 116 // If you want to test those race cases, then these functions are for you. 117 void SetModelThreadIsSynchronous(bool is_synchronous); 118 void PumpModelThread(); 119 120 // Returns true if the |worker_| is ready to commit something. 121 bool WillCommit(); 122 123 // Pretend to successfully commit all outstanding unsynced items. 124 // It is safe to call this only if WillCommit() returns true. 125 void DoSuccessfulCommit(); 126 127 // Read commit messages the worker_ sent to the emulated server. 128 size_t GetNumCommitMessagesOnServer() const; 129 sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const; 130 131 // Read the latest version of sync entities committed to the emulated server. 132 bool HasCommitEntityOnServer(const std::string& tag) const; 133 sync_pb::SyncEntity GetLatestCommitEntityOnServer( 134 const std::string& tag) const; 135 136 // Read the latest update messages received on the model thread. 137 // Note that if the model thread is in non-blocking mode, this data will not 138 // be updated until the response is actually processed by the model thread. 139 size_t GetNumModelThreadUpdateResponses() const; 140 UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const; 141 UpdateResponseDataList GetNthModelThreadPendingUpdates(size_t n) const; 142 DataTypeState GetNthModelThreadUpdateState(size_t n) const; 143 144 // Reads the latest update response datas on the model thread. 145 // Note that if the model thread is in non-blocking mode, this data will not 146 // be updated until the response is actually processed by the model thread. 147 bool HasUpdateResponseOnModelThread(const std::string& tag) const; 148 UpdateResponseData GetUpdateResponseOnModelThread( 149 const std::string& tag) const; 150 151 // Read the latest commit messages received on the model thread. 152 // Note that if the model thread is in non-blocking mode, this data will not 153 // be updated until the response is actually processed by the model thread. 154 size_t GetNumModelThreadCommitResponses() const; 155 CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const; 156 DataTypeState GetNthModelThreadCommitState(size_t n) const; 157 158 // Reads the latest commit response datas on the model thread. 159 // Note that if the model thread is in non-blocking mode, this data will not 160 // be updated until the response is actually processed by the model thread. 161 bool HasCommitResponseOnModelThread(const std::string& tag) const; 162 CommitResponseData GetCommitResponseOnModelThread( 163 const std::string& tag) const; 164 165 // Returns the number of commit nudges sent to the mock nudge handler. 166 int GetNumCommitNudges() const; 167 168 // Returns the number of initial sync nudges sent to the mock nudge handler. 169 int GetNumInitialDownloadNudges() const; 170 171 // Returns the name of the encryption key in the cryptographer last passed to 172 // the ModelTypeSyncWorker. Returns an empty string if no crypgorapher is 173 // in use. See also: UpdateLocalCryptographer(). 174 std::string GetLocalCryptographerKeyName() const; 175 176 // Helpers for building various messages and structures. 177 static std::string GenerateTagHash(const std::string& tag); 178 static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag, 179 const std::string& value); 180 181 // Returns a set of KeyParams for the cryptographer. Each input 'n' value 182 // results in a different set of parameters. 183 static KeyParams GetNthKeyParams(int n); 184 185 // Returns the name for the given Nigori. 186 // 187 // Uses some 'white-box' knowledge to mimic the names that a real sync client 188 // would generate. It's probably not necessary to do so, but it can't hurt. 189 static std::string GetNigoriName(const Nigori& nigori); 190 191 // Modifies the input/output parameter |specifics| by encrypting it with 192 // a Nigori intialized with the specified KeyParams. 193 static void EncryptUpdate(const KeyParams& params, 194 sync_pb::EntitySpecifics* specifics); 195 196 private: 197 // An encryptor for our cryptographer. 198 FakeEncryptor fake_encryptor_; 199 200 // The cryptographer itself. NULL if we're not encrypting the type. 201 scoped_ptr<Cryptographer> cryptographer_; 202 203 // The number of the most recent foreign encryption key known to our 204 // cryptographer. Note that not all of these will be decryptable. 205 int foreign_encryption_key_index_; 206 207 // The number of the encryption key used to encrypt incoming updates. A zero 208 // value implies no encryption. 209 int update_encryption_filter_index_; 210 211 // The ModelTypeSyncWorkerImpl being tested. 212 scoped_ptr<ModelTypeSyncWorkerImpl> worker_; 213 214 // Non-owned, possibly NULL pointer. This object belongs to the 215 // ModelTypeSyncWorkerImpl under test. 216 MockModelTypeSyncProxy* mock_type_sync_proxy_; 217 218 // A mock that emulates enough of the sync server that it can be used 219 // a single UpdateHandler and CommitContributor pair. In this test 220 // harness, the |worker_| is both of them. 221 SingleTypeMockServer mock_server_; 222 223 // A mock to track the number of times the ModelTypeSyncWorker requests to 224 // sync. 225 MockNudgeHandler mock_nudge_handler_; 226 }; 227 228 ModelTypeSyncWorkerImplTest::ModelTypeSyncWorkerImplTest() 229 : foreign_encryption_key_index_(0), 230 update_encryption_filter_index_(0), 231 mock_type_sync_proxy_(NULL), 232 mock_server_(kModelType) { 233 } 234 235 ModelTypeSyncWorkerImplTest::~ModelTypeSyncWorkerImplTest() { 236 } 237 238 void ModelTypeSyncWorkerImplTest::FirstInitialize() { 239 DataTypeState initial_state; 240 initial_state.progress_marker.set_data_type_id( 241 GetSpecificsFieldNumberFromModelType(kModelType)); 242 initial_state.next_client_id = 0; 243 244 InitializeWithState(initial_state, UpdateResponseDataList()); 245 } 246 247 void ModelTypeSyncWorkerImplTest::NormalInitialize() { 248 InitializeWithPendingUpdates(UpdateResponseDataList()); 249 } 250 251 void ModelTypeSyncWorkerImplTest::InitializeWithPendingUpdates( 252 const UpdateResponseDataList& initial_pending_updates) { 253 DataTypeState initial_state; 254 initial_state.progress_marker.set_data_type_id( 255 GetSpecificsFieldNumberFromModelType(kModelType)); 256 initial_state.progress_marker.set_token("some_saved_progress_token"); 257 258 initial_state.next_client_id = 10; 259 initial_state.type_root_id = kTypeParentId; 260 initial_state.initial_sync_done = true; 261 262 InitializeWithState(initial_state, initial_pending_updates); 263 264 mock_nudge_handler_.ClearCounters(); 265 } 266 267 void ModelTypeSyncWorkerImplTest::InitializeWithState( 268 const DataTypeState& state, 269 const UpdateResponseDataList& initial_pending_updates) { 270 DCHECK(!worker_); 271 272 // We don't get to own this object. The |worker_| keeps a scoped_ptr to it. 273 mock_type_sync_proxy_ = new MockModelTypeSyncProxy(); 274 scoped_ptr<ModelTypeSyncProxy> proxy(mock_type_sync_proxy_); 275 276 scoped_ptr<Cryptographer> cryptographer_copy; 277 if (cryptographer_) { 278 cryptographer_copy.reset(new Cryptographer(*cryptographer_)); 279 } 280 281 worker_.reset(new ModelTypeSyncWorkerImpl(kModelType, 282 state, 283 initial_pending_updates, 284 cryptographer_copy.Pass(), 285 &mock_nudge_handler_, 286 proxy.Pass())); 287 } 288 289 void ModelTypeSyncWorkerImplTest::NewForeignEncryptionKey() { 290 if (!cryptographer_) { 291 cryptographer_.reset(new Cryptographer(&fake_encryptor_)); 292 } 293 294 foreign_encryption_key_index_++; 295 296 sync_pb::NigoriKeyBag bag; 297 298 for (int i = 0; i <= foreign_encryption_key_index_; ++i) { 299 Nigori nigori; 300 KeyParams params = GetNthKeyParams(i); 301 nigori.InitByDerivation(params.hostname, params.username, params.password); 302 303 sync_pb::NigoriKey* key = bag.add_key(); 304 305 key->set_name(GetNigoriName(nigori)); 306 nigori.ExportKeys(key->mutable_user_key(), 307 key->mutable_encryption_key(), 308 key->mutable_mac_key()); 309 } 310 311 // Re-create the last nigori from that loop. 312 Nigori last_nigori; 313 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); 314 last_nigori.InitByDerivation( 315 params.hostname, params.username, params.password); 316 317 // Serialize and encrypt the bag with the last nigori. 318 std::string serialized_bag; 319 bag.SerializeToString(&serialized_bag); 320 321 sync_pb::EncryptedData encrypted; 322 encrypted.set_key_name(GetNigoriName(last_nigori)); 323 last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob()); 324 325 // Update the cryptographer with new pending keys. 326 cryptographer_->SetPendingKeys(encrypted); 327 328 // Update the worker with the latest cryptographer. 329 if (worker_) { 330 worker_->UpdateCryptographer( 331 make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_))); 332 } 333 } 334 335 void ModelTypeSyncWorkerImplTest::UpdateLocalCryptographer() { 336 if (!cryptographer_) { 337 cryptographer_.reset(new Cryptographer(&fake_encryptor_)); 338 } 339 340 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_); 341 bool success = cryptographer_->DecryptPendingKeys(params); 342 DCHECK(success); 343 344 // Update the worker with the latest cryptographer. 345 if (worker_) { 346 worker_->UpdateCryptographer( 347 make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_))); 348 } 349 } 350 351 void ModelTypeSyncWorkerImplTest::SetUpdateEncryptionFilter(int n) { 352 update_encryption_filter_index_ = n; 353 } 354 355 void ModelTypeSyncWorkerImplTest::CommitRequest(const std::string& name, 356 const std::string& value) { 357 const std::string tag_hash = GenerateTagHash(name); 358 CommitRequestData data = mock_type_sync_proxy_->CommitRequest( 359 tag_hash, GenerateSpecifics(name, value)); 360 CommitRequestDataList list; 361 list.push_back(data); 362 worker_->EnqueueForCommit(list); 363 } 364 365 void ModelTypeSyncWorkerImplTest::DeleteRequest(const std::string& tag) { 366 const std::string tag_hash = GenerateTagHash(tag); 367 CommitRequestData data = mock_type_sync_proxy_->DeleteRequest(tag_hash); 368 CommitRequestDataList list; 369 list.push_back(data); 370 worker_->EnqueueForCommit(list); 371 } 372 373 void ModelTypeSyncWorkerImplTest::TriggerTypeRootUpdateFromServer() { 374 sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate(); 375 SyncEntityList entity_list; 376 entity_list.push_back(&entity); 377 378 sessions::StatusController dummy_status; 379 380 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), 381 mock_server_.GetContext(), 382 entity_list, 383 &dummy_status); 384 worker_->ApplyUpdates(&dummy_status); 385 } 386 387 void ModelTypeSyncWorkerImplTest::TriggerUpdateFromServer( 388 int64 version_offset, 389 const std::string& tag, 390 const std::string& value) { 391 sync_pb::SyncEntity entity = mock_server_.UpdateFromServer( 392 version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value)); 393 394 if (update_encryption_filter_index_ != 0) { 395 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), 396 entity.mutable_specifics()); 397 } 398 399 SyncEntityList entity_list; 400 entity_list.push_back(&entity); 401 402 sessions::StatusController dummy_status; 403 404 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), 405 mock_server_.GetContext(), 406 entity_list, 407 &dummy_status); 408 worker_->ApplyUpdates(&dummy_status); 409 } 410 411 void ModelTypeSyncWorkerImplTest::DeliverRawUpdates( 412 const SyncEntityList& list) { 413 sessions::StatusController dummy_status; 414 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), 415 mock_server_.GetContext(), 416 list, 417 &dummy_status); 418 worker_->ApplyUpdates(&dummy_status); 419 } 420 421 void ModelTypeSyncWorkerImplTest::TriggerTombstoneFromServer( 422 int64 version_offset, 423 const std::string& tag) { 424 sync_pb::SyncEntity entity = 425 mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag)); 426 427 if (update_encryption_filter_index_ != 0) { 428 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_), 429 entity.mutable_specifics()); 430 } 431 432 SyncEntityList entity_list; 433 entity_list.push_back(&entity); 434 435 sessions::StatusController dummy_status; 436 437 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(), 438 mock_server_.GetContext(), 439 entity_list, 440 &dummy_status); 441 worker_->ApplyUpdates(&dummy_status); 442 } 443 444 void ModelTypeSyncWorkerImplTest::SetModelThreadIsSynchronous( 445 bool is_synchronous) { 446 mock_type_sync_proxy_->SetSynchronousExecution(is_synchronous); 447 } 448 449 void ModelTypeSyncWorkerImplTest::PumpModelThread() { 450 mock_type_sync_proxy_->RunQueuedTasks(); 451 } 452 453 bool ModelTypeSyncWorkerImplTest::WillCommit() { 454 scoped_ptr<CommitContribution> contribution( 455 worker_->GetContribution(INT_MAX)); 456 457 if (contribution) { 458 contribution->CleanUp(); // Gracefully abort the commit. 459 return true; 460 } else { 461 return false; 462 } 463 } 464 465 // Conveniently, this is all one big synchronous operation. The sync thread 466 // remains blocked while the commit is in progress, so we don't need to worry 467 // about other tasks being run between the time when the commit request is 468 // issued and the time when the commit response is received. 469 void ModelTypeSyncWorkerImplTest::DoSuccessfulCommit() { 470 DCHECK(WillCommit()); 471 scoped_ptr<CommitContribution> contribution( 472 worker_->GetContribution(INT_MAX)); 473 474 sync_pb::ClientToServerMessage message; 475 contribution->AddToCommitMessage(&message); 476 477 sync_pb::ClientToServerResponse response = 478 mock_server_.DoSuccessfulCommit(message); 479 480 sessions::StatusController dummy_status; 481 contribution->ProcessCommitResponse(response, &dummy_status); 482 contribution->CleanUp(); 483 } 484 485 size_t ModelTypeSyncWorkerImplTest::GetNumCommitMessagesOnServer() const { 486 return mock_server_.GetNumCommitMessages(); 487 } 488 489 sync_pb::ClientToServerMessage 490 ModelTypeSyncWorkerImplTest::GetNthCommitMessageOnServer(size_t n) const { 491 DCHECK_LT(n, GetNumCommitMessagesOnServer()); 492 return mock_server_.GetNthCommitMessage(n); 493 } 494 495 bool ModelTypeSyncWorkerImplTest::HasCommitEntityOnServer( 496 const std::string& tag) const { 497 const std::string tag_hash = GenerateTagHash(tag); 498 return mock_server_.HasCommitEntity(tag_hash); 499 } 500 501 sync_pb::SyncEntity ModelTypeSyncWorkerImplTest::GetLatestCommitEntityOnServer( 502 const std::string& tag) const { 503 DCHECK(HasCommitEntityOnServer(tag)); 504 const std::string tag_hash = GenerateTagHash(tag); 505 return mock_server_.GetLastCommittedEntity(tag_hash); 506 } 507 508 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadUpdateResponses() const { 509 return mock_type_sync_proxy_->GetNumUpdateResponses(); 510 } 511 512 UpdateResponseDataList 513 ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateResponse(size_t n) const { 514 DCHECK_LT(n, GetNumModelThreadUpdateResponses()); 515 return mock_type_sync_proxy_->GetNthUpdateResponse(n); 516 } 517 518 UpdateResponseDataList 519 ModelTypeSyncWorkerImplTest::GetNthModelThreadPendingUpdates(size_t n) const { 520 DCHECK_LT(n, GetNumModelThreadUpdateResponses()); 521 return mock_type_sync_proxy_->GetNthPendingUpdates(n); 522 } 523 524 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateState( 525 size_t n) const { 526 DCHECK_LT(n, GetNumModelThreadUpdateResponses()); 527 return mock_type_sync_proxy_->GetNthTypeStateReceivedInUpdateResponse(n); 528 } 529 530 bool ModelTypeSyncWorkerImplTest::HasUpdateResponseOnModelThread( 531 const std::string& tag) const { 532 const std::string tag_hash = GenerateTagHash(tag); 533 return mock_type_sync_proxy_->HasUpdateResponse(tag_hash); 534 } 535 536 UpdateResponseData ModelTypeSyncWorkerImplTest::GetUpdateResponseOnModelThread( 537 const std::string& tag) const { 538 const std::string tag_hash = GenerateTagHash(tag); 539 return mock_type_sync_proxy_->GetUpdateResponse(tag_hash); 540 } 541 542 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadCommitResponses() const { 543 return mock_type_sync_proxy_->GetNumCommitResponses(); 544 } 545 546 CommitResponseDataList 547 ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitResponse(size_t n) const { 548 DCHECK_LT(n, GetNumModelThreadCommitResponses()); 549 return mock_type_sync_proxy_->GetNthCommitResponse(n); 550 } 551 552 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitState( 553 size_t n) const { 554 DCHECK_LT(n, GetNumModelThreadCommitResponses()); 555 return mock_type_sync_proxy_->GetNthTypeStateReceivedInCommitResponse(n); 556 } 557 558 bool ModelTypeSyncWorkerImplTest::HasCommitResponseOnModelThread( 559 const std::string& tag) const { 560 const std::string tag_hash = GenerateTagHash(tag); 561 return mock_type_sync_proxy_->HasCommitResponse(tag_hash); 562 } 563 564 CommitResponseData ModelTypeSyncWorkerImplTest::GetCommitResponseOnModelThread( 565 const std::string& tag) const { 566 DCHECK(HasCommitResponseOnModelThread(tag)); 567 const std::string tag_hash = GenerateTagHash(tag); 568 return mock_type_sync_proxy_->GetCommitResponse(tag_hash); 569 } 570 571 int ModelTypeSyncWorkerImplTest::GetNumCommitNudges() const { 572 return mock_nudge_handler_.GetNumCommitNudges(); 573 } 574 575 int ModelTypeSyncWorkerImplTest::GetNumInitialDownloadNudges() const { 576 return mock_nudge_handler_.GetNumInitialDownloadNudges(); 577 } 578 579 std::string ModelTypeSyncWorkerImplTest::GetLocalCryptographerKeyName() const { 580 if (!cryptographer_) { 581 return std::string(); 582 } 583 584 return cryptographer_->GetDefaultNigoriKeyName(); 585 } 586 587 // static. 588 std::string ModelTypeSyncWorkerImplTest::GenerateTagHash( 589 const std::string& tag) { 590 const std::string& client_tag_hash = 591 syncable::GenerateSyncableHash(kModelType, tag); 592 return client_tag_hash; 593 } 594 595 // static. 596 sync_pb::EntitySpecifics ModelTypeSyncWorkerImplTest::GenerateSpecifics( 597 const std::string& tag, 598 const std::string& value) { 599 sync_pb::EntitySpecifics specifics; 600 specifics.mutable_preference()->set_name(tag); 601 specifics.mutable_preference()->set_value(value); 602 return specifics; 603 } 604 605 // static. 606 std::string ModelTypeSyncWorkerImplTest::GetNigoriName(const Nigori& nigori) { 607 std::string name; 608 if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) { 609 NOTREACHED(); 610 return std::string(); 611 } 612 613 return name; 614 } 615 616 // static. 617 KeyParams ModelTypeSyncWorkerImplTest::GetNthKeyParams(int n) { 618 KeyParams params; 619 params.hostname = std::string("localhost"); 620 params.username = std::string("userX"); 621 params.password = base::StringPrintf("pw%02d", n); 622 return params; 623 } 624 625 // static. 626 void ModelTypeSyncWorkerImplTest::EncryptUpdate( 627 const KeyParams& params, 628 sync_pb::EntitySpecifics* specifics) { 629 Nigori nigori; 630 nigori.InitByDerivation(params.hostname, params.username, params.password); 631 632 sync_pb::EntitySpecifics original_specifics = *specifics; 633 std::string plaintext; 634 original_specifics.SerializeToString(&plaintext); 635 636 std::string encrypted; 637 nigori.Encrypt(plaintext, &encrypted); 638 639 specifics->Clear(); 640 AddDefaultFieldValue(kModelType, specifics); 641 specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori)); 642 specifics->mutable_encrypted()->set_blob(encrypted); 643 } 644 645 // Requests a commit and verifies the messages sent to the client and server as 646 // a result. 647 // 648 // This test performs sanity checks on most of the fields in these messages. 649 // For the most part this is checking that the test code behaves as expected 650 // and the |worker_| doesn't mess up its simple task of moving around these 651 // values. It makes sense to have one or two tests that are this thorough, but 652 // we shouldn't be this verbose in all tests. 653 TEST_F(ModelTypeSyncWorkerImplTest, SimpleCommit) { 654 NormalInitialize(); 655 656 EXPECT_FALSE(WillCommit()); 657 EXPECT_EQ(0U, GetNumCommitMessagesOnServer()); 658 EXPECT_EQ(0U, GetNumModelThreadCommitResponses()); 659 660 CommitRequest("tag1", "value1"); 661 662 EXPECT_EQ(1, GetNumCommitNudges()); 663 664 ASSERT_TRUE(WillCommit()); 665 DoSuccessfulCommit(); 666 667 const std::string& client_tag_hash = GenerateTagHash("tag1"); 668 669 // Exhaustively verify the SyncEntity sent in the commit message. 670 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); 671 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); 672 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); 673 const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1"); 674 EXPECT_FALSE(entity.id_string().empty()); 675 EXPECT_EQ(kTypeParentId, entity.parent_id_string()); 676 EXPECT_EQ(kUncommittedVersion, entity.version()); 677 EXPECT_NE(0, entity.mtime()); 678 EXPECT_NE(0, entity.ctime()); 679 EXPECT_FALSE(entity.name().empty()); 680 EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag()); 681 EXPECT_EQ("tag1", entity.specifics().preference().name()); 682 EXPECT_FALSE(entity.deleted()); 683 EXPECT_EQ("value1", entity.specifics().preference().value()); 684 685 // Exhaustively verify the commit response returned to the model thread. 686 ASSERT_EQ(1U, GetNumModelThreadCommitResponses()); 687 EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size()); 688 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); 689 const CommitResponseData& commit_response = 690 GetCommitResponseOnModelThread("tag1"); 691 692 // The ID changes in a commit response to initial commit. 693 EXPECT_FALSE(commit_response.id.empty()); 694 EXPECT_NE(entity.id_string(), commit_response.id); 695 696 EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash); 697 EXPECT_LT(0, commit_response.response_version); 698 } 699 700 TEST_F(ModelTypeSyncWorkerImplTest, SimpleDelete) { 701 NormalInitialize(); 702 703 // We can't delete an entity that was never committed. 704 // Step 1 is to create and commit a new entity. 705 CommitRequest("tag1", "value1"); 706 EXPECT_EQ(1, GetNumCommitNudges()); 707 ASSERT_TRUE(WillCommit()); 708 DoSuccessfulCommit(); 709 710 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); 711 const CommitResponseData& initial_commit_response = 712 GetCommitResponseOnModelThread("tag1"); 713 int64 base_version = initial_commit_response.response_version; 714 715 // Now that we have an entity, we can delete it. 716 DeleteRequest("tag1"); 717 ASSERT_TRUE(WillCommit()); 718 DoSuccessfulCommit(); 719 720 // Verify the SyncEntity sent in the commit message. 721 ASSERT_EQ(2U, GetNumCommitMessagesOnServer()); 722 EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size()); 723 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); 724 const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1"); 725 EXPECT_FALSE(entity.id_string().empty()); 726 EXPECT_EQ(GenerateTagHash("tag1"), entity.client_defined_unique_tag()); 727 EXPECT_EQ(base_version, entity.version()); 728 EXPECT_TRUE(entity.deleted()); 729 730 // Deletions should contain enough specifics to identify the type. 731 EXPECT_TRUE(entity.has_specifics()); 732 EXPECT_EQ(kModelType, GetModelTypeFromSpecifics(entity.specifics())); 733 734 // Verify the commit response returned to the model thread. 735 ASSERT_EQ(2U, GetNumModelThreadCommitResponses()); 736 EXPECT_EQ(1U, GetNthModelThreadCommitResponse(1).size()); 737 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1")); 738 const CommitResponseData& commit_response = 739 GetCommitResponseOnModelThread("tag1"); 740 741 EXPECT_EQ(entity.id_string(), commit_response.id); 742 EXPECT_EQ(entity.client_defined_unique_tag(), 743 commit_response.client_tag_hash); 744 EXPECT_EQ(entity.version(), commit_response.response_version); 745 } 746 747 // The server doesn't like it when we try to delete an entity it's never heard 748 // of before. This test helps ensure we avoid that scenario. 749 TEST_F(ModelTypeSyncWorkerImplTest, NoDeleteUncommitted) { 750 NormalInitialize(); 751 752 // Request the commit of a new, never-before-seen item. 753 CommitRequest("tag1", "value1"); 754 EXPECT_TRUE(WillCommit()); 755 EXPECT_EQ(1, GetNumCommitNudges()); 756 757 // Request a deletion of that item before we've had a chance to commit it. 758 DeleteRequest("tag1"); 759 EXPECT_FALSE(WillCommit()); 760 EXPECT_EQ(2, GetNumCommitNudges()); 761 } 762 763 // Verifies the sending of an "initial sync done" signal. 764 TEST_F(ModelTypeSyncWorkerImplTest, SendInitialSyncDone) { 765 FirstInitialize(); // Initialize with no saved sync state. 766 EXPECT_EQ(0U, GetNumModelThreadUpdateResponses()); 767 EXPECT_EQ(1, GetNumInitialDownloadNudges()); 768 769 // Receive an update response that contains only the type root node. 770 TriggerTypeRootUpdateFromServer(); 771 772 // Two updates: 773 // - One triggered by process updates to forward the type root ID. 774 // - One triggered by apply updates, which the worker interprets to mean 775 // "initial sync done". This triggers a model thread update, too. 776 EXPECT_EQ(2U, GetNumModelThreadUpdateResponses()); 777 778 // The type root and initial sync done updates both contain no entities. 779 EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size()); 780 EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(1).size()); 781 782 const DataTypeState& state = GetNthModelThreadUpdateState(1); 783 EXPECT_FALSE(state.progress_marker.token().empty()); 784 EXPECT_FALSE(state.type_root_id.empty()); 785 EXPECT_TRUE(state.initial_sync_done); 786 } 787 788 // Commit two new entities in two separate commit messages. 789 TEST_F(ModelTypeSyncWorkerImplTest, TwoNewItemsCommittedSeparately) { 790 NormalInitialize(); 791 792 // Commit the first of two entities. 793 CommitRequest("tag1", "value1"); 794 EXPECT_EQ(1, GetNumCommitNudges()); 795 ASSERT_TRUE(WillCommit()); 796 DoSuccessfulCommit(); 797 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); 798 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); 799 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); 800 const sync_pb::SyncEntity& tag1_entity = 801 GetLatestCommitEntityOnServer("tag1"); 802 803 // Commit the second of two entities. 804 CommitRequest("tag2", "value2"); 805 EXPECT_EQ(2, GetNumCommitNudges()); 806 ASSERT_TRUE(WillCommit()); 807 DoSuccessfulCommit(); 808 ASSERT_EQ(2U, GetNumCommitMessagesOnServer()); 809 EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size()); 810 ASSERT_TRUE(HasCommitEntityOnServer("tag2")); 811 const sync_pb::SyncEntity& tag2_entity = 812 GetLatestCommitEntityOnServer("tag2"); 813 814 EXPECT_FALSE(WillCommit()); 815 816 // The IDs assigned by the |worker_| should be unique. 817 EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string()); 818 819 // Check that the committed specifics values are sane. 820 EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1"); 821 EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2"); 822 823 // There should have been two separate commit responses sent to the model 824 // thread. They should be uninteresting, so we don't bother inspecting them. 825 EXPECT_EQ(2U, GetNumModelThreadCommitResponses()); 826 } 827 828 // Test normal update receipt code path. 829 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) { 830 NormalInitialize(); 831 832 const std::string& tag_hash = GenerateTagHash("tag1"); 833 834 TriggerUpdateFromServer(10, "tag1", "value1"); 835 836 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); 837 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); 838 ASSERT_EQ(1U, updates_list.size()); 839 840 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); 841 UpdateResponseData update = GetUpdateResponseOnModelThread("tag1"); 842 843 EXPECT_FALSE(update.id.empty()); 844 EXPECT_EQ(tag_hash, update.client_tag_hash); 845 EXPECT_LT(0, update.response_version); 846 EXPECT_FALSE(update.ctime.is_null()); 847 EXPECT_FALSE(update.mtime.is_null()); 848 EXPECT_FALSE(update.non_unique_name.empty()); 849 EXPECT_FALSE(update.deleted); 850 EXPECT_EQ("tag1", update.specifics.preference().name()); 851 EXPECT_EQ("value1", update.specifics.preference().value()); 852 } 853 854 // Test commit of encrypted updates. 855 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedCommit) { 856 NormalInitialize(); 857 858 ASSERT_EQ(0U, GetNumModelThreadUpdateResponses()); 859 860 NewForeignEncryptionKey(); 861 UpdateLocalCryptographer(); 862 863 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); 864 EXPECT_EQ(GetLocalCryptographerKeyName(), 865 GetNthModelThreadUpdateState(0).encryption_key_name); 866 867 // Normal commit request stuff. 868 CommitRequest("tag1", "value1"); 869 DoSuccessfulCommit(); 870 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); 871 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); 872 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); 873 const sync_pb::SyncEntity& tag1_entity = 874 GetLatestCommitEntityOnServer("tag1"); 875 876 EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); 877 878 // The title should be overwritten. 879 EXPECT_EQ(tag1_entity.name(), "encrypted"); 880 881 // The type should be set, but there should be no non-encrypted contents. 882 EXPECT_TRUE(tag1_entity.specifics().has_preference()); 883 EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); 884 EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); 885 } 886 887 // Test items are not committed when encryption is required but unavailable. 888 TEST_F(ModelTypeSyncWorkerImplTest, EncryptionBlocksCommits) { 889 NormalInitialize(); 890 891 CommitRequest("tag1", "value1"); 892 EXPECT_TRUE(WillCommit()); 893 894 // We know encryption is in use on this account, but don't have the necessary 895 // encryption keys. The worker should refuse to commit. 896 NewForeignEncryptionKey(); 897 EXPECT_FALSE(WillCommit()); 898 899 // Once the cryptographer is returned to a normal state, we should be able to 900 // commit again. 901 EXPECT_EQ(1, GetNumCommitNudges()); 902 UpdateLocalCryptographer(); 903 EXPECT_EQ(2, GetNumCommitNudges()); 904 EXPECT_TRUE(WillCommit()); 905 906 // Verify the committed entity was properly encrypted. 907 DoSuccessfulCommit(); 908 ASSERT_EQ(1U, GetNumCommitMessagesOnServer()); 909 EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size()); 910 ASSERT_TRUE(HasCommitEntityOnServer("tag1")); 911 const sync_pb::SyncEntity& tag1_entity = 912 GetLatestCommitEntityOnServer("tag1"); 913 EXPECT_TRUE(tag1_entity.specifics().has_encrypted()); 914 EXPECT_EQ(tag1_entity.name(), "encrypted"); 915 EXPECT_TRUE(tag1_entity.specifics().has_preference()); 916 EXPECT_FALSE(tag1_entity.specifics().preference().has_name()); 917 EXPECT_FALSE(tag1_entity.specifics().preference().has_value()); 918 } 919 920 // Test the receipt of decryptable entities. 921 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveDecryptableEntities) { 922 NormalInitialize(); 923 924 // Create a new Nigori and allow the cryptographer to decrypt it. 925 NewForeignEncryptionKey(); 926 UpdateLocalCryptographer(); 927 928 // First, receive an unencrypted entry. 929 TriggerUpdateFromServer(10, "tag1", "value1"); 930 931 // Test some basic properties regarding the update. 932 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); 933 UpdateResponseData update1 = GetUpdateResponseOnModelThread("tag1"); 934 EXPECT_EQ("tag1", update1.specifics.preference().name()); 935 EXPECT_EQ("value1", update1.specifics.preference().value()); 936 EXPECT_TRUE(update1.encryption_key_name.empty()); 937 938 // Set received updates to be encrypted using the new nigori. 939 SetUpdateEncryptionFilter(1); 940 941 // This next update will be encrypted. 942 TriggerUpdateFromServer(10, "tag2", "value2"); 943 944 // Test its basic features and the value of encryption_key_name. 945 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag2")); 946 UpdateResponseData update2 = GetUpdateResponseOnModelThread("tag2"); 947 EXPECT_EQ("tag2", update2.specifics.preference().name()); 948 EXPECT_EQ("value2", update2.specifics.preference().value()); 949 EXPECT_FALSE(update2.encryption_key_name.empty()); 950 } 951 952 // Test initializing a ModelTypeSyncWorker with a cryptographer at startup. 953 TEST_F(ModelTypeSyncWorkerImplTest, InitializeWithCryptographer) { 954 // Set up some encryption state. 955 NewForeignEncryptionKey(); 956 UpdateLocalCryptographer(); 957 958 // Then initialize. 959 NormalInitialize(); 960 961 // The worker should tell the model thread about encryption as soon as 962 // possible, so that it will have the chance to re-encrypt local data if 963 // necessary. 964 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); 965 EXPECT_EQ(GetLocalCryptographerKeyName(), 966 GetNthModelThreadUpdateState(0).encryption_key_name); 967 } 968 969 // Receive updates that are initially undecryptable, then ensure they get 970 // delivered to the model thread when decryption becomes possible. 971 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUndecryptableEntries) { 972 NormalInitialize(); 973 974 // Receive a new foreign encryption key that we can't decrypt. 975 NewForeignEncryptionKey(); 976 977 // Receive an encrypted with that new key, which we can't access. 978 SetUpdateEncryptionFilter(1); 979 TriggerUpdateFromServer(10, "tag1", "value1"); 980 981 // At this point, the cryptographer does not have access to the key, so the 982 // updates will be undecryptable. They'll be transfered to the model thread 983 // for safe-keeping as pending updates. 984 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); 985 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); 986 EXPECT_EQ(0U, updates_list.size()); 987 UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0); 988 EXPECT_EQ(1U, pending_updates.size()); 989 990 // The update will be delivered as soon as decryption becomes possible. 991 UpdateLocalCryptographer(); 992 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); 993 UpdateResponseData update = GetUpdateResponseOnModelThread("tag1"); 994 EXPECT_EQ("tag1", update.specifics.preference().name()); 995 EXPECT_EQ("value1", update.specifics.preference().value()); 996 EXPECT_FALSE(update.encryption_key_name.empty()); 997 } 998 999 // Ensure that even encrypted updates can cause conflicts. 1000 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedUpdateOverridesPendingCommit) { 1001 NormalInitialize(); 1002 1003 // Prepeare to commit an item. 1004 CommitRequest("tag1", "value1"); 1005 EXPECT_TRUE(WillCommit()); 1006 1007 // Receive an encrypted update for that item. 1008 SetUpdateEncryptionFilter(1); 1009 TriggerUpdateFromServer(10, "tag1", "value1"); 1010 1011 // The pending commit state should be cleared. 1012 EXPECT_FALSE(WillCommit()); 1013 1014 // The encrypted update will be delivered to the model thread. 1015 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses()); 1016 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0); 1017 EXPECT_EQ(0U, updates_list.size()); 1018 UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0); 1019 EXPECT_EQ(1U, pending_updates.size()); 1020 } 1021 1022 // Test decryption of pending updates saved across a restart. 1023 TEST_F(ModelTypeSyncWorkerImplTest, RestorePendingEntries) { 1024 // Create a fake pending update. 1025 UpdateResponseData update; 1026 1027 update.client_tag_hash = GenerateTagHash("tag1"); 1028 update.id = "SomeID"; 1029 update.response_version = 100; 1030 update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); 1031 update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); 1032 update.non_unique_name = "encrypted"; 1033 update.deleted = false; 1034 1035 update.specifics = GenerateSpecifics("tag1", "value1"); 1036 EncryptUpdate(GetNthKeyParams(1), &(update.specifics)); 1037 1038 // Inject the update during ModelTypeSyncWorker initialization. 1039 UpdateResponseDataList saved_pending_updates; 1040 saved_pending_updates.push_back(update); 1041 InitializeWithPendingUpdates(saved_pending_updates); 1042 1043 // Update will be undecryptable at first. 1044 EXPECT_EQ(0U, GetNumModelThreadUpdateResponses()); 1045 ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1")); 1046 1047 // Update the cryptographer so it can decrypt that update. 1048 NewForeignEncryptionKey(); 1049 UpdateLocalCryptographer(); 1050 1051 // Verify the item gets decrypted and sent back to the model thread. 1052 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); 1053 } 1054 1055 // Test decryption of pending updates saved across a restart. This test 1056 // differs from the previous one in that the restored updates can be decrypted 1057 // immediately after the ModelTypeSyncWorker is constructed. 1058 TEST_F(ModelTypeSyncWorkerImplTest, RestoreApplicableEntries) { 1059 // Update the cryptographer so it can decrypt that update. 1060 NewForeignEncryptionKey(); 1061 UpdateLocalCryptographer(); 1062 1063 // Create a fake pending update. 1064 UpdateResponseData update; 1065 update.client_tag_hash = GenerateTagHash("tag1"); 1066 update.id = "SomeID"; 1067 update.response_version = 100; 1068 update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10); 1069 update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11); 1070 update.non_unique_name = "encrypted"; 1071 update.deleted = false; 1072 1073 update.specifics = GenerateSpecifics("tag1", "value1"); 1074 EncryptUpdate(GetNthKeyParams(1), &(update.specifics)); 1075 1076 // Inject the update during ModelTypeSyncWorker initialization. 1077 UpdateResponseDataList saved_pending_updates; 1078 saved_pending_updates.push_back(update); 1079 InitializeWithPendingUpdates(saved_pending_updates); 1080 1081 // Verify the item gets decrypted and sent back to the model thread. 1082 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1")); 1083 } 1084 1085 // Test that undecryptable updates provide sufficient reason to not commit. 1086 // 1087 // This should be rare in practice. Usually the cryptographer will be in an 1088 // unusable state when we receive undecryptable updates, and that alone will be 1089 // enough to prevent all commits. 1090 TEST_F(ModelTypeSyncWorkerImplTest, CommitBlockedByPending) { 1091 NormalInitialize(); 1092 1093 // Prepeare to commit an item. 1094 CommitRequest("tag1", "value1"); 1095 EXPECT_TRUE(WillCommit()); 1096 1097 // Receive an encrypted update for that item. 1098 SetUpdateEncryptionFilter(1); 1099 TriggerUpdateFromServer(10, "tag1", "value1"); 1100 1101 // The pending commit state should be cleared. 1102 EXPECT_FALSE(WillCommit()); 1103 1104 // The pending update will be delivered to the model thread. 1105 HasUpdateResponseOnModelThread("tag1"); 1106 1107 // Pretend the update arrived too late to prevent another commit request. 1108 CommitRequest("tag1", "value2"); 1109 1110 EXPECT_FALSE(WillCommit()); 1111 } 1112 1113 // Verify that corrupted encrypted updates don't cause crashes. 1114 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveCorruptEncryption) { 1115 // Initialize the worker with basic encryption state. 1116 NormalInitialize(); 1117 NewForeignEncryptionKey(); 1118 UpdateLocalCryptographer(); 1119 1120 // Manually create an update. 1121 sync_pb::SyncEntity entity; 1122 entity.set_client_defined_unique_tag(GenerateTagHash("tag1")); 1123 entity.set_id_string("SomeID"); 1124 entity.set_version(1); 1125 entity.set_ctime(1000); 1126 entity.set_mtime(1001); 1127 entity.set_name("encrypted"); 1128 entity.set_deleted(false); 1129 1130 // Encrypt it. 1131 entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1")); 1132 EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics()); 1133 1134 // Replace a few bytes to corrupt it. 1135 entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace( 1136 0, 4, "xyz!"); 1137 1138 SyncEntityList entity_list; 1139 entity_list.push_back(&entity); 1140 1141 // If a corrupt update could trigger a crash, this is where it would happen. 1142 DeliverRawUpdates(entity_list); 1143 1144 EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1")); 1145 1146 // Deliver a non-corrupt update to see if the everything still works. 1147 SetUpdateEncryptionFilter(1); 1148 TriggerUpdateFromServer(10, "tag1", "value1"); 1149 EXPECT_TRUE(HasUpdateResponseOnModelThread("tag1")); 1150 } 1151 1152 } // namespace syncer 1153