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.
4
5 #include "sync/test/fake_server/fake_server.h"
6
7 #include <algorithm>
8 #include <limits>
9 #include <string>
10 #include <vector>
11
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"
29
30 using std::string;
31 using std::vector;
32
33 using syncer::GetModelType;
34 using syncer::ModelType;
35 using syncer::ModelTypeSet;
36
37 // The default store birthday value.
38 static const char kDefaultStoreBirthday[] = "1234567890";
39
40 // The default keystore key.
41 static const char kDefaultKeystoreKey[] = "1111111111111111";
42
43 namespace fake_server {
44
45 class FakeServerEntity;
46
47 namespace {
48
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.
52 class UpdateSieve {
53 public:
~UpdateSieve()54 ~UpdateSieve() { }
55
56 // Factory method for creating an UpdateSieve.
57 static scoped_ptr<UpdateSieve> Create(
58 const sync_pb::GetUpdatesMessage& get_updates_message);
59
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).
UpdateProgressMarkers(int64 new_version,sync_pb::GetUpdatesResponse * get_updates_response) const63 void UpdateProgressMarkers(
64 int64 new_version,
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();
68 ++it) {
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));
73
74 int64 version = std::max(new_version, it->second);
75 new_marker->set_token(base::Int64ToString(version));
76 }
77 }
78
79 // Determines whether the server should send an |entity| to the client as
80 // part of a GetUpdatesResponse.
ClientWantsItem(FakeServerEntity * entity) const81 bool ClientWantsItem(FakeServerEntity* entity) const {
82 int64 version = entity->GetVersion();
83 if (version <= min_version_) {
84 return false;
85 } else if (entity->IsDeleted()) {
86 return true;
87 }
88
89 ModelTypeToVersionMap::const_iterator it =
90 request_from_version_.find(entity->GetModelType());
91
92 return it == request_from_version_.end() ? false : it->second < version;
93 }
94
95 // Returns the minimum version seen across all types.
GetMinVersion() const96 int64 GetMinVersion() const {
97 return min_version_;
98 }
99
100 private:
101 typedef std::map<ModelType, int64> ModelTypeToVersionMap;
102
103 // Creates an UpdateSieve.
UpdateSieve(const ModelTypeToVersionMap request_from_version,const int64 min_version)104 UpdateSieve(const ModelTypeToVersionMap request_from_version,
105 const int64 min_version)
106 : request_from_version_(request_from_version),
107 min_version_(min_version) { }
108
109 // Maps data type IDs to the latest version seen for that type.
110 const ModelTypeToVersionMap request_from_version_;
111
112 // The minimum version seen among all data types.
113 const int min_version_;
114 };
115
Create(const sync_pb::GetUpdatesMessage & get_updates_message)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.";
120
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);
126
127 int64 version = 0;
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.";
133 }
134
135 ModelType model_type = syncer::GetModelTypeFromSpecificsFieldNumber(
136 marker.data_type_id());
137 request_from_version[model_type] = version;
138
139 if (version < min_version)
140 min_version = version;
141 }
142
143 return scoped_ptr<UpdateSieve>(
144 new UpdateSieve(request_from_version, min_version));
145 }
146
147 } // namespace
148
FakeServer()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());
155 }
156
~FakeServer()157 FakeServer::~FakeServer() {
158 STLDeleteContainerPairSecondPointers(entities_.begin(), entities_.end());
159 }
160
CreateDefaultPermanentItems()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) {
168 return false;
169 }
170 SaveEntity(top_level_entity);
171
172 if (model_type == syncer::BOOKMARKS) {
173 FakeServerEntity* bookmark_bar_entity =
174 PermanentEntity::Create(syncer::BOOKMARKS,
175 "bookmark_bar",
176 "Bookmark Bar",
177 ModelTypeToRootTag(syncer::BOOKMARKS));
178 if (bookmark_bar_entity == NULL) {
179 return false;
180 }
181 SaveEntity(bookmark_bar_entity);
182
183 FakeServerEntity* other_bookmarks_entity =
184 PermanentEntity::Create(syncer::BOOKMARKS,
185 "other_bookmarks",
186 "Other Bookmarks",
187 ModelTypeToRootTag(syncer::BOOKMARKS));
188 if (other_bookmarks_entity == NULL) {
189 return false;
190 }
191 SaveEntity(other_bookmarks_entity);
192 }
193 }
194
195 return true;
196 }
197
CreateMobileBookmarksPermanentItem()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,
203 "synced_bookmarks",
204 "Synced Bookmarks",
205 ModelTypeToRootTag(syncer::BOOKMARKS));
206 if (mobile_bookmarks_entity == NULL) {
207 return false;
208 }
209 SaveEntity(mobile_bookmarks_entity);
210 return true;
211 }
212
SaveEntity(FakeServerEntity * entity)213 void FakeServer::SaveEntity(FakeServerEntity* entity) {
214 delete entities_[entity->GetId()];
215 entity->SetVersion(++version_);
216 entities_[entity->GetId()] = entity;
217 }
218
HandleCommand(const string & request,const HandleCommandCallback & callback)219 void FakeServer::HandleCommand(const string& request,
220 const HandleCommandCallback& callback) {
221 if (!authenticated_) {
222 callback.Run(0, net::HTTP_UNAUTHORIZED, string());
223 return;
224 }
225
226 sync_pb::ClientToServerMessage message;
227 bool parsed = message.ParseFromString(request);
228 CHECK(parsed) << "Unable to parse the ClientToServerMessage.";
229
230 sync_pb::SyncEnums_ErrorType error_code;
231 sync_pb::ClientToServerResponse response_proto;
232
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_;
238 } else {
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());
244 break;
245 case sync_pb::ClientToServerMessage::COMMIT:
246 success = HandleCommitRequest(message.commit(),
247 message.invalidator_client_id(),
248 response_proto.mutable_commit());
249 break;
250 default:
251 callback.Run(net::ERR_NOT_IMPLEMENTED, 0, string());;
252 return;
253 }
254
255 if (!success) {
256 // TODO(pvalenzuela): Add logging here so that tests have more info about
257 // the failure.
258 callback.Run(net::ERR_FAILED, 0, string());
259 return;
260 }
261
262 error_code = sync_pb::SyncEnums::SUCCESS;
263 }
264
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());
268 }
269
HandleGetUpdatesRequest(const sync_pb::GetUpdatesMessage & get_updates,sync_pb::GetUpdatesResponse * response)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
274 // at once.
275 response->set_changes_remaining(0);
276
277 scoped_ptr<UpdateSieve> sieve = UpdateSieve::Create(get_updates);
278
279 if (get_updates.create_mobile_bookmarks_folder() &&
280 !CreateMobileBookmarksPermanentItem()) {
281 return false;
282 }
283
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();
287 ++it) {
288 FakeServerEntity* entity = it->second;
289 if (sieve->ClientWantsItem(entity)) {
290 sync_pb::SyncEntity* response_entity = response->add_entries();
291 entity->SerializeAsProto(response_entity);
292 max_response_version = std::max(max_response_version,
293 response_entity->version());
294
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;
299 }
300 }
301 }
302
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);
308 }
309 }
310
311 sieve->UpdateProgressMarkers(max_response_version, response);
312 return true;
313 }
314
CommitEntity(const sync_pb::SyncEntity & client_entity,sync_pb::CommitResponse_EntryResponse * entry_response,string client_guid,string parent_id)315 string FakeServer::CommitEntity(
316 const sync_pb::SyncEntity& client_entity,
317 sync_pb::CommitResponse_EntryResponse* entry_response,
318 string client_guid,
319 string parent_id) {
320 if (client_entity.version() == 0 && client_entity.deleted()) {
321 return string();
322 }
323
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())) {
330 return string();
331 }
332 } else if (GetModelType(client_entity) == syncer::NIGORI) {
333 // NIGORI is the only permanent item type that should be updated by the
334 // client.
335 entity = PermanentEntity::CreateUpdatedNigoriEntity(
336 client_entity,
337 entities_[client_entity.id_string()]);
338 } else if (client_entity.has_client_defined_unique_tag()) {
339 entity = UniqueClientEntity::Create(client_entity);
340 } else {
341 // TODO(pvalenzuela): Validate entity's parent ID.
342 if (entities_.find(client_entity.id_string()) != entities_.end()) {
343 entity = BookmarkEntity::CreateUpdatedVersion(
344 client_entity,
345 entities_[client_entity.id_string()],
346 parent_id);
347 } else {
348 entity = BookmarkEntity::CreateNew(client_entity, parent_id, client_guid);
349 }
350 }
351
352 if (entity == NULL) {
353 // TODO(pvalenzuela): Add logging so that it is easier to determine why
354 // creation failed.
355 return string();
356 }
357
358 SaveEntity(entity);
359 BuildEntryResponseForSuccessfulCommit(entry_response, entity);
360 return entity->GetId();
361 }
362
BuildEntryResponseForSuccessfulCommit(sync_pb::CommitResponse_EntryResponse * entry_response,FakeServerEntity * entity)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());
368
369 if (entity->IsDeleted()) {
370 entry_response->set_version(entity->GetVersion() + 1);
371 } else {
372 entry_response->set_version(entity->GetVersion());
373 entry_response->set_name(entity->GetName());
374 }
375 }
376
IsChild(const string & id,const string & potential_parent_id)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.
381 return false;
382 } else if (entities_[id]->GetParentId() == potential_parent_id) {
383 return true;
384 } else {
385 // Recursively look up the tree.
386 return IsChild(entities_[id]->GetParentId(), potential_parent_id);
387 }
388 }
389
DeleteChildren(const string & id)390 bool FakeServer::DeleteChildren(const string& id) {
391 vector<string> child_ids;
392 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
393 ++it) {
394 if (IsChild(it->first, id)) {
395 child_ids.push_back(it->first);
396 }
397 }
398
399 for (vector<string>::iterator it = child_ids.begin(); it != child_ids.end();
400 ++it) {
401 FakeServerEntity* tombstone = TombstoneEntity::Create(*it);
402 if (tombstone == NULL) {
403 LOG(WARNING) << "Tombstone creation failed for entity with ID " << *it;
404 return false;
405 }
406 SaveEntity(tombstone);
407 }
408
409 return true;
410 }
411
HandleCommitRequest(const sync_pb::CommitMessage & commit,const std::string & invalidator_client_id,sync_pb::CommitResponse * response)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;
419
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();
425
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];
431 }
432
433 string entity_id = CommitEntity(client_entity,
434 entry_response,
435 guid,
436 parent_id);
437 if (entity_id.empty()) {
438 return false;
439 }
440
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;
444 }
445 FakeServerEntity* entity = entities_[entity_id];
446 committed_model_types.Put(entity->GetModelType());
447 }
448
449 FOR_EACH_OBSERVER(Observer, observers_,
450 OnCommit(invalidator_client_id, committed_model_types));
451 return true;
452 }
453
GetEntitiesAsDictionaryValue()454 scoped_ptr<base::DictionaryValue> FakeServer::GetEntitiesAsDictionaryValue() {
455 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
456
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());
461 }
462
463 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
464 ++it) {
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
469 // consider them.
470 continue;
471 }
472 base::ListValue* list_value;
473 if (!dictionary->GetList(ModelTypeToString(entity->GetModelType()),
474 &list_value)) {
475 return scoped_ptr<base::DictionaryValue>();
476 }
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()));
481 }
482
483 return dictionary.Pass();
484 }
485
InjectEntity(scoped_ptr<FakeServerEntity> entity)486 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) {
487 SaveEntity(entity.release());
488 }
489
SetNewStoreBirthday(const string & store_birthday)490 bool FakeServer::SetNewStoreBirthday(const string& store_birthday) {
491 if (store_birthday_ == store_birthday)
492 return false;
493
494 store_birthday_ = store_birthday;
495 return true;
496 }
497
SetAuthenticated()498 void FakeServer::SetAuthenticated() {
499 authenticated_ = true;
500 }
501
SetUnauthenticated()502 void FakeServer::SetUnauthenticated() {
503 authenticated_ = false;
504 }
505
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
510 // triggering logic.
TriggerError(const sync_pb::SyncEnums::ErrorType & error_type)511 void FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType& error_type) {
512 error_type_ = error_type;
513 }
514
AddObserver(Observer * observer)515 void FakeServer::AddObserver(Observer* observer) {
516 observers_.AddObserver(observer);
517 }
518
RemoveObserver(Observer * observer)519 void FakeServer::RemoveObserver(Observer* observer) {
520 observers_.RemoveObserver(observer);
521 }
522
523 } // namespace fake_server
524