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.
5 #include "sync/test/fake_server/fake_server.h"
12 #include "base/basictypes.h"
13 #include "base/guid.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/synchronization/lock.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_status_code.h"
23 #include "sync/internal_api/public/base/model_type.h"
24 #include "sync/protocol/sync.pb.h"
25 #include "sync/test/fake_server/bookmark_entity.h"
26 #include "sync/test/fake_server/permanent_entity.h"
27 #include "sync/test/fake_server/tombstone_entity.h"
28 #include "sync/test/fake_server/unique_client_entity.h"
33 using syncer::GetModelType
;
34 using syncer::ModelType
;
35 using syncer::ModelTypeSet
;
37 namespace fake_server
{
39 class FakeServerEntity
;
43 // The default store birthday value.
44 static const char kDefaultStoreBirthday
[] = "1234567890";
46 // The default keystore key.
47 static const char kDefaultKeystoreKey
[] = "1111111111111111";
49 // Properties of the bookmark bar permanent folder.
50 static const char kBookmarkBarFolderServerTag
[] = "bookmark_bar";
51 static const char kBookmarkBarFolderName
[] = "Bookmark Bar";
53 // Properties of the other bookmarks permanent folder.
54 static const char kOtherBookmarksFolderServerTag
[] = "other_bookmarks";
55 static const char kOtherBookmarksFolderName
[] = "Other Bookmarks";
57 // Properties of the synced bookmarks permanent folder.
58 static const char kSyncedBookmarksFolderServerTag
[] = "synced_bookmarks";
59 static const char kSyncedBookmarksFolderName
[] = "Synced Bookmarks";
61 // A filter used during GetUpdates calls to determine what information to
62 // send back to the client. There is a 1:1 correspondence between any given
63 // GetUpdates call and an UpdateSieve instance.
68 // Factory method for creating an UpdateSieve.
69 static scoped_ptr
<UpdateSieve
> Create(
70 const sync_pb::GetUpdatesMessage
& get_updates_message
);
72 // Sets the progress markers in |get_updates_response| given the progress
73 // markers from the original GetUpdatesMessage and |new_version| (the latest
74 // version in the entries sent back).
75 void UpdateProgressMarkers(
77 sync_pb::GetUpdatesResponse
* get_updates_response
) const {
78 ModelTypeToVersionMap::const_iterator it
;
79 for (it
= request_from_version_
.begin(); it
!= request_from_version_
.end();
81 sync_pb::DataTypeProgressMarker
* new_marker
=
82 get_updates_response
->add_new_progress_marker();
83 new_marker
->set_data_type_id(
84 GetSpecificsFieldNumberFromModelType(it
->first
));
86 int64 version
= std::max(new_version
, it
->second
);
87 new_marker
->set_token(base::Int64ToString(version
));
91 // Determines whether the server should send an |entity| to the client as
92 // part of a GetUpdatesResponse.
93 bool ClientWantsItem(FakeServerEntity
* entity
) const {
94 int64 version
= entity
->GetVersion();
95 if (version
<= min_version_
) {
97 } else if (entity
->IsDeleted()) {
101 ModelTypeToVersionMap::const_iterator it
=
102 request_from_version_
.find(entity
->GetModelType());
104 return it
== request_from_version_
.end() ? false : it
->second
< version
;
107 // Returns the minimum version seen across all types.
108 int64
GetMinVersion() const {
113 typedef std::map
<ModelType
, int64
> ModelTypeToVersionMap
;
115 // Creates an UpdateSieve.
116 UpdateSieve(const ModelTypeToVersionMap request_from_version
,
117 const int64 min_version
)
118 : request_from_version_(request_from_version
),
119 min_version_(min_version
) { }
121 // Maps data type IDs to the latest version seen for that type.
122 const ModelTypeToVersionMap request_from_version_
;
124 // The minimum version seen among all data types.
125 const int min_version_
;
128 scoped_ptr
<UpdateSieve
> UpdateSieve::Create(
129 const sync_pb::GetUpdatesMessage
& get_updates_message
) {
130 CHECK_GT(get_updates_message
.from_progress_marker_size(), 0)
131 << "A GetUpdates request must have at least one progress marker.";
133 UpdateSieve::ModelTypeToVersionMap request_from_version
;
134 int64 min_version
= std::numeric_limits
<int64
>::max();
135 for (int i
= 0; i
< get_updates_message
.from_progress_marker_size(); i
++) {
136 sync_pb::DataTypeProgressMarker marker
=
137 get_updates_message
.from_progress_marker(i
);
140 // Let the version remain zero if there is no token or an empty token (the
141 // first request for this type).
142 if (marker
.has_token() && !marker
.token().empty()) {
143 bool parsed
= base::StringToInt64(marker
.token(), &version
);
144 CHECK(parsed
) << "Unable to parse progress marker token.";
147 ModelType model_type
= syncer::GetModelTypeFromSpecificsFieldNumber(
148 marker
.data_type_id());
149 request_from_version
[model_type
] = version
;
151 if (version
< min_version
)
152 min_version
= version
;
155 return scoped_ptr
<UpdateSieve
>(
156 new UpdateSieve(request_from_version
, min_version
));
159 // Returns whether |entity| is deleted or a folder.
160 bool IsDeletedOrFolder(const FakeServerEntity
* entity
) {
162 return entity
->IsDeleted() || entity
->IsFolder();
167 FakeServer::FakeServer() : version_(0),
168 store_birthday_(kDefaultStoreBirthday
),
169 authenticated_(true),
170 error_type_(sync_pb::SyncEnums::SUCCESS
),
171 alternate_triggered_errors_(false),
173 network_enabled_(true),
174 weak_ptr_factory_(this) {
175 keystore_keys_
.push_back(kDefaultKeystoreKey
);
177 const bool create_result
= CreateDefaultPermanentItems();
178 DCHECK(create_result
) << "Permanent items were not created successfully.";
181 FakeServer::~FakeServer() {
182 DCHECK(thread_checker_
.CalledOnValidThread());
183 STLDeleteContainerPairSecondPointers(entities_
.begin(), entities_
.end());
186 bool FakeServer::CreatePermanentBookmarkFolder(const std::string
& server_tag
,
187 const std::string
& name
) {
188 DCHECK(thread_checker_
.CalledOnValidThread());
189 FakeServerEntity
* entity
=
190 PermanentEntity::Create(syncer::BOOKMARKS
, server_tag
, name
,
191 ModelTypeToRootTag(syncer::BOOKMARKS
));
199 bool FakeServer::CreateDefaultPermanentItems() {
200 ModelTypeSet all_types
= syncer::ProtocolTypes();
201 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
202 ModelType model_type
= it
.Get();
203 FakeServerEntity
* top_level_entity
=
204 PermanentEntity::CreateTopLevel(model_type
);
205 if (top_level_entity
== NULL
) {
208 SaveEntity(top_level_entity
);
210 if (model_type
== syncer::BOOKMARKS
) {
211 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag
,
212 kBookmarkBarFolderName
))
214 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag
,
215 kOtherBookmarksFolderName
))
223 void FakeServer::SaveEntity(FakeServerEntity
* entity
) {
224 delete entities_
[entity
->GetId()];
225 entity
->SetVersion(++version_
);
226 entities_
[entity
->GetId()] = entity
;
229 void FakeServer::HandleCommand(const string
& request
,
230 const base::Closure
& completion_closure
,
233 std::string
* response
) {
234 DCHECK(thread_checker_
.CalledOnValidThread());
235 if (!network_enabled_
) {
236 *error_code
= net::ERR_FAILED
;
237 *response_code
= net::ERR_FAILED
;
238 *response
= string();
239 completion_closure
.Run();
244 if (!authenticated_
) {
246 *response_code
= net::HTTP_UNAUTHORIZED
;
247 *response
= string();
248 completion_closure
.Run();
252 sync_pb::ClientToServerMessage message
;
253 bool parsed
= message
.ParseFromString(request
);
254 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
256 sync_pb::ClientToServerResponse response_proto
;
258 if (message
.has_store_birthday() &&
259 message
.store_birthday() != store_birthday_
) {
260 response_proto
.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY
);
261 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
&&
262 ShouldSendTriggeredError()) {
263 response_proto
.set_error_code(error_type_
);
264 } else if (triggered_actionable_error_
.get() && ShouldSendTriggeredError()) {
265 sync_pb::ClientToServerResponse_Error
* error
=
266 response_proto
.mutable_error();
267 error
->CopyFrom(*(triggered_actionable_error_
.get()));
269 bool success
= false;
270 switch (message
.message_contents()) {
271 case sync_pb::ClientToServerMessage::GET_UPDATES
:
272 success
= HandleGetUpdatesRequest(message
.get_updates(),
273 response_proto
.mutable_get_updates());
275 case sync_pb::ClientToServerMessage::COMMIT
:
276 success
= HandleCommitRequest(message
.commit(),
277 message
.invalidator_client_id(),
278 response_proto
.mutable_commit());
281 *error_code
= net::ERR_NOT_IMPLEMENTED
;
283 *response
= string();
284 completion_closure
.Run();
289 // TODO(pvalenzuela): Add logging here so that tests have more info about
291 *error_code
= net::ERR_FAILED
;
293 *response
= string();
294 completion_closure
.Run();
298 response_proto
.set_error_code(sync_pb::SyncEnums::SUCCESS
);
301 response_proto
.set_store_birthday(store_birthday_
);
304 *response_code
= net::HTTP_OK
;
305 *response
= response_proto
.SerializeAsString();
306 completion_closure
.Run();
309 bool FakeServer::HandleGetUpdatesRequest(
310 const sync_pb::GetUpdatesMessage
& get_updates
,
311 sync_pb::GetUpdatesResponse
* response
) {
312 // TODO(pvalenzuela): Implement batching instead of sending all information
314 response
->set_changes_remaining(0);
316 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
318 // This folder is called "Synced Bookmarks" by sync and is renamed
319 // "Mobile Bookmarks" by the mobile client UIs.
320 if (get_updates
.create_mobile_bookmarks_folder() &&
321 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag
,
322 kSyncedBookmarksFolderName
)) {
326 bool send_encryption_keys_based_on_nigori
= false;
327 int64 max_response_version
= 0;
328 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
330 FakeServerEntity
* entity
= it
->second
;
331 if (sieve
->ClientWantsItem(entity
)) {
332 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
333 entity
->SerializeAsProto(response_entity
);
334 max_response_version
= std::max(max_response_version
,
335 response_entity
->version());
337 if (entity
->GetModelType() == syncer::NIGORI
) {
338 send_encryption_keys_based_on_nigori
=
339 response_entity
->specifics().nigori().passphrase_type() ==
340 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
345 if (send_encryption_keys_based_on_nigori
||
346 get_updates
.need_encryption_key()) {
347 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
348 it
!= keystore_keys_
.end(); ++it
) {
349 response
->add_encryption_keys(*it
);
353 sieve
->UpdateProgressMarkers(max_response_version
, response
);
357 string
FakeServer::CommitEntity(
358 const sync_pb::SyncEntity
& client_entity
,
359 sync_pb::CommitResponse_EntryResponse
* entry_response
,
362 if (client_entity
.version() == 0 && client_entity
.deleted()) {
366 FakeServerEntity
* entity
;
367 if (client_entity
.deleted()) {
368 entity
= TombstoneEntity::Create(client_entity
.id_string());
369 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
370 // not modify server data if it fails.
371 if (!DeleteChildren(client_entity
.id_string())) {
374 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
375 // NIGORI is the only permanent item type that should be updated by the
377 entity
= PermanentEntity::CreateUpdatedNigoriEntity(
379 entities_
[client_entity
.id_string()]);
380 } else if (client_entity
.has_client_defined_unique_tag()) {
381 entity
= UniqueClientEntity::Create(client_entity
);
383 // TODO(pvalenzuela): Validate entity's parent ID.
384 if (entities_
.find(client_entity
.id_string()) != entities_
.end()) {
385 entity
= BookmarkEntity::CreateUpdatedVersion(
387 entities_
[client_entity
.id_string()],
390 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
394 if (entity
== NULL
) {
395 // TODO(pvalenzuela): Add logging so that it is easier to determine why
401 BuildEntryResponseForSuccessfulCommit(entry_response
, entity
);
402 return entity
->GetId();
405 void FakeServer::BuildEntryResponseForSuccessfulCommit(
406 sync_pb::CommitResponse_EntryResponse
* entry_response
,
407 FakeServerEntity
* entity
) {
408 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
409 entry_response
->set_id_string(entity
->GetId());
411 if (entity
->IsDeleted()) {
412 entry_response
->set_version(entity
->GetVersion() + 1);
414 entry_response
->set_version(entity
->GetVersion());
415 entry_response
->set_name(entity
->GetName());
419 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
420 if (entities_
.find(id
) == entities_
.end()) {
421 // We've hit an ID (probably the imaginary root entity) that isn't stored
422 // by the server, so it can't be a child.
424 } else if (entities_
[id
]->GetParentId() == potential_parent_id
) {
427 // Recursively look up the tree.
428 return IsChild(entities_
[id
]->GetParentId(), potential_parent_id
);
432 bool FakeServer::DeleteChildren(const string
& id
) {
433 vector
<string
> child_ids
;
434 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
436 if (IsChild(it
->first
, id
)) {
437 child_ids
.push_back(it
->first
);
441 for (vector
<string
>::iterator it
= child_ids
.begin(); it
!= child_ids
.end();
443 FakeServerEntity
* tombstone
= TombstoneEntity::Create(*it
);
444 if (tombstone
== NULL
) {
445 LOG(WARNING
) << "Tombstone creation failed for entity with ID " << *it
;
448 SaveEntity(tombstone
);
454 bool FakeServer::HandleCommitRequest(
455 const sync_pb::CommitMessage
& commit
,
456 const std::string
& invalidator_client_id
,
457 sync_pb::CommitResponse
* response
) {
458 std::map
<string
, string
> client_to_server_ids
;
459 string guid
= commit
.cache_guid();
460 ModelTypeSet committed_model_types
;
462 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
463 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
464 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
465 sync_pb::CommitResponse_EntryResponse
* entry_response
=
466 response
->add_entryresponse();
468 sync_pb::SyncEntity client_entity
= *it
;
469 string parent_id
= client_entity
.parent_id_string();
470 if (client_to_server_ids
.find(parent_id
) !=
471 client_to_server_ids
.end()) {
472 parent_id
= client_to_server_ids
[parent_id
];
475 string entity_id
= CommitEntity(client_entity
,
479 if (entity_id
.empty()) {
483 // Record the ID if it was renamed.
484 if (entity_id
!= client_entity
.id_string()) {
485 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
487 FakeServerEntity
* entity
= entities_
[entity_id
];
488 committed_model_types
.Put(entity
->GetModelType());
491 FOR_EACH_OBSERVER(Observer
, observers_
,
492 OnCommit(invalidator_client_id
, committed_model_types
));
496 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
497 DCHECK(thread_checker_
.CalledOnValidThread());
498 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
500 // Initialize an empty ListValue for all ModelTypes.
501 ModelTypeSet all_types
= ModelTypeSet::All();
502 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
503 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
506 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
508 FakeServerEntity
* entity
= it
->second
;
509 if (IsDeletedOrFolder(entity
)) {
510 // Tombstones are ignored as they don't represent current data. Folders
511 // are also ignored as current verification infrastructure does not
515 base::ListValue
* list_value
;
516 if (!dictionary
->GetList(ModelTypeToString(entity
->GetModelType()),
518 return scoped_ptr
<base::DictionaryValue
>();
520 // TODO(pvalenzuela): Store more data for each entity so additional
521 // verification can be performed. One example of additional verification
522 // is checking the correctness of the bookmark hierarchy.
523 list_value
->Append(new base::StringValue(entity
->GetName()));
526 return dictionary
.Pass();
529 std::vector
<sync_pb::SyncEntity
> FakeServer::GetSyncEntitiesByModelType(
530 ModelType model_type
) {
531 std::vector
<sync_pb::SyncEntity
> sync_entities
;
532 DCHECK(thread_checker_
.CalledOnValidThread());
533 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
535 FakeServerEntity
* entity
= it
->second
;
536 if (!IsDeletedOrFolder(entity
) && entity
->GetModelType() == model_type
) {
537 sync_pb::SyncEntity sync_entity
;
538 entity
->SerializeAsProto(&sync_entity
);
539 sync_entities
.push_back(sync_entity
);
542 return sync_entities
;
545 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
546 DCHECK(thread_checker_
.CalledOnValidThread());
547 SaveEntity(entity
.release());
550 bool FakeServer::SetNewStoreBirthday(const string
& store_birthday
) {
551 DCHECK(thread_checker_
.CalledOnValidThread());
552 if (store_birthday_
== store_birthday
)
555 store_birthday_
= store_birthday
;
559 void FakeServer::SetAuthenticated() {
560 DCHECK(thread_checker_
.CalledOnValidThread());
561 authenticated_
= true;
564 void FakeServer::SetUnauthenticated() {
565 DCHECK(thread_checker_
.CalledOnValidThread());
566 authenticated_
= false;
569 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
570 DCHECK(thread_checker_
.CalledOnValidThread());
571 if (triggered_actionable_error_
.get()) {
572 DVLOG(1) << "Only one type of error can be triggered at any given time.";
576 error_type_
= error_type
;
580 bool FakeServer::TriggerActionableError(
581 const sync_pb::SyncEnums::ErrorType
& error_type
,
582 const string
& description
,
584 const sync_pb::SyncEnums::Action
& action
) {
585 DCHECK(thread_checker_
.CalledOnValidThread());
586 if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
587 DVLOG(1) << "Only one type of error can be triggered at any given time.";
591 sync_pb::ClientToServerResponse_Error
* error
=
592 new sync_pb::ClientToServerResponse_Error();
593 error
->set_error_type(error_type
);
594 error
->set_error_description(description
);
596 error
->set_action(action
);
597 triggered_actionable_error_
.reset(error
);
601 bool FakeServer::EnableAlternatingTriggeredErrors() {
602 DCHECK(thread_checker_
.CalledOnValidThread());
603 if (error_type_
== sync_pb::SyncEnums::SUCCESS
&&
604 !triggered_actionable_error_
.get()) {
605 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
609 alternate_triggered_errors_
= true;
610 // Reset the counter so that the the first request yields a triggered error.
611 request_counter_
= 0;
615 bool FakeServer::ShouldSendTriggeredError() const {
616 if (!alternate_triggered_errors_
)
619 // Check that the counter is odd so that we trigger an error on the first
620 // request after alternating is enabled.
621 return request_counter_
% 2 != 0;
624 void FakeServer::AddObserver(Observer
* observer
) {
625 DCHECK(thread_checker_
.CalledOnValidThread());
626 observers_
.AddObserver(observer
);
629 void FakeServer::RemoveObserver(Observer
* observer
) {
630 DCHECK(thread_checker_
.CalledOnValidThread());
631 observers_
.RemoveObserver(observer
);
634 void FakeServer::EnableNetwork() {
635 DCHECK(thread_checker_
.CalledOnValidThread());
636 network_enabled_
= true;
639 void FakeServer::DisableNetwork() {
640 DCHECK(thread_checker_
.CalledOnValidThread());
641 network_enabled_
= false;
644 std::string
FakeServer::GetBookmarkBarFolderId() const {
645 DCHECK(thread_checker_
.CalledOnValidThread());
646 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
648 FakeServerEntity
* entity
= it
->second
;
649 if (entity
->GetName() == kBookmarkBarFolderName
&&
650 entity
->IsFolder() &&
651 entity
->GetModelType() == syncer::BOOKMARKS
) {
652 return entity
->GetId();
655 NOTREACHED() << "Bookmark Bar entity not found.";
659 base::WeakPtr
<FakeServer
> FakeServer::AsWeakPtr() {
660 DCHECK(thread_checker_
.CalledOnValidThread());
661 return weak_ptr_factory_
.GetWeakPtr();
664 } // namespace fake_server