• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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