OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "sync/test/fake_server/fake_server.h" | 5 #include "sync/test/fake_server/fake_server.h" |
6 | 6 |
7 #include <stdint.h> | 7 #include <stdint.h> |
8 | 8 |
9 #include <algorithm> | 9 #include <algorithm> |
10 #include <limits> | 10 #include <limits> |
| 11 #include <memory> |
11 #include <set> | 12 #include <set> |
12 #include <string> | 13 #include <string> |
13 #include <utility> | 14 #include <utility> |
14 #include <vector> | 15 #include <vector> |
15 | 16 |
16 #include "base/guid.h" | 17 #include "base/guid.h" |
17 #include "base/logging.h" | 18 #include "base/logging.h" |
18 #include "base/memory/scoped_ptr.h" | |
19 #include "base/stl_util.h" | 19 #include "base/stl_util.h" |
20 #include "base/strings/string_number_conversions.h" | 20 #include "base/strings/string_number_conversions.h" |
21 #include "base/strings/string_util.h" | 21 #include "base/strings/string_util.h" |
22 #include "base/strings/stringprintf.h" | 22 #include "base/strings/stringprintf.h" |
23 #include "base/synchronization/lock.h" | 23 #include "base/synchronization/lock.h" |
24 #include "net/base/net_errors.h" | 24 #include "net/base/net_errors.h" |
25 #include "net/http/http_status_code.h" | 25 #include "net/http/http_status_code.h" |
26 #include "sync/internal_api/public/base/model_type.h" | 26 #include "sync/internal_api/public/base/model_type.h" |
27 #include "sync/protocol/sync.pb.h" | 27 #include "sync/protocol/sync.pb.h" |
28 #include "sync/test/fake_server/bookmark_entity.h" | 28 #include "sync/test/fake_server/bookmark_entity.h" |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
60 static const char kSyncedBookmarksFolderName[] = "Synced Bookmarks"; | 60 static const char kSyncedBookmarksFolderName[] = "Synced Bookmarks"; |
61 | 61 |
62 // A filter used during GetUpdates calls to determine what information to | 62 // A filter used during GetUpdates calls to determine what information to |
63 // send back to the client. There is a 1:1 correspondence between any given | 63 // send back to the client. There is a 1:1 correspondence between any given |
64 // GetUpdates call and an UpdateSieve instance. | 64 // GetUpdates call and an UpdateSieve instance. |
65 class UpdateSieve { | 65 class UpdateSieve { |
66 public: | 66 public: |
67 ~UpdateSieve() { } | 67 ~UpdateSieve() { } |
68 | 68 |
69 // Factory method for creating an UpdateSieve. | 69 // Factory method for creating an UpdateSieve. |
70 static scoped_ptr<UpdateSieve> Create( | 70 static std::unique_ptr<UpdateSieve> Create( |
71 const sync_pb::GetUpdatesMessage& get_updates_message); | 71 const sync_pb::GetUpdatesMessage& get_updates_message); |
72 | 72 |
73 // Sets the progress markers in |get_updates_response| given the progress | 73 // Sets the progress markers in |get_updates_response| given the progress |
74 // markers from the original GetUpdatesMessage and |new_version| (the latest | 74 // markers from the original GetUpdatesMessage and |new_version| (the latest |
75 // version in the entries sent back). | 75 // version in the entries sent back). |
76 void UpdateProgressMarkers( | 76 void UpdateProgressMarkers( |
77 int64_t new_version, | 77 int64_t new_version, |
78 sync_pb::GetUpdatesResponse* get_updates_response) const { | 78 sync_pb::GetUpdatesResponse* get_updates_response) const { |
79 ModelTypeToVersionMap::const_iterator it; | 79 ModelTypeToVersionMap::const_iterator it; |
80 for (it = request_from_version_.begin(); it != request_from_version_.end(); | 80 for (it = request_from_version_.begin(); it != request_from_version_.end(); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 : request_from_version_(request_from_version), | 117 : request_from_version_(request_from_version), |
118 min_version_(min_version) {} | 118 min_version_(min_version) {} |
119 | 119 |
120 // Maps data type IDs to the latest version seen for that type. | 120 // Maps data type IDs to the latest version seen for that type. |
121 const ModelTypeToVersionMap request_from_version_; | 121 const ModelTypeToVersionMap request_from_version_; |
122 | 122 |
123 // The minimum version seen among all data types. | 123 // The minimum version seen among all data types. |
124 const int min_version_; | 124 const int min_version_; |
125 }; | 125 }; |
126 | 126 |
127 scoped_ptr<UpdateSieve> UpdateSieve::Create( | 127 std::unique_ptr<UpdateSieve> UpdateSieve::Create( |
128 const sync_pb::GetUpdatesMessage& get_updates_message) { | 128 const sync_pb::GetUpdatesMessage& get_updates_message) { |
129 CHECK_GT(get_updates_message.from_progress_marker_size(), 0) | 129 CHECK_GT(get_updates_message.from_progress_marker_size(), 0) |
130 << "A GetUpdates request must have at least one progress marker."; | 130 << "A GetUpdates request must have at least one progress marker."; |
131 | 131 |
132 UpdateSieve::ModelTypeToVersionMap request_from_version; | 132 UpdateSieve::ModelTypeToVersionMap request_from_version; |
133 int64_t min_version = std::numeric_limits<int64_t>::max(); | 133 int64_t min_version = std::numeric_limits<int64_t>::max(); |
134 for (int i = 0; i < get_updates_message.from_progress_marker_size(); i++) { | 134 for (int i = 0; i < get_updates_message.from_progress_marker_size(); i++) { |
135 sync_pb::DataTypeProgressMarker marker = | 135 sync_pb::DataTypeProgressMarker marker = |
136 get_updates_message.from_progress_marker(i); | 136 get_updates_message.from_progress_marker(i); |
137 | 137 |
138 int64_t version = 0; | 138 int64_t version = 0; |
139 // Let the version remain zero if there is no token or an empty token (the | 139 // Let the version remain zero if there is no token or an empty token (the |
140 // first request for this type). | 140 // first request for this type). |
141 if (marker.has_token() && !marker.token().empty()) { | 141 if (marker.has_token() && !marker.token().empty()) { |
142 bool parsed = base::StringToInt64(marker.token(), &version); | 142 bool parsed = base::StringToInt64(marker.token(), &version); |
143 CHECK(parsed) << "Unable to parse progress marker token."; | 143 CHECK(parsed) << "Unable to parse progress marker token."; |
144 } | 144 } |
145 | 145 |
146 ModelType model_type = syncer::GetModelTypeFromSpecificsFieldNumber( | 146 ModelType model_type = syncer::GetModelTypeFromSpecificsFieldNumber( |
147 marker.data_type_id()); | 147 marker.data_type_id()); |
148 request_from_version[model_type] = version; | 148 request_from_version[model_type] = version; |
149 | 149 |
150 if (version < min_version) | 150 if (version < min_version) |
151 min_version = version; | 151 min_version = version; |
152 } | 152 } |
153 | 153 |
154 return scoped_ptr<UpdateSieve>( | 154 return std::unique_ptr<UpdateSieve>( |
155 new UpdateSieve(request_from_version, min_version)); | 155 new UpdateSieve(request_from_version, min_version)); |
156 } | 156 } |
157 | 157 |
158 // Returns whether |entity| is deleted or permanent. | 158 // Returns whether |entity| is deleted or permanent. |
159 bool IsDeletedOrPermanent(const FakeServerEntity& entity) { | 159 bool IsDeletedOrPermanent(const FakeServerEntity& entity) { |
160 return entity.IsDeleted() || entity.IsPermanent(); | 160 return entity.IsDeleted() || entity.IsPermanent(); |
161 } | 161 } |
162 | 162 |
163 } // namespace | 163 } // namespace |
164 | 164 |
(...skipping 13 matching lines...) Expand all Loading... |
178 void FakeServer::Init() { | 178 void FakeServer::Init() { |
179 keystore_keys_.push_back(kDefaultKeystoreKey); | 179 keystore_keys_.push_back(kDefaultKeystoreKey); |
180 | 180 |
181 const bool create_result = CreateDefaultPermanentItems(); | 181 const bool create_result = CreateDefaultPermanentItems(); |
182 DCHECK(create_result) << "Permanent items were not created successfully."; | 182 DCHECK(create_result) << "Permanent items were not created successfully."; |
183 } | 183 } |
184 | 184 |
185 bool FakeServer::CreatePermanentBookmarkFolder(const std::string& server_tag, | 185 bool FakeServer::CreatePermanentBookmarkFolder(const std::string& server_tag, |
186 const std::string& name) { | 186 const std::string& name) { |
187 DCHECK(thread_checker_.CalledOnValidThread()); | 187 DCHECK(thread_checker_.CalledOnValidThread()); |
188 scoped_ptr<FakeServerEntity> entity = | 188 std::unique_ptr<FakeServerEntity> entity = |
189 PermanentEntity::Create(syncer::BOOKMARKS, server_tag, name, | 189 PermanentEntity::Create(syncer::BOOKMARKS, server_tag, name, |
190 ModelTypeToRootTag(syncer::BOOKMARKS)); | 190 ModelTypeToRootTag(syncer::BOOKMARKS)); |
191 if (!entity) | 191 if (!entity) |
192 return false; | 192 return false; |
193 | 193 |
194 SaveEntity(std::move(entity)); | 194 SaveEntity(std::move(entity)); |
195 return true; | 195 return true; |
196 } | 196 } |
197 | 197 |
198 bool FakeServer::CreateDefaultPermanentItems() { | 198 bool FakeServer::CreateDefaultPermanentItems() { |
199 // Permanent folders are always required for Bookmarks (hierarchical | 199 // Permanent folders are always required for Bookmarks (hierarchical |
200 // structure) and Nigori (data stored in permanent root folder). | 200 // structure) and Nigori (data stored in permanent root folder). |
201 ModelTypeSet permanent_folder_types = | 201 ModelTypeSet permanent_folder_types = |
202 ModelTypeSet(syncer::BOOKMARKS, syncer::NIGORI); | 202 ModelTypeSet(syncer::BOOKMARKS, syncer::NIGORI); |
203 | 203 |
204 for (ModelTypeSet::Iterator it = permanent_folder_types.First(); it.Good(); | 204 for (ModelTypeSet::Iterator it = permanent_folder_types.First(); it.Good(); |
205 it.Inc()) { | 205 it.Inc()) { |
206 ModelType model_type = it.Get(); | 206 ModelType model_type = it.Get(); |
207 | 207 |
208 scoped_ptr<FakeServerEntity> top_level_entity = | 208 std::unique_ptr<FakeServerEntity> top_level_entity = |
209 PermanentEntity::CreateTopLevel(model_type); | 209 PermanentEntity::CreateTopLevel(model_type); |
210 if (!top_level_entity) { | 210 if (!top_level_entity) { |
211 return false; | 211 return false; |
212 } | 212 } |
213 SaveEntity(std::move(top_level_entity)); | 213 SaveEntity(std::move(top_level_entity)); |
214 | 214 |
215 if (model_type == syncer::BOOKMARKS) { | 215 if (model_type == syncer::BOOKMARKS) { |
216 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag, | 216 if (!CreatePermanentBookmarkFolder(kBookmarkBarFolderServerTag, |
217 kBookmarkBarFolderName)) | 217 kBookmarkBarFolderName)) |
218 return false; | 218 return false; |
219 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag, | 219 if (!CreatePermanentBookmarkFolder(kOtherBookmarksFolderServerTag, |
220 kOtherBookmarksFolderName)) | 220 kOtherBookmarksFolderName)) |
221 return false; | 221 return false; |
222 } | 222 } |
223 } | 223 } |
224 | 224 |
225 return true; | 225 return true; |
226 } | 226 } |
227 | 227 |
228 void FakeServer::UpdateEntityVersion(FakeServerEntity* entity) { | 228 void FakeServer::UpdateEntityVersion(FakeServerEntity* entity) { |
229 entity->SetVersion(++version_); | 229 entity->SetVersion(++version_); |
230 } | 230 } |
231 | 231 |
232 void FakeServer::SaveEntity(scoped_ptr<FakeServerEntity> entity) { | 232 void FakeServer::SaveEntity(std::unique_ptr<FakeServerEntity> entity) { |
233 UpdateEntityVersion(entity.get()); | 233 UpdateEntityVersion(entity.get()); |
234 entities_[entity->GetId()] = std::move(entity); | 234 entities_[entity->GetId()] = std::move(entity); |
235 } | 235 } |
236 | 236 |
237 void FakeServer::HandleCommand(const string& request, | 237 void FakeServer::HandleCommand(const string& request, |
238 const base::Closure& completion_closure, | 238 const base::Closure& completion_closure, |
239 int* error_code, | 239 int* error_code, |
240 int* response_code, | 240 int* response_code, |
241 std::string* response) { | 241 std::string* response) { |
242 DCHECK(thread_checker_.CalledOnValidThread()); | 242 DCHECK(thread_checker_.CalledOnValidThread()); |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 return true; | 339 return true; |
340 } | 340 } |
341 | 341 |
342 bool FakeServer::HandleGetUpdatesRequest( | 342 bool FakeServer::HandleGetUpdatesRequest( |
343 const sync_pb::GetUpdatesMessage& get_updates, | 343 const sync_pb::GetUpdatesMessage& get_updates, |
344 sync_pb::GetUpdatesResponse* response) { | 344 sync_pb::GetUpdatesResponse* response) { |
345 // TODO(pvalenzuela): Implement batching instead of sending all information | 345 // TODO(pvalenzuela): Implement batching instead of sending all information |
346 // at once. | 346 // at once. |
347 response->set_changes_remaining(0); | 347 response->set_changes_remaining(0); |
348 | 348 |
349 scoped_ptr<UpdateSieve> sieve = UpdateSieve::Create(get_updates); | 349 std::unique_ptr<UpdateSieve> sieve = UpdateSieve::Create(get_updates); |
350 | 350 |
351 // This folder is called "Synced Bookmarks" by sync and is renamed | 351 // This folder is called "Synced Bookmarks" by sync and is renamed |
352 // "Mobile Bookmarks" by the mobile client UIs. | 352 // "Mobile Bookmarks" by the mobile client UIs. |
353 if (get_updates.create_mobile_bookmarks_folder() && | 353 if (get_updates.create_mobile_bookmarks_folder() && |
354 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag, | 354 !CreatePermanentBookmarkFolder(kSyncedBookmarksFolderServerTag, |
355 kSyncedBookmarksFolderName)) { | 355 kSyncedBookmarksFolderName)) { |
356 return false; | 356 return false; |
357 } | 357 } |
358 | 358 |
359 bool send_encryption_keys_based_on_nigori = false; | 359 bool send_encryption_keys_based_on_nigori = false; |
(...skipping 30 matching lines...) Expand all Loading... |
390 | 390 |
391 string FakeServer::CommitEntity( | 391 string FakeServer::CommitEntity( |
392 const sync_pb::SyncEntity& client_entity, | 392 const sync_pb::SyncEntity& client_entity, |
393 sync_pb::CommitResponse_EntryResponse* entry_response, | 393 sync_pb::CommitResponse_EntryResponse* entry_response, |
394 const string& client_guid, | 394 const string& client_guid, |
395 const string& parent_id) { | 395 const string& parent_id) { |
396 if (client_entity.version() == 0 && client_entity.deleted()) { | 396 if (client_entity.version() == 0 && client_entity.deleted()) { |
397 return string(); | 397 return string(); |
398 } | 398 } |
399 | 399 |
400 scoped_ptr<FakeServerEntity> entity; | 400 std::unique_ptr<FakeServerEntity> entity; |
401 if (client_entity.deleted()) { | 401 if (client_entity.deleted()) { |
402 entity = TombstoneEntity::Create(client_entity.id_string()); | 402 entity = TombstoneEntity::Create(client_entity.id_string()); |
403 DeleteChildren(client_entity.id_string()); | 403 DeleteChildren(client_entity.id_string()); |
404 } else if (GetModelType(client_entity) == syncer::NIGORI) { | 404 } else if (GetModelType(client_entity) == syncer::NIGORI) { |
405 // NIGORI is the only permanent item type that should be updated by the | 405 // NIGORI is the only permanent item type that should be updated by the |
406 // client. | 406 // client. |
407 EntityMap::const_iterator iter = entities_.find(client_entity.id_string()); | 407 EntityMap::const_iterator iter = entities_.find(client_entity.id_string()); |
408 CHECK(iter != entities_.end()); | 408 CHECK(iter != entities_.end()); |
409 entity = PermanentEntity::CreateUpdatedNigoriEntity(client_entity, | 409 entity = PermanentEntity::CreateUpdatedNigoriEntity(client_entity, |
410 *iter->second); | 410 *iter->second); |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
514 EntityMap::const_iterator iter = entities_.find(entity_id); | 514 EntityMap::const_iterator iter = entities_.find(entity_id); |
515 CHECK(iter != entities_.end()); | 515 CHECK(iter != entities_.end()); |
516 committed_model_types.Put(iter->second->GetModelType()); | 516 committed_model_types.Put(iter->second->GetModelType()); |
517 } | 517 } |
518 | 518 |
519 FOR_EACH_OBSERVER(Observer, observers_, | 519 FOR_EACH_OBSERVER(Observer, observers_, |
520 OnCommit(invalidator_client_id, committed_model_types)); | 520 OnCommit(invalidator_client_id, committed_model_types)); |
521 return true; | 521 return true; |
522 } | 522 } |
523 | 523 |
524 scoped_ptr<base::DictionaryValue> FakeServer::GetEntitiesAsDictionaryValue() { | 524 std::unique_ptr<base::DictionaryValue> |
| 525 FakeServer::GetEntitiesAsDictionaryValue() { |
525 DCHECK(thread_checker_.CalledOnValidThread()); | 526 DCHECK(thread_checker_.CalledOnValidThread()); |
526 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue()); | 527 std::unique_ptr<base::DictionaryValue> dictionary( |
| 528 new base::DictionaryValue()); |
527 | 529 |
528 // Initialize an empty ListValue for all ModelTypes. | 530 // Initialize an empty ListValue for all ModelTypes. |
529 ModelTypeSet all_types = ModelTypeSet::All(); | 531 ModelTypeSet all_types = ModelTypeSet::All(); |
530 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) { | 532 for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) { |
531 dictionary->Set(ModelTypeToString(it.Get()), new base::ListValue()); | 533 dictionary->Set(ModelTypeToString(it.Get()), new base::ListValue()); |
532 } | 534 } |
533 | 535 |
534 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); | 536 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); |
535 ++it) { | 537 ++it) { |
536 const FakeServerEntity& entity = *it->second; | 538 const FakeServerEntity& entity = *it->second; |
537 if (IsDeletedOrPermanent(entity)) { | 539 if (IsDeletedOrPermanent(entity)) { |
538 // Tombstones are ignored as they don't represent current data. Folders | 540 // Tombstones are ignored as they don't represent current data. Folders |
539 // are also ignored as current verification infrastructure does not | 541 // are also ignored as current verification infrastructure does not |
540 // consider them. | 542 // consider them. |
541 continue; | 543 continue; |
542 } | 544 } |
543 base::ListValue* list_value; | 545 base::ListValue* list_value; |
544 if (!dictionary->GetList(ModelTypeToString(entity.GetModelType()), | 546 if (!dictionary->GetList(ModelTypeToString(entity.GetModelType()), |
545 &list_value)) { | 547 &list_value)) { |
546 return scoped_ptr<base::DictionaryValue>(); | 548 return std::unique_ptr<base::DictionaryValue>(); |
547 } | 549 } |
548 // TODO(pvalenzuela): Store more data for each entity so additional | 550 // TODO(pvalenzuela): Store more data for each entity so additional |
549 // verification can be performed. One example of additional verification | 551 // verification can be performed. One example of additional verification |
550 // is checking the correctness of the bookmark hierarchy. | 552 // is checking the correctness of the bookmark hierarchy. |
551 list_value->Append(new base::StringValue(entity.GetName())); | 553 list_value->Append(new base::StringValue(entity.GetName())); |
552 } | 554 } |
553 | 555 |
554 return dictionary; | 556 return dictionary; |
555 } | 557 } |
556 | 558 |
557 std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByModelType( | 559 std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByModelType( |
558 ModelType model_type) { | 560 ModelType model_type) { |
559 std::vector<sync_pb::SyncEntity> sync_entities; | 561 std::vector<sync_pb::SyncEntity> sync_entities; |
560 DCHECK(thread_checker_.CalledOnValidThread()); | 562 DCHECK(thread_checker_.CalledOnValidThread()); |
561 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); | 563 for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); |
562 ++it) { | 564 ++it) { |
563 const FakeServerEntity& entity = *it->second; | 565 const FakeServerEntity& entity = *it->second; |
564 if (!IsDeletedOrPermanent(entity) && entity.GetModelType() == model_type) { | 566 if (!IsDeletedOrPermanent(entity) && entity.GetModelType() == model_type) { |
565 sync_pb::SyncEntity sync_entity; | 567 sync_pb::SyncEntity sync_entity; |
566 entity.SerializeAsProto(&sync_entity); | 568 entity.SerializeAsProto(&sync_entity); |
567 sync_entities.push_back(sync_entity); | 569 sync_entities.push_back(sync_entity); |
568 } | 570 } |
569 } | 571 } |
570 return sync_entities; | 572 return sync_entities; |
571 } | 573 } |
572 | 574 |
573 void FakeServer::InjectEntity(scoped_ptr<FakeServerEntity> entity) { | 575 void FakeServer::InjectEntity(std::unique_ptr<FakeServerEntity> entity) { |
574 DCHECK(thread_checker_.CalledOnValidThread()); | 576 DCHECK(thread_checker_.CalledOnValidThread()); |
575 SaveEntity(std::move(entity)); | 577 SaveEntity(std::move(entity)); |
576 } | 578 } |
577 | 579 |
578 bool FakeServer::ModifyEntitySpecifics( | 580 bool FakeServer::ModifyEntitySpecifics( |
579 const std::string& id, | 581 const std::string& id, |
580 const sync_pb::EntitySpecifics& updated_specifics) { | 582 const sync_pb::EntitySpecifics& updated_specifics) { |
581 EntityMap::const_iterator iter = entities_.find(id); | 583 EntityMap::const_iterator iter = entities_.find(id); |
582 if (iter == entities_.end() || | 584 if (iter == entities_.end() || |
583 iter->second->GetModelType() != | 585 iter->second->GetModelType() != |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
725 DCHECK(thread_checker_.CalledOnValidThread()); | 727 DCHECK(thread_checker_.CalledOnValidThread()); |
726 return weak_ptr_factory_.GetWeakPtr(); | 728 return weak_ptr_factory_.GetWeakPtr(); |
727 } | 729 } |
728 | 730 |
729 std::string FakeServer::GetStoreBirthday() const { | 731 std::string FakeServer::GetStoreBirthday() const { |
730 DCHECK(thread_checker_.CalledOnValidThread()); | 732 DCHECK(thread_checker_.CalledOnValidThread()); |
731 return base::Int64ToString(store_birthday_); | 733 return base::Int64ToString(store_birthday_); |
732 } | 734 } |
733 | 735 |
734 } // namespace fake_server | 736 } // namespace fake_server |
OLD | NEW |