Include cert status in invalid certificate reports
[chromium-blink-merge.git] / sync / test / fake_server / fake_server.cc
blobdbe3105a943cb9d33037b23b04ec4dfc2ae7adca
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.
5 #include "sync/test/fake_server/fake_server.h"
7 #include <algorithm>
8 #include <limits>
9 #include <string>
10 #include <vector>
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"
30 using std::string;
31 using std::vector;
33 using syncer::GetModelType;
34 using syncer::ModelType;
35 using syncer::ModelTypeSet;
37 namespace fake_server {
39 class FakeServerEntity;
41 namespace {
43 // The default store birthday value.
44 static const char kDefaultStoreBirthday[] = "1234567890";
46 // The default keystore key.
47 static const char kDefaultKeystoreKey[] = "1111111111111111";
49 // Properties of the bookmark bar permanent folder.
50 static const char kBookmarkBarFolderServerTag[] = "bookmark_bar";
51 static const char kBookmarkBarFolderName[] = "Bookmark Bar";
53 // Properties of the other bookmarks permanent folder.
54 static const char kOtherBookmarksFolderServerTag[] = "other_bookmarks";
55 static const char kOtherBookmarksFolderName[] = "Other Bookmarks";
57 // Properties of the synced bookmarks permanent folder.
58 static const char kSyncedBookmarksFolderServerTag[] = "synced_bookmarks";
59 static const char kSyncedBookmarksFolderName[] = "Synced Bookmarks";
61 // A filter used during GetUpdates calls to determine what information to
62 // send back to the client. There is a 1:1 correspondence between any given
63 // GetUpdates call and an UpdateSieve instance.
64 class UpdateSieve {
65 public:
66 ~UpdateSieve() { }
68 // Factory method for creating an UpdateSieve.
69 static scoped_ptr<UpdateSieve> Create(
70 const sync_pb::GetUpdatesMessage& get_updates_message);
72 // Sets the progress markers in |get_updates_response| given the progress
73 // markers from the original GetUpdatesMessage and |new_version| (the latest
74 // version in the entries sent back).
75 void UpdateProgressMarkers(
76 int64 new_version,
77 sync_pb::GetUpdatesResponse* get_updates_response) const {
78 ModelTypeToVersionMap::const_iterator it;
79 for (it = request_from_version_.begin(); it != request_from_version_.end();
80 ++it) {
81 sync_pb::DataTypeProgressMarker* new_marker =
82 get_updates_response->add_new_progress_marker();
83 new_marker->set_data_type_id(
84 GetSpecificsFieldNumberFromModelType(it->first));
86 int64 version = std::max(new_version, it->second);
87 new_marker->set_token(base::Int64ToString(version));
91 // Determines whether the server should send an |entity| to the client as
92 // part of a GetUpdatesResponse.
93 bool ClientWantsItem(FakeServerEntity* entity) const {
94 int64 version = entity->GetVersion();
95 if (version <= min_version_) {
96 return false;
97 } else if (entity->IsDeleted()) {
98 return true;
101 ModelTypeToVersionMap::const_iterator it =
102 request_from_version_.find(entity->GetModelType());
104 return it == request_from_version_.end() ? false : it->second < version;
107 // Returns the minimum version seen across all types.
108 int64 GetMinVersion() const {
109 return min_version_;
112 private:
113 typedef std::map<ModelType, int64> ModelTypeToVersionMap;
115 // Creates an UpdateSieve.
116 UpdateSieve(const ModelTypeToVersionMap request_from_version,
117 const int64 min_version)
118 : request_from_version_(request_from_version),
119 min_version_(min_version) { }
121 // Maps data type IDs to the latest version seen for that type.
122 const ModelTypeToVersionMap request_from_version_;
124 // The minimum version seen among all data types.
125 const int min_version_;
128 scoped_ptr<UpdateSieve> UpdateSieve::Create(
129 const sync_pb::GetUpdatesMessage& get_updates_message) {
130 CHECK_GT(get_updates_message.from_progress_marker_size(), 0)
131 << "A GetUpdates request must have at least one progress marker.";
133 UpdateSieve::ModelTypeToVersionMap request_from_version;
134 int64 min_version = std::numeric_limits<int64>::max();
135 for (int i = 0; i < get_updates_message.from_progress_marker_size(); i++) {
136 sync_pb::DataTypeProgressMarker marker =
137 get_updates_message.from_progress_marker(i);
139 int64 version = 0;
140 // Let the version remain zero if there is no token or an empty token (the
141 // first request for this type).
142 if (marker.has_token() && !marker.token().empty()) {
143 bool parsed = base::StringToInt64(marker.token(), &version);
144 CHECK(parsed) << "Unable to parse progress marker token.";
147 ModelType model_type = syncer::GetModelTypeFromSpecificsFieldNumber(
148 marker.data_type_id());
149 request_from_version[model_type] = version;
151 if (version < min_version)
152 min_version = version;
155 return scoped_ptr<UpdateSieve>(
156 new UpdateSieve(request_from_version, min_version));
159 // Returns whether |entity| is deleted or a folder.
160 bool IsDeletedOrFolder(const FakeServerEntity* entity) {
161 DCHECK(entity);
162 return entity->IsDeleted() || entity->IsFolder();
165 } // namespace
167 FakeServer::FakeServer() : version_(0),
168 store_birthday_(kDefaultStoreBirthday),
169 authenticated_(true),
170 error_type_(sync_pb::SyncEnums::SUCCESS),
171 alternate_triggered_errors_(false),
172 request_counter_(0),
173 network_enabled_(true) {
174 keystore_keys_.push_back(kDefaultKeystoreKey);
175 CHECK(CreateDefaultPermanentItems());
178 FakeServer::~FakeServer() {
179 STLDeleteContainerPairSecondPointers(entities_.begin(), entities_.end());
182 bool FakeServer::CreatePermanentBookmarkFolder(const std::string& server_tag,
183 const std::string& name) {
184 FakeServerEntity* entity =
185 PermanentEntity::Create(syncer::BOOKMARKS, server_tag, name,
186 ModelTypeToRootTag(syncer::BOOKMARKS));
187 if (entity == NULL)
188 return false;
190 SaveEntity(entity);
191 return true;
194 bool FakeServer::CreateDefaultPermanentItems() {
195 ModelTypeSet all_types = syncer::ProtocolTypes();
196 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
197 ModelType model_type = it.Get();
198 FakeServerEntity* top_level_entity =
199 PermanentEntity::CreateTopLevel(model_type);
200 if (top_level_entity == NULL) {
201 return false;
203 SaveEntity(top_level_entity);
205 if (model_type == syncer::BOOKMARKS) {
206 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag,
207 kBookmarkBarFolderName))
208 return false;
209 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag,
210 kOtherBookmarksFolderName))
211 return false;
215 return true;
218 void FakeServer::SaveEntity(FakeServerEntity* entity) {
219 delete entities_[entity->GetId()];
220 entity->SetVersion(++version_);
221 entities_[entity->GetId()] = entity;
224 void FakeServer::HandleCommand(const string& request,
225 const HandleCommandCallback& callback) {
226 if (!network_enabled_) {
227 callback.Run(net::ERR_FAILED, net::ERR_FAILED, string());
228 return;
230 request_counter_++;
232 if (!authenticated_) {
233 callback.Run(0, net::HTTP_UNAUTHORIZED, string());
234 return;
237 sync_pb::ClientToServerMessage message;
238 bool parsed = message.ParseFromString(request);
239 CHECK(parsed) << "Unable to parse the ClientToServerMessage.";
241 sync_pb::ClientToServerResponse response_proto;
243 if (message.has_store_birthday() &&
244 message.store_birthday() != store_birthday_) {
245 response_proto.set_error_code(sync_pb::SyncEnums::NOT_MY_BIRTHDAY);
246 } else if (error_type_ != sync_pb::SyncEnums::SUCCESS &&
247 ShouldSendTriggeredError()) {
248 response_proto.set_error_code(error_type_);
249 } else if (triggered_actionable_error_.get() && ShouldSendTriggeredError()) {
250 sync_pb::ClientToServerResponse_Error* error =
251 response_proto.mutable_error();
252 error->CopyFrom(*(triggered_actionable_error_.get()));
253 } else {
254 bool success = false;
255 switch (message.message_contents()) {
256 case sync_pb::ClientToServerMessage::GET_UPDATES:
257 success = HandleGetUpdatesRequest(message.get_updates(),
258 response_proto.mutable_get_updates());
259 break;
260 case sync_pb::ClientToServerMessage::COMMIT:
261 success = HandleCommitRequest(message.commit(),
262 message.invalidator_client_id(),
263 response_proto.mutable_commit());
264 break;
265 default:
266 callback.Run(net::ERR_NOT_IMPLEMENTED, 0, string());;
267 return;
270 if (!success) {
271 // TODO(pvalenzuela): Add logging here so that tests have more info about
272 // the failure.
273 callback.Run(net::ERR_FAILED, 0, string());
274 return;
277 response_proto.set_error_code(sync_pb::SyncEnums::SUCCESS);
280 response_proto.set_store_birthday(store_birthday_);
281 callback.Run(0, net::HTTP_OK, response_proto.SerializeAsString());
284 bool FakeServer::HandleGetUpdatesRequest(
285 const sync_pb::GetUpdatesMessage& get_updates,
286 sync_pb::GetUpdatesResponse* response) {
287 // TODO(pvalenzuela): Implement batching instead of sending all information
288 // at once.
289 response->set_changes_remaining(0);
291 scoped_ptr<UpdateSieve> sieve = UpdateSieve::Create(get_updates);
293 // This folder is called "Synced Bookmarks" by sync and is renamed
294 // "Mobile Bookmarks" by the mobile client UIs.
295 if (get_updates.create_mobile_bookmarks_folder() &&
296 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag,
297 kSyncedBookmarksFolderName)) {
298 return false;
301 bool send_encryption_keys_based_on_nigori = false;
302 int64 max_response_version = 0;
303 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
304 ++it) {
305 FakeServerEntity* entity = it->second;
306 if (sieve->ClientWantsItem(entity)) {
307 sync_pb::SyncEntity* response_entity = response->add_entries();
308 entity->SerializeAsProto(response_entity);
309 max_response_version = std::max(max_response_version,
310 response_entity->version());
312 if (entity->GetModelType() == syncer::NIGORI) {
313 send_encryption_keys_based_on_nigori =
314 response_entity->specifics().nigori().passphrase_type() ==
315 sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE;
320 if (send_encryption_keys_based_on_nigori ||
321 get_updates.need_encryption_key()) {
322 for (vector<string>::iterator it = keystore_keys_.begin();
323 it != keystore_keys_.end(); ++it) {
324 response->add_encryption_keys(*it);
328 sieve->UpdateProgressMarkers(max_response_version, response);
329 return true;
332 string FakeServer::CommitEntity(
333 const sync_pb::SyncEntity& client_entity,
334 sync_pb::CommitResponse_EntryResponse* entry_response,
335 string client_guid,
336 string parent_id) {
337 if (client_entity.version() == 0 && client_entity.deleted()) {
338 return string();
341 FakeServerEntity* entity;
342 if (client_entity.deleted()) {
343 entity = TombstoneEntity::Create(client_entity.id_string());
344 // TODO(pvalenzuela): Change the behavior of DeleteChilden so that it does
345 // not modify server data if it fails.
346 if (!DeleteChildren(client_entity.id_string())) {
347 return string();
349 } else if (GetModelType(client_entity) == syncer::NIGORI) {
350 // NIGORI is the only permanent item type that should be updated by the
351 // client.
352 entity = PermanentEntity::CreateUpdatedNigoriEntity(
353 client_entity,
354 entities_[client_entity.id_string()]);
355 } else if (client_entity.has_client_defined_unique_tag()) {
356 entity = UniqueClientEntity::Create(client_entity);
357 } else {
358 // TODO(pvalenzuela): Validate entity's parent ID.
359 if (entities_.find(client_entity.id_string()) != entities_.end()) {
360 entity = BookmarkEntity::CreateUpdatedVersion(
361 client_entity,
362 entities_[client_entity.id_string()],
363 parent_id);
364 } else {
365 entity = BookmarkEntity::CreateNew(client_entity, parent_id, client_guid);
369 if (entity == NULL) {
370 // TODO(pvalenzuela): Add logging so that it is easier to determine why
371 // creation failed.
372 return string();
375 SaveEntity(entity);
376 BuildEntryResponseForSuccessfulCommit(entry_response, entity);
377 return entity->GetId();
380 void FakeServer::BuildEntryResponseForSuccessfulCommit(
381 sync_pb::CommitResponse_EntryResponse* entry_response,
382 FakeServerEntity* entity) {
383 entry_response->set_response_type(sync_pb::CommitResponse::SUCCESS);
384 entry_response->set_id_string(entity->GetId());
386 if (entity->IsDeleted()) {
387 entry_response->set_version(entity->GetVersion() + 1);
388 } else {
389 entry_response->set_version(entity->GetVersion());
390 entry_response->set_name(entity->GetName());
394 bool FakeServer::IsChild(const string& id, const string& potential_parent_id) {
395 if (entities_.find(id) == entities_.end()) {
396 // We've hit an ID (probably the imaginary root entity) that isn't stored
397 // by the server, so it can't be a child.
398 return false;
399 } else if (entities_[id]->GetParentId() == potential_parent_id) {
400 return true;
401 } else {
402 // Recursively look up the tree.
403 return IsChild(entities_[id]->GetParentId(), potential_parent_id);
407 bool FakeServer::DeleteChildren(const string& id) {
408 vector<string> child_ids;
409 for (EntityMap::iterator it = entities_.begin(); it != entities_.end();
410 ++it) {
411 if (IsChild(it->first, id)) {
412 child_ids.push_back(it->first);
416 for (vector<string>::iterator it = child_ids.begin(); it != child_ids.end();
417 ++it) {
418 FakeServerEntity* tombstone = TombstoneEntity::Create(*it);
419 if (tombstone == NULL) {
420 LOG(WARNING) << "Tombstone creation failed for entity with ID " << *it;
421 return false;
423 SaveEntity(tombstone);
426 return true;
429 bool FakeServer::HandleCommitRequest(
430 const sync_pb::CommitMessage& commit,
431 const std::string& invalidator_client_id,
432 sync_pb::CommitResponse* response) {
433 std::map<string, string> client_to_server_ids;
434 string guid = commit.cache_guid();
435 ModelTypeSet committed_model_types;
437 // TODO(pvalenzuela): Add validation of CommitMessage.entries.
438 ::google::protobuf::RepeatedPtrField<sync_pb::SyncEntity>::const_iterator it;
439 for (it = commit.entries().begin(); it != commit.entries().end(); ++it) {
440 sync_pb::CommitResponse_EntryResponse* entry_response =
441 response->add_entryresponse();
443 sync_pb::SyncEntity client_entity = *it;
444 string parent_id = client_entity.parent_id_string();
445 if (client_to_server_ids.find(parent_id) !=
446 client_to_server_ids.end()) {
447 parent_id = client_to_server_ids[parent_id];
450 string entity_id = CommitEntity(client_entity,
451 entry_response,
452 guid,
453 parent_id);
454 if (entity_id.empty()) {
455 return false;
458 // Record the ID if it was renamed.
459 if (entity_id != client_entity.id_string()) {
460 client_to_server_ids[client_entity.id_string()] = entity_id;
462 FakeServerEntity* entity = entities_[entity_id];
463 committed_model_types.Put(entity->GetModelType());
466 FOR_EACH_OBSERVER(Observer, observers_,
467 OnCommit(invalidator_client_id, committed_model_types));
468 return true;
471 scoped_ptr<base::DictionaryValue> FakeServer::GetEntitiesAsDictionaryValue() {
472 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
474 // Initialize an empty ListValue for all ModelTypes.
475 ModelTypeSet all_types = ModelTypeSet::All();
476 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) {
477 dictionary->Set(ModelTypeToString(it.Get()), new base::ListValue());
480 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
481 ++it) {
482 FakeServerEntity* entity = it->second;
483 if (IsDeletedOrFolder(entity)) {
484 // Tombstones are ignored as they don't represent current data. Folders
485 // are also ignored as current verification infrastructure does not
486 // consider them.
487 continue;
489 base::ListValue* list_value;
490 if (!dictionary->GetList(ModelTypeToString(entity->GetModelType()),
491 &list_value)) {
492 return scoped_ptr<base::DictionaryValue>();
494 // TODO(pvalenzuela): Store more data for each entity so additional
495 // verification can be performed. One example of additional verification
496 // is checking the correctness of the bookmark hierarchy.
497 list_value->Append(new base::StringValue(entity->GetName()));
500 return dictionary.Pass();
503 std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByModelType(
504 ModelType model_type) {
505 std::vector<sync_pb::SyncEntity> sync_entities;
506 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
507 ++it) {
508 FakeServerEntity* entity = it->second;
509 if (!IsDeletedOrFolder(entity) && entity->GetModelType() == model_type) {
510 sync_pb::SyncEntity sync_entity;
511 entity->SerializeAsProto(&sync_entity);
512 sync_entities.push_back(sync_entity);
515 return sync_entities;
518 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) {
519 SaveEntity(entity.release());
522 bool FakeServer::SetNewStoreBirthday(const string& store_birthday) {
523 if (store_birthday_ == store_birthday)
524 return false;
526 store_birthday_ = store_birthday;
527 return true;
530 void FakeServer::SetAuthenticated() {
531 authenticated_ = true;
534 void FakeServer::SetUnauthenticated() {
535 authenticated_ = false;
538 bool FakeServer::TriggerError(const sync_pb::SyncEnums::ErrorType& error_type) {
539 if (triggered_actionable_error_.get()) {
540 DVLOG(1) << "Only one type of error can be triggered at any given time.";
541 return false;
544 error_type_ = error_type;
545 return true;
548 bool FakeServer::TriggerActionableError(
549 const sync_pb::SyncEnums::ErrorType& error_type,
550 const string& description,
551 const string& url,
552 const sync_pb::SyncEnums::Action& action) {
553 if (error_type_ != sync_pb::SyncEnums::SUCCESS) {
554 DVLOG(1) << "Only one type of error can be triggered at any given time.";
555 return false;
558 sync_pb::ClientToServerResponse_Error* error =
559 new sync_pb::ClientToServerResponse_Error();
560 error->set_error_type(error_type);
561 error->set_error_description(description);
562 error->set_url(url);
563 error->set_action(action);
564 triggered_actionable_error_.reset(error);
565 return true;
568 bool FakeServer::EnableAlternatingTriggeredErrors() {
569 if (error_type_ == sync_pb::SyncEnums::SUCCESS &&
570 !triggered_actionable_error_.get()) {
571 DVLOG(1) << "No triggered error set. Alternating can't be enabled.";
572 return false;
575 alternate_triggered_errors_ = true;
576 // Reset the counter so that the the first request yields a triggered error.
577 request_counter_ = 0;
578 return true;
581 bool FakeServer::ShouldSendTriggeredError() const {
582 if (!alternate_triggered_errors_)
583 return true;
585 // Check that the counter is odd so that we trigger an error on the first
586 // request after alternating is enabled.
587 return request_counter_ % 2 != 0;
590 void FakeServer::AddObserver(Observer* observer) {
591 observers_.AddObserver(observer);
594 void FakeServer::RemoveObserver(Observer* observer) {
595 observers_.RemoveObserver(observer);
598 void FakeServer::EnableNetwork() {
599 network_enabled_ = true;
602 void FakeServer::DisableNetwork() {
603 network_enabled_ = false;
606 std::string FakeServer::GetBookmarkBarFolderId() const {
607 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end();
608 ++it) {
609 FakeServerEntity* entity = it->second;
610 if (entity->GetName() == kBookmarkBarFolderName &&
611 entity->IsFolder() &&
612 entity->GetModelType() == syncer::BOOKMARKS) {
613 return entity->GetId();
616 NOTREACHED() << "Bookmark Bar entity not found.";
617 return "";
620 } // namespace fake_server