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 mininum 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 response_entity->CopyFrom(*(entity->SerializeAsProto()));
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 if (entities_.find(client_entity.id_string()) != entities_.end()) {
340 entity = UniqueClientEntity::CreateUpdatedVersion(
341 client_entity,
342 entities_[client_entity.id_string()]);
343 } else {
344 entity = UniqueClientEntity::CreateNew(client_entity);
345 }
346 } else {
347 // TODO(pvalenzuela): Validate entity's parent ID.
348 if (entities_.find(client_entity.id_string()) != entities_.end()) {
349 entity = BookmarkEntity::CreateUpdatedVersion(
350 client_entity,
351 entities_[client_entity.id_string()],
352 parent_id);
353 } else {
354 entity = BookmarkEntity::CreateNew(client_entity, parent_id, client_guid);
355 }
356 }
357
358 if (entity == NULL) {
359 // TODO(pvalenzuela): Add logging so that it is easier to determine why
360 // creation failed.
361 return string();
362 }
363
364 SaveEntity(entity);
365 BuildEntryResponseForSuccessfulCommit(entry_response, entity);
366 return entity->GetId();
367 }
368
BuildEntryResponseForSuccessfulCommit(sync_pb::CommitResponse_EntryResponse * entry_response,FakeServerEntity * entity)369 void FakeServer::BuildEntryResponseForSuccessfulCommit(
370 sync_pb::CommitResponse_EntryResponse* entry_response,
371 FakeServerEntity* entity) {
372 entry_response->set_response_type(sync_pb::CommitResponse::SUCCESS);
373 entry_response->set_id_string(entity->GetId());
374
375 if (entity->IsDeleted()) {
376 entry_response->set_version(entity->GetVersion() + 1);
377 } else {
378 entry_response->set_version(entity->GetVersion());
379 entry_response->set_name(entity->GetName());
380 }
381 }
382
IsChild(const string & id,const string & potential_parent_id)383 bool FakeServer::IsChild(const string& id, const string& potential_parent_id) {
384 if (entities_.find(id) == entities_.end()) {
385 // We've hit an ID (probably the imaginary root entity) that isn't stored
386 // by the server, so it can't be a child.
387 return false;
388 } else if (entities_[id]->GetParentId() == potential_parent_id) {
389 return true;
390 } else {
391 // Recursively look up the tree.
392 return IsChild(entities_[id]->GetParentId(), potential_parent_id);
393 }
394 }
395
DeleteChildren(const string & id)396 bool FakeServer::DeleteChildren(const string& id) {
397 vector<string> child_ids;
398 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
399 ++it) {
400 if (IsChild(it->first, id)) {
401 child_ids.push_back(it->first);
402 }
403 }
404
405 for (vector<string>::iterator it = child_ids.begin(); it != child_ids.end();
406 ++it) {
407 FakeServerEntity* tombstone = TombstoneEntity::Create(*it);
408 if (tombstone == NULL) {
409 LOG(WARNING) << "Tombstone creation failed for entity with ID " << *it;
410 return false;
411 }
412 SaveEntity(tombstone);
413 }
414
415 return true;
416 }
417
HandleCommitRequest(const sync_pb::CommitMessage & commit,const std::string & invalidator_client_id,sync_pb::CommitResponse * response)418 bool FakeServer::HandleCommitRequest(
419 const sync_pb::CommitMessage& commit,
420 const std::string& invalidator_client_id,
421 sync_pb::CommitResponse* response) {
422 std::map<string, string> client_to_server_ids;
423 string guid = commit.cache_guid();
424 ModelTypeSet committed_model_types;
425
426 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
427 ::google::protobuf::RepeatedPtrField<sync_pb::SyncEntity>::const_iterator it;
428 for (it = commit.entries().begin(); it != commit.entries().end(); ++it) {
429 sync_pb::CommitResponse_EntryResponse* entry_response =
430 response->add_entryresponse();
431
432 sync_pb::SyncEntity client_entity = *it;
433 string parent_id = client_entity.parent_id_string();
434 if (client_to_server_ids.find(parent_id) !=
435 client_to_server_ids.end()) {
436 parent_id = client_to_server_ids[parent_id];
437 }
438
439 string entity_id = CommitEntity(client_entity,
440 entry_response,
441 guid,
442 parent_id);
443 if (entity_id.empty()) {
444 return false;
445 }
446
447 // Record the ID if it was renamed.
448 if (entity_id != client_entity.id_string()) {
449 client_to_server_ids[client_entity.id_string()] = entity_id;
450 }
451 FakeServerEntity* entity = entities_[entity_id];
452 committed_model_types.Put(entity->GetModelType());
453 }
454
455 FOR_EACH_OBSERVER(Observer, observers_,
456 OnCommit(invalidator_client_id, committed_model_types));
457 return true;
458 }
459
GetEntitiesAsDictionaryValue()460 scoped_ptr<base::DictionaryValue> FakeServer::GetEntitiesAsDictionaryValue() {
461 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
462
463 // Initialize an empty ListValue for all ModelTypes.
464 ModelTypeSet all_types = ModelTypeSet::All();
465 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
466 dictionary->Set(ModelTypeToString(it.Get()), new base::ListValue());
467 }
468
469 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
470 ++it) {
471 FakeServerEntity* entity = it->second;
472 if (entity->IsDeleted() || entity->IsFolder()) {
473 // Tombstones are ignored as they don't represent current data. Folders
474 // are also ignored as current verification infrastructure does not
475 // consider them.
476 continue;
477 }
478 base::ListValue* list_value;
479 if (!dictionary->GetList(ModelTypeToString(entity->GetModelType()),
480 &list_value)) {
481 return scoped_ptr<base::DictionaryValue>();
482 }
483 // TODO(pvalenzuela): Store more data for each entity so additional
484 // verification can be performed. One example of additional verification
485 // is checking the correctness of the bookmark hierarchy.
486 list_value->Append(new base::StringValue(entity->GetName()));
487 }
488
489 return dictionary.Pass();
490 }
491
InjectEntity(scoped_ptr<FakeServerEntity> entity)492 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) {
493 SaveEntity(entity.release());
494 }
495
SetNewStoreBirthday(const string & store_birthday)496 bool FakeServer::SetNewStoreBirthday(const string& store_birthday) {
497 if (store_birthday_ == store_birthday)
498 return false;
499
500 store_birthday_ = store_birthday;
501 return true;
502 }
503
SetAuthenticated()504 void FakeServer::SetAuthenticated() {
505 authenticated_ = true;
506 }
507
SetUnauthenticated()508 void FakeServer::SetUnauthenticated() {
509 authenticated_ = false;
510 }
511
512 // TODO(pvalenzuela): comments from Richard: we should look at
513 // mock_connection_manager.cc and take it as a warning. This style of injecting
514 // errors works when there's one or two conditions we care about, but it can
515 // eventually lead to a hairball once we have many different conditions and
516 // triggering logic.
TriggerError(const sync_pb::SyncEnums::ErrorType & error_type)517 void FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType& error_type) {
518 error_type_ = error_type;
519 }
520
AddObserver(Observer * observer)521 void FakeServer::AddObserver(Observer* observer) {
522 observers_.AddObserver(observer);
523 }
524
RemoveObserver(Observer * observer)525 void FakeServer::RemoveObserver(Observer* observer) {
526 observers_.RemoveObserver(observer);
527 }
528
529 } // namespace fake_server
530