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