Home | History | Annotate | Download | only in fake_server
      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:
     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).
     63   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.
     81   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.
     96   int64 GetMinVersion() const {
     97     return min_version_;
     98   }
     99 
    100  private:
    101   typedef std::map<ModelType, int64> ModelTypeToVersionMap;
    102 
    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) { }
    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 
    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 
    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 
    157 FakeServer::~FakeServer() {
    158   STLDeleteContainerPairSecondPointers(entities_.begin(), entities_.end());
    159 }
    160 
    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 
    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 
    213 void FakeServer::SaveEntity(FakeServerEntity* entity) {
    214   delete entities_[entity->GetId()];
    215   entity->SetVersion(++version_);
    216   entities_[entity->GetId()] = entity;
    217 }
    218 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    486 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) {
    487   SaveEntity(entity.release());
    488 }
    489 
    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 
    498 void FakeServer::SetAuthenticated() {
    499   authenticated_ = true;
    500 }
    501 
    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.
    511 void FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType& error_type) {
    512   error_type_ = error_type;
    513 }
    514 
    515 void FakeServer::AddObserver(Observer* observer) {
    516   observers_.AddObserver(observer);
    517 }
    518 
    519 void FakeServer::RemoveObserver(Observer* observer) {
    520   observers_.RemoveObserver(observer);
    521 }
    522 
    523 }  // namespace fake_server
    524