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 // The default store birthday value.
38 static const char kDefaultStoreBirthday
[] = "1234567890";
40 // The default keystore key.
41 static const char kDefaultKeystoreKey
[] = "1111111111111111";
43 namespace fake_server
{
45 class FakeServerEntity
;
49 // A filter used during GetUpdates calls to determine what information to
50 // send back to the client. There is a 1:1 correspondence between any given
51 // GetUpdates call and an UpdateSieve instance.
56 // Factory method for creating an UpdateSieve.
57 static scoped_ptr
<UpdateSieve
> Create(
58 const sync_pb::GetUpdatesMessage
& get_updates_message
);
60 // Sets the progress markers in |get_updates_response| given the progress
61 // markers from the original GetUpdatesMessage and |new_version| (the latest
62 // version in the entries sent back).
63 void UpdateProgressMarkers(
65 sync_pb::GetUpdatesResponse
* get_updates_response
) const {
66 ModelTypeToVersionMap::const_iterator it
;
67 for (it
= request_from_version_
.begin(); it
!= request_from_version_
.end();
69 sync_pb::DataTypeProgressMarker
* new_marker
=
70 get_updates_response
->add_new_progress_marker();
71 new_marker
->set_data_type_id(
72 GetSpecificsFieldNumberFromModelType(it
->first
));
74 int64 version
= std::max(new_version
, it
->second
);
75 new_marker
->set_token(base::Int64ToString(version
));
79 // Determines whether the server should send an |entity| to the client as
80 // part of a GetUpdatesResponse.
81 bool ClientWantsItem(FakeServerEntity
* entity
) const {
82 int64 version
= entity
->GetVersion();
83 if (version
<= min_version_
) {
85 } else if (entity
->IsDeleted()) {
89 ModelTypeToVersionMap::const_iterator it
=
90 request_from_version_
.find(entity
->GetModelType());
92 return it
== request_from_version_
.end() ? false : it
->second
< version
;
95 // Returns the minimum version seen across all types.
96 int64
GetMinVersion() const {
101 typedef std::map
<ModelType
, int64
> ModelTypeToVersionMap
;
103 // Creates an UpdateSieve.
104 UpdateSieve(const ModelTypeToVersionMap request_from_version
,
105 const int64 min_version
)
106 : request_from_version_(request_from_version
),
107 min_version_(min_version
) { }
109 // Maps data type IDs to the latest version seen for that type.
110 const ModelTypeToVersionMap request_from_version_
;
112 // The minimum version seen among all data types.
113 const int min_version_
;
116 scoped_ptr
<UpdateSieve
> UpdateSieve::Create(
117 const sync_pb::GetUpdatesMessage
& get_updates_message
) {
118 CHECK_GT(get_updates_message
.from_progress_marker_size(), 0)
119 << "A GetUpdates request must have at least one progress marker.";
121 UpdateSieve::ModelTypeToVersionMap request_from_version
;
122 int64 min_version
= std::numeric_limits
<int64
>::max();
123 for (int i
= 0; i
< get_updates_message
.from_progress_marker_size(); i
++) {
124 sync_pb::DataTypeProgressMarker marker
=
125 get_updates_message
.from_progress_marker(i
);
128 // Let the version remain zero if there is no token or an empty token (the
129 // first request for this type).
130 if (marker
.has_token() && !marker
.token().empty()) {
131 bool parsed
= base::StringToInt64(marker
.token(), &version
);
132 CHECK(parsed
) << "Unable to parse progress marker token.";
135 ModelType model_type
= syncer::GetModelTypeFromSpecificsFieldNumber(
136 marker
.data_type_id());
137 request_from_version
[model_type
] = version
;
139 if (version
< min_version
)
140 min_version
= version
;
143 return scoped_ptr
<UpdateSieve
>(
144 new UpdateSieve(request_from_version
, min_version
));
149 FakeServer::FakeServer() : version_(0),
150 store_birthday_(kDefaultStoreBirthday
),
151 authenticated_(true),
152 error_type_(sync_pb::SyncEnums::SUCCESS
),
153 alternate_triggered_errors_(false),
155 network_enabled_(true) {
156 keystore_keys_
.push_back(kDefaultKeystoreKey
);
157 CHECK(CreateDefaultPermanentItems());
160 FakeServer::~FakeServer() {
161 STLDeleteContainerPairSecondPointers(entities_
.begin(), entities_
.end());
164 bool FakeServer::CreateDefaultPermanentItems() {
165 ModelTypeSet all_types
= syncer::ProtocolTypes();
166 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
167 ModelType model_type
= it
.Get();
168 FakeServerEntity
* top_level_entity
=
169 PermanentEntity::CreateTopLevel(model_type
);
170 if (top_level_entity
== NULL
) {
173 SaveEntity(top_level_entity
);
175 if (model_type
== syncer::BOOKMARKS
) {
176 FakeServerEntity
* bookmark_bar_entity
=
177 PermanentEntity::Create(syncer::BOOKMARKS
,
180 ModelTypeToRootTag(syncer::BOOKMARKS
));
181 if (bookmark_bar_entity
== NULL
) {
184 SaveEntity(bookmark_bar_entity
);
186 FakeServerEntity
* other_bookmarks_entity
=
187 PermanentEntity::Create(syncer::BOOKMARKS
,
190 ModelTypeToRootTag(syncer::BOOKMARKS
));
191 if (other_bookmarks_entity
== NULL
) {
194 SaveEntity(other_bookmarks_entity
);
201 bool FakeServer::CreateMobileBookmarksPermanentItem() {
202 // This folder is called "Synced Bookmarks" by sync and is renamed
203 // "Mobile Bookmarks" by the mobile client UIs.
204 FakeServerEntity
* mobile_bookmarks_entity
=
205 PermanentEntity::Create(syncer::BOOKMARKS
,
208 ModelTypeToRootTag(syncer::BOOKMARKS
));
209 if (mobile_bookmarks_entity
== NULL
) {
212 SaveEntity(mobile_bookmarks_entity
);
216 void FakeServer::SaveEntity(FakeServerEntity
* entity
) {
217 delete entities_
[entity
->GetId()];
218 entity
->SetVersion(++version_
);
219 entities_
[entity
->GetId()] = entity
;
222 void FakeServer::HandleCommand(const string
& request
,
223 const HandleCommandCallback
& callback
) {
224 if (!network_enabled_
) {
225 callback
.Run(net::ERR_FAILED
, net::ERR_FAILED
, string());
230 if (!authenticated_
) {
231 callback
.Run(0, net::HTTP_UNAUTHORIZED
, string());
235 sync_pb::ClientToServerMessage message
;
236 bool parsed
= message
.ParseFromString(request
);
237 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
239 sync_pb::ClientToServerResponse response_proto
;
241 if (message
.has_store_birthday() &&
242 message
.store_birthday() != store_birthday_
) {
243 response_proto
.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY
);
244 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
&&
245 ShouldSendTriggeredError()) {
246 response_proto
.set_error_code(error_type_
);
247 } else if (triggered_actionable_error_
.get() && ShouldSendTriggeredError()) {
248 sync_pb::ClientToServerResponse_Error
* error
=
249 response_proto
.mutable_error();
250 error
->CopyFrom(*(triggered_actionable_error_
.get()));
252 bool success
= false;
253 switch (message
.message_contents()) {
254 case sync_pb::ClientToServerMessage::GET_UPDATES
:
255 success
= HandleGetUpdatesRequest(message
.get_updates(),
256 response_proto
.mutable_get_updates());
258 case sync_pb::ClientToServerMessage::COMMIT
:
259 success
= HandleCommitRequest(message
.commit(),
260 message
.invalidator_client_id(),
261 response_proto
.mutable_commit());
264 callback
.Run(net::ERR_NOT_IMPLEMENTED
, 0, string());;
269 // TODO(pvalenzuela): Add logging here so that tests have more info about
271 callback
.Run(net::ERR_FAILED
, 0, string());
275 response_proto
.set_error_code(sync_pb::SyncEnums::SUCCESS
);
278 response_proto
.set_store_birthday(store_birthday_
);
279 callback
.Run(0, net::HTTP_OK
, response_proto
.SerializeAsString());
282 bool FakeServer::HandleGetUpdatesRequest(
283 const sync_pb::GetUpdatesMessage
& get_updates
,
284 sync_pb::GetUpdatesResponse
* response
) {
285 // TODO(pvalenzuela): Implement batching instead of sending all information
287 response
->set_changes_remaining(0);
289 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
291 if (get_updates
.create_mobile_bookmarks_folder() &&
292 !CreateMobileBookmarksPermanentItem()) {
296 bool send_encryption_keys_based_on_nigori
= false;
297 int64 max_response_version
= 0;
298 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
300 FakeServerEntity
* entity
= it
->second
;
301 if (sieve
->ClientWantsItem(entity
)) {
302 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
303 entity
->SerializeAsProto(response_entity
);
304 max_response_version
= std::max(max_response_version
,
305 response_entity
->version());
307 if (entity
->GetModelType() == syncer::NIGORI
) {
308 send_encryption_keys_based_on_nigori
=
309 response_entity
->specifics().nigori().passphrase_type() ==
310 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
315 if (send_encryption_keys_based_on_nigori
||
316 get_updates
.need_encryption_key()) {
317 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
318 it
!= keystore_keys_
.end(); ++it
) {
319 response
->add_encryption_keys(*it
);
323 sieve
->UpdateProgressMarkers(max_response_version
, response
);
327 string
FakeServer::CommitEntity(
328 const sync_pb::SyncEntity
& client_entity
,
329 sync_pb::CommitResponse_EntryResponse
* entry_response
,
332 if (client_entity
.version() == 0 && client_entity
.deleted()) {
336 FakeServerEntity
* entity
;
337 if (client_entity
.deleted()) {
338 entity
= TombstoneEntity::Create(client_entity
.id_string());
339 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
340 // not modify server data if it fails.
341 if (!DeleteChildren(client_entity
.id_string())) {
344 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
345 // NIGORI is the only permanent item type that should be updated by the
347 entity
= PermanentEntity::CreateUpdatedNigoriEntity(
349 entities_
[client_entity
.id_string()]);
350 } else if (client_entity
.has_client_defined_unique_tag()) {
351 entity
= UniqueClientEntity::Create(client_entity
);
353 // TODO(pvalenzuela): Validate entity's parent ID.
354 if (entities_
.find(client_entity
.id_string()) != entities_
.end()) {
355 entity
= BookmarkEntity::CreateUpdatedVersion(
357 entities_
[client_entity
.id_string()],
360 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
364 if (entity
== NULL
) {
365 // TODO(pvalenzuela): Add logging so that it is easier to determine why
371 BuildEntryResponseForSuccessfulCommit(entry_response
, entity
);
372 return entity
->GetId();
375 void FakeServer::BuildEntryResponseForSuccessfulCommit(
376 sync_pb::CommitResponse_EntryResponse
* entry_response
,
377 FakeServerEntity
* entity
) {
378 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
379 entry_response
->set_id_string(entity
->GetId());
381 if (entity
->IsDeleted()) {
382 entry_response
->set_version(entity
->GetVersion() + 1);
384 entry_response
->set_version(entity
->GetVersion());
385 entry_response
->set_name(entity
->GetName());
389 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
390 if (entities_
.find(id
) == entities_
.end()) {
391 // We've hit an ID (probably the imaginary root entity) that isn't stored
392 // by the server, so it can't be a child.
394 } else if (entities_
[id
]->GetParentId() == potential_parent_id
) {
397 // Recursively look up the tree.
398 return IsChild(entities_
[id
]->GetParentId(), potential_parent_id
);
402 bool FakeServer::DeleteChildren(const string
& id
) {
403 vector
<string
> child_ids
;
404 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
406 if (IsChild(it
->first
, id
)) {
407 child_ids
.push_back(it
->first
);
411 for (vector
<string
>::iterator it
= child_ids
.begin(); it
!= child_ids
.end();
413 FakeServerEntity
* tombstone
= TombstoneEntity::Create(*it
);
414 if (tombstone
== NULL
) {
415 LOG(WARNING
) << "Tombstone creation failed for entity with ID " << *it
;
418 SaveEntity(tombstone
);
424 bool FakeServer::HandleCommitRequest(
425 const sync_pb::CommitMessage
& commit
,
426 const std::string
& invalidator_client_id
,
427 sync_pb::CommitResponse
* response
) {
428 std::map
<string
, string
> client_to_server_ids
;
429 string guid
= commit
.cache_guid();
430 ModelTypeSet committed_model_types
;
432 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
433 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
434 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
435 sync_pb::CommitResponse_EntryResponse
* entry_response
=
436 response
->add_entryresponse();
438 sync_pb::SyncEntity client_entity
= *it
;
439 string parent_id
= client_entity
.parent_id_string();
440 if (client_to_server_ids
.find(parent_id
) !=
441 client_to_server_ids
.end()) {
442 parent_id
= client_to_server_ids
[parent_id
];
445 string entity_id
= CommitEntity(client_entity
,
449 if (entity_id
.empty()) {
453 // Record the ID if it was renamed.
454 if (entity_id
!= client_entity
.id_string()) {
455 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
457 FakeServerEntity
* entity
= entities_
[entity_id
];
458 committed_model_types
.Put(entity
->GetModelType());
461 FOR_EACH_OBSERVER(Observer
, observers_
,
462 OnCommit(invalidator_client_id
, committed_model_types
));
466 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
467 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
469 // Initialize an empty ListValue for all ModelTypes.
470 ModelTypeSet all_types
= ModelTypeSet::All();
471 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
472 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
475 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
477 FakeServerEntity
* entity
= it
->second
;
478 if (entity
->IsDeleted() || entity
->IsFolder()) {
479 // Tombstones are ignored as they don't represent current data. Folders
480 // are also ignored as current verification infrastructure does not
484 base::ListValue
* list_value
;
485 if (!dictionary
->GetList(ModelTypeToString(entity
->GetModelType()),
487 return scoped_ptr
<base::DictionaryValue
>();
489 // TODO(pvalenzuela): Store more data for each entity so additional
490 // verification can be performed. One example of additional verification
491 // is checking the correctness of the bookmark hierarchy.
492 list_value
->Append(new base::StringValue(entity
->GetName()));
495 return dictionary
.Pass();
498 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
499 SaveEntity(entity
.release());
502 bool FakeServer::SetNewStoreBirthday(const string
& store_birthday
) {
503 if (store_birthday_
== store_birthday
)
506 store_birthday_
= store_birthday
;
510 void FakeServer::SetAuthenticated() {
511 authenticated_
= true;
514 void FakeServer::SetUnauthenticated() {
515 authenticated_
= false;
518 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
519 if (triggered_actionable_error_
.get()) {
520 DVLOG(1) << "Only one type of error can be triggered at any given time.";
524 error_type_
= error_type
;
528 bool FakeServer::TriggerActionableError(
529 const sync_pb::SyncEnums::ErrorType
& error_type
,
530 const string
& description
,
532 const sync_pb::SyncEnums::Action
& action
) {
533 if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
534 DVLOG(1) << "Only one type of error can be triggered at any given time.";
538 sync_pb::ClientToServerResponse_Error
* error
=
539 new sync_pb::ClientToServerResponse_Error();
540 error
->set_error_type(error_type
);
541 error
->set_error_description(description
);
543 error
->set_action(action
);
544 triggered_actionable_error_
.reset(error
);
548 bool FakeServer::EnableAlternatingTriggeredErrors() {
549 if (error_type_
== sync_pb::SyncEnums::SUCCESS
&&
550 !triggered_actionable_error_
.get()) {
551 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
555 alternate_triggered_errors_
= true;
556 // Reset the counter so that the the first request yields a triggered error.
557 request_counter_
= 0;
561 bool FakeServer::ShouldSendTriggeredError() const {
562 if (!alternate_triggered_errors_
)
565 // Check that the counter is odd so that we trigger an error on the first
566 // request after alternating is enabled.
567 return request_counter_
% 2 != 0;
570 void FakeServer::AddObserver(Observer
* observer
) {
571 observers_
.AddObserver(observer
);
574 void FakeServer::RemoveObserver(Observer
* observer
) {
575 observers_
.RemoveObserver(observer
);
578 void FakeServer::EnableNetwork() {
579 network_enabled_
= true;
582 void FakeServer::DisableNetwork() {
583 network_enabled_
= false;
586 } // namespace fake_server