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 keystore_keys_
.push_back(kDefaultKeystoreKey
);
175 CHECK(CreateDefaultPermanentItems());
178 FakeServer::~FakeServer() {
179 STLDeleteContainerPairSecondPointers(entities_
.begin(), entities_
.end());
182 bool FakeServer::CreatePermanentBookmarkFolder(const std::string
& server_tag
,
183 const std::string
& name
) {
184 FakeServerEntity
* entity
=
185 PermanentEntity::Create(syncer::BOOKMARKS
, server_tag
, name
,
186 ModelTypeToRootTag(syncer::BOOKMARKS
));
194 bool FakeServer::CreateDefaultPermanentItems() {
195 ModelTypeSet all_types
= syncer::ProtocolTypes();
196 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
197 ModelType model_type
= it
.Get();
198 FakeServerEntity
* top_level_entity
=
199 PermanentEntity::CreateTopLevel(model_type
);
200 if (top_level_entity
== NULL
) {
203 SaveEntity(top_level_entity
);
205 if (model_type
== syncer::BOOKMARKS
) {
206 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag
,
207 kBookmarkBarFolderName
))
209 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag
,
210 kOtherBookmarksFolderName
))
218 void FakeServer::SaveEntity(FakeServerEntity
* entity
) {
219 delete entities_
[entity
->GetId()];
220 entity
->SetVersion(++version_
);
221 entities_
[entity
->GetId()] = entity
;
224 void FakeServer::HandleCommand(const string
& request
,
225 const HandleCommandCallback
& callback
) {
226 if (!network_enabled_
) {
227 callback
.Run(net::ERR_FAILED
, net::ERR_FAILED
, string());
232 if (!authenticated_
) {
233 callback
.Run(0, net::HTTP_UNAUTHORIZED
, string());
237 sync_pb::ClientToServerMessage message
;
238 bool parsed
= message
.ParseFromString(request
);
239 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
241 sync_pb::ClientToServerResponse response_proto
;
243 if (message
.has_store_birthday() &&
244 message
.store_birthday() != store_birthday_
) {
245 response_proto
.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY
);
246 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
&&
247 ShouldSendTriggeredError()) {
248 response_proto
.set_error_code(error_type_
);
249 } else if (triggered_actionable_error_
.get() && ShouldSendTriggeredError()) {
250 sync_pb::ClientToServerResponse_Error
* error
=
251 response_proto
.mutable_error();
252 error
->CopyFrom(*(triggered_actionable_error_
.get()));
254 bool success
= false;
255 switch (message
.message_contents()) {
256 case sync_pb::ClientToServerMessage::GET_UPDATES
:
257 success
= HandleGetUpdatesRequest(message
.get_updates(),
258 response_proto
.mutable_get_updates());
260 case sync_pb::ClientToServerMessage::COMMIT
:
261 success
= HandleCommitRequest(message
.commit(),
262 message
.invalidator_client_id(),
263 response_proto
.mutable_commit());
266 callback
.Run(net::ERR_NOT_IMPLEMENTED
, 0, string());;
271 // TODO(pvalenzuela): Add logging here so that tests have more info about
273 callback
.Run(net::ERR_FAILED
, 0, string());
277 response_proto
.set_error_code(sync_pb::SyncEnums::SUCCESS
);
280 response_proto
.set_store_birthday(store_birthday_
);
281 callback
.Run(0, net::HTTP_OK
, response_proto
.SerializeAsString());
284 bool FakeServer::HandleGetUpdatesRequest(
285 const sync_pb::GetUpdatesMessage
& get_updates
,
286 sync_pb::GetUpdatesResponse
* response
) {
287 // TODO(pvalenzuela): Implement batching instead of sending all information
289 response
->set_changes_remaining(0);
291 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
293 // This folder is called "Synced Bookmarks" by sync and is renamed
294 // "Mobile Bookmarks" by the mobile client UIs.
295 if (get_updates
.create_mobile_bookmarks_folder() &&
296 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag
,
297 kSyncedBookmarksFolderName
)) {
301 bool send_encryption_keys_based_on_nigori
= false;
302 int64 max_response_version
= 0;
303 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
305 FakeServerEntity
* entity
= it
->second
;
306 if (sieve
->ClientWantsItem(entity
)) {
307 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
308 entity
->SerializeAsProto(response_entity
);
309 max_response_version
= std::max(max_response_version
,
310 response_entity
->version());
312 if (entity
->GetModelType() == syncer::NIGORI
) {
313 send_encryption_keys_based_on_nigori
=
314 response_entity
->specifics().nigori().passphrase_type() ==
315 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
320 if (send_encryption_keys_based_on_nigori
||
321 get_updates
.need_encryption_key()) {
322 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
323 it
!= keystore_keys_
.end(); ++it
) {
324 response
->add_encryption_keys(*it
);
328 sieve
->UpdateProgressMarkers(max_response_version
, response
);
332 string
FakeServer::CommitEntity(
333 const sync_pb::SyncEntity
& client_entity
,
334 sync_pb::CommitResponse_EntryResponse
* entry_response
,
337 if (client_entity
.version() == 0 && client_entity
.deleted()) {
341 FakeServerEntity
* entity
;
342 if (client_entity
.deleted()) {
343 entity
= TombstoneEntity::Create(client_entity
.id_string());
344 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
345 // not modify server data if it fails.
346 if (!DeleteChildren(client_entity
.id_string())) {
349 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
350 // NIGORI is the only permanent item type that should be updated by the
352 entity
= PermanentEntity::CreateUpdatedNigoriEntity(
354 entities_
[client_entity
.id_string()]);
355 } else if (client_entity
.has_client_defined_unique_tag()) {
356 entity
= UniqueClientEntity::Create(client_entity
);
358 // TODO(pvalenzuela): Validate entity's parent ID.
359 if (entities_
.find(client_entity
.id_string()) != entities_
.end()) {
360 entity
= BookmarkEntity::CreateUpdatedVersion(
362 entities_
[client_entity
.id_string()],
365 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
369 if (entity
== NULL
) {
370 // TODO(pvalenzuela): Add logging so that it is easier to determine why
376 BuildEntryResponseForSuccessfulCommit(entry_response
, entity
);
377 return entity
->GetId();
380 void FakeServer::BuildEntryResponseForSuccessfulCommit(
381 sync_pb::CommitResponse_EntryResponse
* entry_response
,
382 FakeServerEntity
* entity
) {
383 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
384 entry_response
->set_id_string(entity
->GetId());
386 if (entity
->IsDeleted()) {
387 entry_response
->set_version(entity
->GetVersion() + 1);
389 entry_response
->set_version(entity
->GetVersion());
390 entry_response
->set_name(entity
->GetName());
394 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
395 if (entities_
.find(id
) == entities_
.end()) {
396 // We've hit an ID (probably the imaginary root entity) that isn't stored
397 // by the server, so it can't be a child.
399 } else if (entities_
[id
]->GetParentId() == potential_parent_id
) {
402 // Recursively look up the tree.
403 return IsChild(entities_
[id
]->GetParentId(), potential_parent_id
);
407 bool FakeServer::DeleteChildren(const string
& id
) {
408 vector
<string
> child_ids
;
409 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
411 if (IsChild(it
->first
, id
)) {
412 child_ids
.push_back(it
->first
);
416 for (vector
<string
>::iterator it
= child_ids
.begin(); it
!= child_ids
.end();
418 FakeServerEntity
* tombstone
= TombstoneEntity::Create(*it
);
419 if (tombstone
== NULL
) {
420 LOG(WARNING
) << "Tombstone creation failed for entity with ID " << *it
;
423 SaveEntity(tombstone
);
429 bool FakeServer::HandleCommitRequest(
430 const sync_pb::CommitMessage
& commit
,
431 const std::string
& invalidator_client_id
,
432 sync_pb::CommitResponse
* response
) {
433 std::map
<string
, string
> client_to_server_ids
;
434 string guid
= commit
.cache_guid();
435 ModelTypeSet committed_model_types
;
437 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
438 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
439 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
440 sync_pb::CommitResponse_EntryResponse
* entry_response
=
441 response
->add_entryresponse();
443 sync_pb::SyncEntity client_entity
= *it
;
444 string parent_id
= client_entity
.parent_id_string();
445 if (client_to_server_ids
.find(parent_id
) !=
446 client_to_server_ids
.end()) {
447 parent_id
= client_to_server_ids
[parent_id
];
450 string entity_id
= CommitEntity(client_entity
,
454 if (entity_id
.empty()) {
458 // Record the ID if it was renamed.
459 if (entity_id
!= client_entity
.id_string()) {
460 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
462 FakeServerEntity
* entity
= entities_
[entity_id
];
463 committed_model_types
.Put(entity
->GetModelType());
466 FOR_EACH_OBSERVER(Observer
, observers_
,
467 OnCommit(invalidator_client_id
, committed_model_types
));
471 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
472 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
474 // Initialize an empty ListValue for all ModelTypes.
475 ModelTypeSet all_types
= ModelTypeSet::All();
476 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
477 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
480 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
482 FakeServerEntity
* entity
= it
->second
;
483 if (IsDeletedOrFolder(entity
)) {
484 // Tombstones are ignored as they don't represent current data. Folders
485 // are also ignored as current verification infrastructure does not
489 base::ListValue
* list_value
;
490 if (!dictionary
->GetList(ModelTypeToString(entity
->GetModelType()),
492 return scoped_ptr
<base::DictionaryValue
>();
494 // TODO(pvalenzuela): Store more data for each entity so additional
495 // verification can be performed. One example of additional verification
496 // is checking the correctness of the bookmark hierarchy.
497 list_value
->Append(new base::StringValue(entity
->GetName()));
500 return dictionary
.Pass();
503 std::vector
<sync_pb::SyncEntity
> FakeServer::GetSyncEntitiesByModelType(
504 ModelType model_type
) {
505 std::vector
<sync_pb::SyncEntity
> sync_entities
;
506 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
508 FakeServerEntity
* entity
= it
->second
;
509 if (!IsDeletedOrFolder(entity
) && entity
->GetModelType() == model_type
) {
510 sync_pb::SyncEntity sync_entity
;
511 entity
->SerializeAsProto(&sync_entity
);
512 sync_entities
.push_back(sync_entity
);
515 return sync_entities
;
518 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
519 SaveEntity(entity
.release());
522 bool FakeServer::SetNewStoreBirthday(const string
& store_birthday
) {
523 if (store_birthday_
== store_birthday
)
526 store_birthday_
= store_birthday
;
530 void FakeServer::SetAuthenticated() {
531 authenticated_
= true;
534 void FakeServer::SetUnauthenticated() {
535 authenticated_
= false;
538 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
539 if (triggered_actionable_error_
.get()) {
540 DVLOG(1) << "Only one type of error can be triggered at any given time.";
544 error_type_
= error_type
;
548 bool FakeServer::TriggerActionableError(
549 const sync_pb::SyncEnums::ErrorType
& error_type
,
550 const string
& description
,
552 const sync_pb::SyncEnums::Action
& action
) {
553 if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
554 DVLOG(1) << "Only one type of error can be triggered at any given time.";
558 sync_pb::ClientToServerResponse_Error
* error
=
559 new sync_pb::ClientToServerResponse_Error();
560 error
->set_error_type(error_type
);
561 error
->set_error_description(description
);
563 error
->set_action(action
);
564 triggered_actionable_error_
.reset(error
);
568 bool FakeServer::EnableAlternatingTriggeredErrors() {
569 if (error_type_
== sync_pb::SyncEnums::SUCCESS
&&
570 !triggered_actionable_error_
.get()) {
571 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
575 alternate_triggered_errors_
= true;
576 // Reset the counter so that the the first request yields a triggered error.
577 request_counter_
= 0;
581 bool FakeServer::ShouldSendTriggeredError() const {
582 if (!alternate_triggered_errors_
)
585 // Check that the counter is odd so that we trigger an error on the first
586 // request after alternating is enabled.
587 return request_counter_
% 2 != 0;
590 void FakeServer::AddObserver(Observer
* observer
) {
591 observers_
.AddObserver(observer
);
594 void FakeServer::RemoveObserver(Observer
* observer
) {
595 observers_
.RemoveObserver(observer
);
598 void FakeServer::EnableNetwork() {
599 network_enabled_
= true;
602 void FakeServer::DisableNetwork() {
603 network_enabled_
= false;
606 std::string
FakeServer::GetBookmarkBarFolderId() const {
607 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
609 FakeServerEntity
* entity
= it
->second
;
610 if (entity
->GetName() == kBookmarkBarFolderName
&&
611 entity
->IsFolder() &&
612 entity
->GetModelType() == syncer::BOOKMARKS
) {
613 return entity
->GetId();
616 NOTREACHED() << "Bookmark Bar entity not found.";
620 } // namespace fake_server