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::CreatePermanentBookmarkFolder(const char* server_tag
,
166 FakeServerEntity
* entity
=
167 PermanentEntity::Create(syncer::BOOKMARKS
, server_tag
, name
,
168 ModelTypeToRootTag(syncer::BOOKMARKS
));
176 bool FakeServer::CreateDefaultPermanentItems() {
177 ModelTypeSet all_types
= syncer::ProtocolTypes();
178 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
179 ModelType model_type
= it
.Get();
180 FakeServerEntity
* top_level_entity
=
181 PermanentEntity::CreateTopLevel(model_type
);
182 if (top_level_entity
== NULL
) {
185 SaveEntity(top_level_entity
);
187 if (model_type
== syncer::BOOKMARKS
) {
188 if (!CreatePermanentBookmarkFolder("bookmark_bar", "Bookmark Bar"))
190 if (!CreatePermanentBookmarkFolder("other_bookmarks", "Other Bookmarks"))
198 bool FakeServer::CreateMobileBookmarksPermanentItem() {
199 // This folder is called "Synced Bookmarks" by sync and is renamed
200 // "Mobile Bookmarks" by the mobile client UIs.
201 FakeServerEntity
* mobile_bookmarks_entity
=
202 PermanentEntity::Create(syncer::BOOKMARKS
,
205 ModelTypeToRootTag(syncer::BOOKMARKS
));
206 if (mobile_bookmarks_entity
== NULL
) {
209 SaveEntity(mobile_bookmarks_entity
);
213 void FakeServer::SaveEntity(FakeServerEntity
* entity
) {
214 delete entities_
[entity
->GetId()];
215 entity
->SetVersion(++version_
);
216 entities_
[entity
->GetId()] = entity
;
219 void FakeServer::HandleCommand(const string
& request
,
220 const HandleCommandCallback
& callback
) {
221 if (!network_enabled_
) {
222 callback
.Run(net::ERR_FAILED
, net::ERR_FAILED
, string());
227 if (!authenticated_
) {
228 callback
.Run(0, net::HTTP_UNAUTHORIZED
, string());
232 sync_pb::ClientToServerMessage message
;
233 bool parsed
= message
.ParseFromString(request
);
234 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
236 sync_pb::ClientToServerResponse response_proto
;
238 if (message
.has_store_birthday() &&
239 message
.store_birthday() != store_birthday_
) {
240 response_proto
.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY
);
241 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
&&
242 ShouldSendTriggeredError()) {
243 response_proto
.set_error_code(error_type_
);
244 } else if (triggered_actionable_error_
.get() && ShouldSendTriggeredError()) {
245 sync_pb::ClientToServerResponse_Error
* error
=
246 response_proto
.mutable_error();
247 error
->CopyFrom(*(triggered_actionable_error_
.get()));
249 bool success
= false;
250 switch (message
.message_contents()) {
251 case sync_pb::ClientToServerMessage::GET_UPDATES
:
252 success
= HandleGetUpdatesRequest(message
.get_updates(),
253 response_proto
.mutable_get_updates());
255 case sync_pb::ClientToServerMessage::COMMIT
:
256 success
= HandleCommitRequest(message
.commit(),
257 message
.invalidator_client_id(),
258 response_proto
.mutable_commit());
261 callback
.Run(net::ERR_NOT_IMPLEMENTED
, 0, string());;
266 // TODO(pvalenzuela): Add logging here so that tests have more info about
268 callback
.Run(net::ERR_FAILED
, 0, string());
272 response_proto
.set_error_code(sync_pb::SyncEnums::SUCCESS
);
275 response_proto
.set_store_birthday(store_birthday_
);
276 callback
.Run(0, net::HTTP_OK
, response_proto
.SerializeAsString());
279 bool FakeServer::HandleGetUpdatesRequest(
280 const sync_pb::GetUpdatesMessage
& get_updates
,
281 sync_pb::GetUpdatesResponse
* response
) {
282 // TODO(pvalenzuela): Implement batching instead of sending all information
284 response
->set_changes_remaining(0);
286 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
288 if (get_updates
.create_mobile_bookmarks_folder() &&
289 !CreateMobileBookmarksPermanentItem()) {
293 bool send_encryption_keys_based_on_nigori
= false;
294 int64 max_response_version
= 0;
295 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
297 FakeServerEntity
* entity
= it
->second
;
298 if (sieve
->ClientWantsItem(entity
)) {
299 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
300 entity
->SerializeAsProto(response_entity
);
301 max_response_version
= std::max(max_response_version
,
302 response_entity
->version());
304 if (entity
->GetModelType() == syncer::NIGORI
) {
305 send_encryption_keys_based_on_nigori
=
306 response_entity
->specifics().nigori().passphrase_type() ==
307 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
312 if (send_encryption_keys_based_on_nigori
||
313 get_updates
.need_encryption_key()) {
314 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
315 it
!= keystore_keys_
.end(); ++it
) {
316 response
->add_encryption_keys(*it
);
320 sieve
->UpdateProgressMarkers(max_response_version
, response
);
324 string
FakeServer::CommitEntity(
325 const sync_pb::SyncEntity
& client_entity
,
326 sync_pb::CommitResponse_EntryResponse
* entry_response
,
329 if (client_entity
.version() == 0 && client_entity
.deleted()) {
333 FakeServerEntity
* entity
;
334 if (client_entity
.deleted()) {
335 entity
= TombstoneEntity::Create(client_entity
.id_string());
336 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
337 // not modify server data if it fails.
338 if (!DeleteChildren(client_entity
.id_string())) {
341 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
342 // NIGORI is the only permanent item type that should be updated by the
344 entity
= PermanentEntity::CreateUpdatedNigoriEntity(
346 entities_
[client_entity
.id_string()]);
347 } else if (client_entity
.has_client_defined_unique_tag()) {
348 entity
= UniqueClientEntity::Create(client_entity
);
350 // TODO(pvalenzuela): Validate entity's parent ID.
351 if (entities_
.find(client_entity
.id_string()) != entities_
.end()) {
352 entity
= BookmarkEntity::CreateUpdatedVersion(
354 entities_
[client_entity
.id_string()],
357 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
361 if (entity
== NULL
) {
362 // TODO(pvalenzuela): Add logging so that it is easier to determine why
368 BuildEntryResponseForSuccessfulCommit(entry_response
, entity
);
369 return entity
->GetId();
372 void FakeServer::BuildEntryResponseForSuccessfulCommit(
373 sync_pb::CommitResponse_EntryResponse
* entry_response
,
374 FakeServerEntity
* entity
) {
375 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
376 entry_response
->set_id_string(entity
->GetId());
378 if (entity
->IsDeleted()) {
379 entry_response
->set_version(entity
->GetVersion() + 1);
381 entry_response
->set_version(entity
->GetVersion());
382 entry_response
->set_name(entity
->GetName());
386 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
387 if (entities_
.find(id
) == entities_
.end()) {
388 // We've hit an ID (probably the imaginary root entity) that isn't stored
389 // by the server, so it can't be a child.
391 } else if (entities_
[id
]->GetParentId() == potential_parent_id
) {
394 // Recursively look up the tree.
395 return IsChild(entities_
[id
]->GetParentId(), potential_parent_id
);
399 bool FakeServer::DeleteChildren(const string
& id
) {
400 vector
<string
> child_ids
;
401 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
403 if (IsChild(it
->first
, id
)) {
404 child_ids
.push_back(it
->first
);
408 for (vector
<string
>::iterator it
= child_ids
.begin(); it
!= child_ids
.end();
410 FakeServerEntity
* tombstone
= TombstoneEntity::Create(*it
);
411 if (tombstone
== NULL
) {
412 LOG(WARNING
) << "Tombstone creation failed for entity with ID " << *it
;
415 SaveEntity(tombstone
);
421 bool FakeServer::HandleCommitRequest(
422 const sync_pb::CommitMessage
& commit
,
423 const std::string
& invalidator_client_id
,
424 sync_pb::CommitResponse
* response
) {
425 std::map
<string
, string
> client_to_server_ids
;
426 string guid
= commit
.cache_guid();
427 ModelTypeSet committed_model_types
;
429 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
430 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
431 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
432 sync_pb::CommitResponse_EntryResponse
* entry_response
=
433 response
->add_entryresponse();
435 sync_pb::SyncEntity client_entity
= *it
;
436 string parent_id
= client_entity
.parent_id_string();
437 if (client_to_server_ids
.find(parent_id
) !=
438 client_to_server_ids
.end()) {
439 parent_id
= client_to_server_ids
[parent_id
];
442 string entity_id
= CommitEntity(client_entity
,
446 if (entity_id
.empty()) {
450 // Record the ID if it was renamed.
451 if (entity_id
!= client_entity
.id_string()) {
452 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
454 FakeServerEntity
* entity
= entities_
[entity_id
];
455 committed_model_types
.Put(entity
->GetModelType());
458 FOR_EACH_OBSERVER(Observer
, observers_
,
459 OnCommit(invalidator_client_id
, committed_model_types
));
463 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
464 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
466 // Initialize an empty ListValue for all ModelTypes.
467 ModelTypeSet all_types
= ModelTypeSet::All();
468 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
469 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
472 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
474 FakeServerEntity
* entity
= it
->second
;
475 if (entity
->IsDeleted() || entity
->IsFolder()) {
476 // Tombstones are ignored as they don't represent current data. Folders
477 // are also ignored as current verification infrastructure does not
481 base::ListValue
* list_value
;
482 if (!dictionary
->GetList(ModelTypeToString(entity
->GetModelType()),
484 return scoped_ptr
<base::DictionaryValue
>();
486 // TODO(pvalenzuela): Store more data for each entity so additional
487 // verification can be performed. One example of additional verification
488 // is checking the correctness of the bookmark hierarchy.
489 list_value
->Append(new base::StringValue(entity
->GetName()));
492 return dictionary
.Pass();
495 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
496 SaveEntity(entity
.release());
499 bool FakeServer::SetNewStoreBirthday(const string
& store_birthday
) {
500 if (store_birthday_
== store_birthday
)
503 store_birthday_
= store_birthday
;
507 void FakeServer::SetAuthenticated() {
508 authenticated_
= true;
511 void FakeServer::SetUnauthenticated() {
512 authenticated_
= false;
515 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
516 if (triggered_actionable_error_
.get()) {
517 DVLOG(1) << "Only one type of error can be triggered at any given time.";
521 error_type_
= error_type
;
525 bool FakeServer::TriggerActionableError(
526 const sync_pb::SyncEnums::ErrorType
& error_type
,
527 const string
& description
,
529 const sync_pb::SyncEnums::Action
& action
) {
530 if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
531 DVLOG(1) << "Only one type of error can be triggered at any given time.";
535 sync_pb::ClientToServerResponse_Error
* error
=
536 new sync_pb::ClientToServerResponse_Error();
537 error
->set_error_type(error_type
);
538 error
->set_error_description(description
);
540 error
->set_action(action
);
541 triggered_actionable_error_
.reset(error
);
545 bool FakeServer::EnableAlternatingTriggeredErrors() {
546 if (error_type_
== sync_pb::SyncEnums::SUCCESS
&&
547 !triggered_actionable_error_
.get()) {
548 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
552 alternate_triggered_errors_
= true;
553 // Reset the counter so that the the first request yields a triggered error.
554 request_counter_
= 0;
558 bool FakeServer::ShouldSendTriggeredError() const {
559 if (!alternate_triggered_errors_
)
562 // Check that the counter is odd so that we trigger an error on the first
563 // request after alternating is enabled.
564 return request_counter_
% 2 != 0;
567 void FakeServer::AddObserver(Observer
* observer
) {
568 observers_
.AddObserver(observer
);
571 void FakeServer::RemoveObserver(Observer
* observer
) {
572 observers_
.RemoveObserver(observer
);
575 void FakeServer::EnableNetwork() {
576 network_enabled_
= true;
579 void FakeServer::DisableNetwork() {
580 network_enabled_
= false;
583 } // namespace fake_server