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 keystore_keys_
.push_back(kDefaultKeystoreKey
);
154 CHECK(CreateDefaultPermanentItems());
157 FakeServer::~FakeServer() {
158 STLDeleteContainerPairSecondPointers(entities_
.begin(), entities_
.end());
161 bool FakeServer::CreateDefaultPermanentItems() {
162 ModelTypeSet all_types
= syncer::ProtocolTypes();
163 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
164 ModelType model_type
= it
.Get();
165 FakeServerEntity
* top_level_entity
=
166 PermanentEntity::CreateTopLevel(model_type
);
167 if (top_level_entity
== NULL
) {
170 SaveEntity(top_level_entity
);
172 if (model_type
== syncer::BOOKMARKS
) {
173 FakeServerEntity
* bookmark_bar_entity
=
174 PermanentEntity::Create(syncer::BOOKMARKS
,
177 ModelTypeToRootTag(syncer::BOOKMARKS
));
178 if (bookmark_bar_entity
== NULL
) {
181 SaveEntity(bookmark_bar_entity
);
183 FakeServerEntity
* other_bookmarks_entity
=
184 PermanentEntity::Create(syncer::BOOKMARKS
,
187 ModelTypeToRootTag(syncer::BOOKMARKS
));
188 if (other_bookmarks_entity
== NULL
) {
191 SaveEntity(other_bookmarks_entity
);
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 (!authenticated_
) {
222 callback
.Run(0, net::HTTP_UNAUTHORIZED
, string());
226 sync_pb::ClientToServerMessage message
;
227 bool parsed
= message
.ParseFromString(request
);
228 CHECK(parsed
) << "Unable to parse the ClientToServerMessage.";
230 sync_pb::SyncEnums_ErrorType error_code
;
231 sync_pb::ClientToServerResponse response_proto
;
233 if (message
.has_store_birthday() &&
234 message
.store_birthday() != store_birthday_
) {
235 error_code
= sync_pb::SyncEnums::NOT_MY_BIRTHDAY
;
236 } else if (error_type_
!= sync_pb::SyncEnums::SUCCESS
) {
237 error_code
= error_type_
;
239 bool success
= false;
240 switch (message
.message_contents()) {
241 case sync_pb::ClientToServerMessage::GET_UPDATES
:
242 success
= HandleGetUpdatesRequest(message
.get_updates(),
243 response_proto
.mutable_get_updates());
245 case sync_pb::ClientToServerMessage::COMMIT
:
246 success
= HandleCommitRequest(message
.commit(),
247 message
.invalidator_client_id(),
248 response_proto
.mutable_commit());
251 callback
.Run(net::ERR_NOT_IMPLEMENTED
, 0, string());;
256 // TODO(pvalenzuela): Add logging here so that tests have more info about
258 callback
.Run(net::ERR_FAILED
, 0, string());
262 error_code
= sync_pb::SyncEnums::SUCCESS
;
265 response_proto
.set_error_code(error_code
);
266 response_proto
.set_store_birthday(store_birthday_
);
267 callback
.Run(0, net::HTTP_OK
, response_proto
.SerializeAsString());
270 bool FakeServer::HandleGetUpdatesRequest(
271 const sync_pb::GetUpdatesMessage
& get_updates
,
272 sync_pb::GetUpdatesResponse
* response
) {
273 // TODO(pvalenzuela): Implement batching instead of sending all information
275 response
->set_changes_remaining(0);
277 scoped_ptr
<UpdateSieve
> sieve
= UpdateSieve::Create(get_updates
);
279 if (get_updates
.create_mobile_bookmarks_folder() &&
280 !CreateMobileBookmarksPermanentItem()) {
284 bool send_encryption_keys_based_on_nigori
= false;
285 int64 max_response_version
= 0;
286 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
288 FakeServerEntity
* entity
= it
->second
;
289 if (sieve
->ClientWantsItem(entity
)) {
290 sync_pb::SyncEntity
* response_entity
= response
->add_entries();
291 response_entity
->CopyFrom(*(entity
->SerializeAsProto()));
292 max_response_version
= std::max(max_response_version
,
293 response_entity
->version());
295 if (entity
->GetModelType() == syncer::NIGORI
) {
296 send_encryption_keys_based_on_nigori
=
297 response_entity
->specifics().nigori().passphrase_type() ==
298 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE
;
303 if (send_encryption_keys_based_on_nigori
||
304 get_updates
.need_encryption_key()) {
305 for (vector
<string
>::iterator it
= keystore_keys_
.begin();
306 it
!= keystore_keys_
.end(); ++it
) {
307 response
->add_encryption_keys(*it
);
311 sieve
->UpdateProgressMarkers(max_response_version
, response
);
315 string
FakeServer::CommitEntity(
316 const sync_pb::SyncEntity
& client_entity
,
317 sync_pb::CommitResponse_EntryResponse
* entry_response
,
320 if (client_entity
.version() == 0 && client_entity
.deleted()) {
324 FakeServerEntity
* entity
;
325 if (client_entity
.deleted()) {
326 entity
= TombstoneEntity::Create(client_entity
.id_string());
327 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
328 // not modify server data if it fails.
329 if (!DeleteChildren(client_entity
.id_string())) {
332 } else if (GetModelType(client_entity
) == syncer::NIGORI
) {
333 // NIGORI is the only permanent item type that should be updated by the
335 entity
= PermanentEntity::CreateUpdatedNigoriEntity(
337 entities_
[client_entity
.id_string()]);
338 } else if (client_entity
.has_client_defined_unique_tag()) {
339 entity
= UniqueClientEntity::Create(client_entity
);
341 // TODO(pvalenzuela): Validate entity's parent ID.
342 if (entities_
.find(client_entity
.id_string()) != entities_
.end()) {
343 entity
= BookmarkEntity::CreateUpdatedVersion(
345 entities_
[client_entity
.id_string()],
348 entity
= BookmarkEntity::CreateNew(client_entity
, parent_id
, client_guid
);
352 if (entity
== NULL
) {
353 // TODO(pvalenzuela): Add logging so that it is easier to determine why
359 BuildEntryResponseForSuccessfulCommit(entry_response
, entity
);
360 return entity
->GetId();
363 void FakeServer::BuildEntryResponseForSuccessfulCommit(
364 sync_pb::CommitResponse_EntryResponse
* entry_response
,
365 FakeServerEntity
* entity
) {
366 entry_response
->set_response_type(sync_pb::CommitResponse::SUCCESS
);
367 entry_response
->set_id_string(entity
->GetId());
369 if (entity
->IsDeleted()) {
370 entry_response
->set_version(entity
->GetVersion() + 1);
372 entry_response
->set_version(entity
->GetVersion());
373 entry_response
->set_name(entity
->GetName());
377 bool FakeServer::IsChild(const string
& id
, const string
& potential_parent_id
) {
378 if (entities_
.find(id
) == entities_
.end()) {
379 // We've hit an ID (probably the imaginary root entity) that isn't stored
380 // by the server, so it can't be a child.
382 } else if (entities_
[id
]->GetParentId() == potential_parent_id
) {
385 // Recursively look up the tree.
386 return IsChild(entities_
[id
]->GetParentId(), potential_parent_id
);
390 bool FakeServer::DeleteChildren(const string
& id
) {
391 vector
<string
> child_ids
;
392 for (EntityMap::iterator it
= entities_
.begin(); it
!= entities_
.end();
394 if (IsChild(it
->first
, id
)) {
395 child_ids
.push_back(it
->first
);
399 for (vector
<string
>::iterator it
= child_ids
.begin(); it
!= child_ids
.end();
401 FakeServerEntity
* tombstone
= TombstoneEntity::Create(*it
);
402 if (tombstone
== NULL
) {
403 LOG(WARNING
) << "Tombstone creation failed for entity with ID " << *it
;
406 SaveEntity(tombstone
);
412 bool FakeServer::HandleCommitRequest(
413 const sync_pb::CommitMessage
& commit
,
414 const std::string
& invalidator_client_id
,
415 sync_pb::CommitResponse
* response
) {
416 std::map
<string
, string
> client_to_server_ids
;
417 string guid
= commit
.cache_guid();
418 ModelTypeSet committed_model_types
;
420 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
421 ::google::protobuf::RepeatedPtrField
<sync_pb::SyncEntity
>::const_iterator it
;
422 for (it
= commit
.entries().begin(); it
!= commit
.entries().end(); ++it
) {
423 sync_pb::CommitResponse_EntryResponse
* entry_response
=
424 response
->add_entryresponse();
426 sync_pb::SyncEntity client_entity
= *it
;
427 string parent_id
= client_entity
.parent_id_string();
428 if (client_to_server_ids
.find(parent_id
) !=
429 client_to_server_ids
.end()) {
430 parent_id
= client_to_server_ids
[parent_id
];
433 string entity_id
= CommitEntity(client_entity
,
437 if (entity_id
.empty()) {
441 // Record the ID if it was renamed.
442 if (entity_id
!= client_entity
.id_string()) {
443 client_to_server_ids
[client_entity
.id_string()] = entity_id
;
445 FakeServerEntity
* entity
= entities_
[entity_id
];
446 committed_model_types
.Put(entity
->GetModelType());
449 FOR_EACH_OBSERVER(Observer
, observers_
,
450 OnCommit(invalidator_client_id
, committed_model_types
));
454 scoped_ptr
<base::DictionaryValue
> FakeServer::GetEntitiesAsDictionaryValue() {
455 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
457 // Initialize an empty ListValue for all ModelTypes.
458 ModelTypeSet all_types
= ModelTypeSet::All();
459 for (ModelTypeSet::Iterator it
= all_types
.First(); it
.Good(); it
.Inc()) {
460 dictionary
->Set(ModelTypeToString(it
.Get()), new base::ListValue());
463 for (EntityMap::const_iterator it
= entities_
.begin(); it
!= entities_
.end();
465 FakeServerEntity
* entity
= it
->second
;
466 if (entity
->IsDeleted() || entity
->IsFolder()) {
467 // Tombstones are ignored as they don't represent current data. Folders
468 // are also ignored as current verification infrastructure does not
472 base::ListValue
* list_value
;
473 if (!dictionary
->GetList(ModelTypeToString(entity
->GetModelType()),
475 return scoped_ptr
<base::DictionaryValue
>();
477 // TODO(pvalenzuela): Store more data for each entity so additional
478 // verification can be performed. One example of additional verification
479 // is checking the correctness of the bookmark hierarchy.
480 list_value
->Append(new base::StringValue(entity
->GetName()));
483 return dictionary
.Pass();
486 void FakeServer::InjectEntity(scoped_ptr
<FakeServerEntity
> entity
) {
487 SaveEntity(entity
.release());
490 bool FakeServer::SetNewStoreBirthday(const string
& store_birthday
) {
491 if (store_birthday_
== store_birthday
)
494 store_birthday_
= store_birthday
;
498 void FakeServer::SetAuthenticated() {
499 authenticated_
= true;
502 void FakeServer::SetUnauthenticated() {
503 authenticated_
= false;
506 // TODO(pvalenzuela): comments from Richard: we should look at
507 // mock_connection_manager.cc and take it as a warning. This style of injecting
508 // errors works when there's one or two conditions we care about, but it can
509 // eventually lead to a hairball once we have many different conditions and
511 void FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType
& error_type
) {
512 error_type_
= error_type
;
515 void FakeServer::AddObserver(Observer
* observer
) {
516 observers_
.AddObserver(observer
);
519 void FakeServer::RemoveObserver(Observer
* observer
) {
520 observers_
.RemoveObserver(observer
);
523 } // namespace fake_server