1 // Copyright (c) 2012 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/engine/syncer_proto_util.h" 6 7 #include "base/format_macros.h" 8 #include "base/strings/stringprintf.h" 9 #include "google_apis/google_api_keys.h" 10 #include "sync/engine/net/server_connection_manager.h" 11 #include "sync/engine/syncer.h" 12 #include "sync/engine/syncer_types.h" 13 #include "sync/engine/traffic_logger.h" 14 #include "sync/internal_api/public/base/model_type.h" 15 #include "sync/protocol/sync_enums.pb.h" 16 #include "sync/protocol/sync_protocol_error.h" 17 #include "sync/sessions/sync_session.h" 18 #include "sync/syncable/directory.h" 19 #include "sync/syncable/entry.h" 20 #include "sync/syncable/syncable-inl.h" 21 #include "sync/syncable/syncable_proto_util.h" 22 #include "sync/util/time.h" 23 24 using std::string; 25 using std::stringstream; 26 using sync_pb::ClientToServerMessage; 27 using sync_pb::ClientToServerResponse; 28 29 namespace syncer { 30 31 using sessions::SyncSession; 32 using syncable::BASE_VERSION; 33 using syncable::CTIME; 34 using syncable::ID; 35 using syncable::IS_DEL; 36 using syncable::IS_DIR; 37 using syncable::IS_UNSYNCED; 38 using syncable::MTIME; 39 using syncable::PARENT_ID; 40 41 namespace { 42 43 // Time to backoff syncing after receiving a throttled response. 44 const int kSyncDelayAfterThrottled = 2 * 60 * 60; // 2 hours 45 46 void LogResponseProfilingData(const ClientToServerResponse& response) { 47 if (response.has_profiling_data()) { 48 stringstream response_trace; 49 response_trace << "Server response trace:"; 50 51 if (response.profiling_data().has_user_lookup_time()) { 52 response_trace << " user lookup: " 53 << response.profiling_data().user_lookup_time() << "ms"; 54 } 55 56 if (response.profiling_data().has_meta_data_write_time()) { 57 response_trace << " meta write: " 58 << response.profiling_data().meta_data_write_time() 59 << "ms"; 60 } 61 62 if (response.profiling_data().has_meta_data_read_time()) { 63 response_trace << " meta read: " 64 << response.profiling_data().meta_data_read_time() << "ms"; 65 } 66 67 if (response.profiling_data().has_file_data_write_time()) { 68 response_trace << " file write: " 69 << response.profiling_data().file_data_write_time() 70 << "ms"; 71 } 72 73 if (response.profiling_data().has_file_data_read_time()) { 74 response_trace << " file read: " 75 << response.profiling_data().file_data_read_time() << "ms"; 76 } 77 78 if (response.profiling_data().has_total_request_time()) { 79 response_trace << " total time: " 80 << response.profiling_data().total_request_time() << "ms"; 81 } 82 DVLOG(1) << response_trace.str(); 83 } 84 } 85 86 SyncerError ServerConnectionErrorAsSyncerError( 87 const HttpResponse::ServerConnectionCode server_status) { 88 switch (server_status) { 89 case HttpResponse::CONNECTION_UNAVAILABLE: 90 return NETWORK_CONNECTION_UNAVAILABLE; 91 case HttpResponse::IO_ERROR: 92 return NETWORK_IO_ERROR; 93 case HttpResponse::SYNC_SERVER_ERROR: 94 // FIXME what does this mean? 95 return SYNC_SERVER_ERROR; 96 case HttpResponse::SYNC_AUTH_ERROR: 97 return SYNC_AUTH_ERROR; 98 case HttpResponse::RETRY: 99 return SERVER_RETURN_TRANSIENT_ERROR; 100 case HttpResponse::SERVER_CONNECTION_OK: 101 case HttpResponse::NONE: 102 default: 103 NOTREACHED(); 104 return UNSET; 105 } 106 } 107 108 SyncProtocolErrorType ConvertSyncProtocolErrorTypePBToLocalType( 109 const sync_pb::SyncEnums::ErrorType& error_type) { 110 switch (error_type) { 111 case sync_pb::SyncEnums::SUCCESS: 112 return SYNC_SUCCESS; 113 case sync_pb::SyncEnums::NOT_MY_BIRTHDAY: 114 return NOT_MY_BIRTHDAY; 115 case sync_pb::SyncEnums::THROTTLED: 116 return THROTTLED; 117 case sync_pb::SyncEnums::CLEAR_PENDING: 118 return CLEAR_PENDING; 119 case sync_pb::SyncEnums::TRANSIENT_ERROR: 120 return TRANSIENT_ERROR; 121 case sync_pb::SyncEnums::MIGRATION_DONE: 122 return MIGRATION_DONE; 123 case sync_pb::SyncEnums::DISABLED_BY_ADMIN: 124 return DISABLED_BY_ADMIN; 125 case sync_pb::SyncEnums::UNKNOWN: 126 return UNKNOWN_ERROR; 127 case sync_pb::SyncEnums::USER_NOT_ACTIVATED: 128 case sync_pb::SyncEnums::AUTH_INVALID: 129 case sync_pb::SyncEnums::ACCESS_DENIED: 130 return INVALID_CREDENTIAL; 131 default: 132 NOTREACHED(); 133 return UNKNOWN_ERROR; 134 } 135 } 136 137 ClientAction ConvertClientActionPBToLocalClientAction( 138 const sync_pb::SyncEnums::Action& action) { 139 switch (action) { 140 case sync_pb::SyncEnums::UPGRADE_CLIENT: 141 return UPGRADE_CLIENT; 142 case sync_pb::SyncEnums::CLEAR_USER_DATA_AND_RESYNC: 143 return CLEAR_USER_DATA_AND_RESYNC; 144 case sync_pb::SyncEnums::ENABLE_SYNC_ON_ACCOUNT: 145 return ENABLE_SYNC_ON_ACCOUNT; 146 case sync_pb::SyncEnums::STOP_AND_RESTART_SYNC: 147 return STOP_AND_RESTART_SYNC; 148 case sync_pb::SyncEnums::DISABLE_SYNC_ON_CLIENT: 149 return DISABLE_SYNC_ON_CLIENT; 150 case sync_pb::SyncEnums::UNKNOWN_ACTION: 151 return UNKNOWN_ACTION; 152 default: 153 NOTREACHED(); 154 return UNKNOWN_ACTION; 155 } 156 } 157 158 } // namespace 159 160 ModelTypeSet GetTypesToMigrate(const ClientToServerResponse& response) { 161 ModelTypeSet to_migrate; 162 for (int i = 0; i < response.migrated_data_type_id_size(); i++) { 163 int field_number = response.migrated_data_type_id(i); 164 ModelType model_type = GetModelTypeFromSpecificsFieldNumber(field_number); 165 if (!IsRealDataType(model_type)) { 166 DLOG(WARNING) << "Unknown field number " << field_number; 167 continue; 168 } 169 to_migrate.Put(model_type); 170 } 171 return to_migrate; 172 } 173 174 SyncProtocolError ConvertErrorPBToLocalType( 175 const sync_pb::ClientToServerResponse_Error& error) { 176 SyncProtocolError sync_protocol_error; 177 sync_protocol_error.error_type = ConvertSyncProtocolErrorTypePBToLocalType( 178 error.error_type()); 179 sync_protocol_error.error_description = error.error_description(); 180 sync_protocol_error.url = error.url(); 181 sync_protocol_error.action = ConvertClientActionPBToLocalClientAction( 182 error.action()); 183 184 if (error.error_data_type_ids_size() > 0) { 185 // THROTTLED is currently the only error code that uses |error_data_types|. 186 DCHECK_EQ(error.error_type(), sync_pb::SyncEnums::THROTTLED); 187 for (int i = 0; i < error.error_data_type_ids_size(); ++i) { 188 int field_number = error.error_data_type_ids(i); 189 ModelType model_type = 190 GetModelTypeFromSpecificsFieldNumber(field_number); 191 if (!IsRealDataType(model_type)) { 192 DLOG(WARNING) << "Unknown field number " << field_number; 193 continue; 194 } 195 sync_protocol_error.error_data_types.Put(model_type); 196 } 197 } 198 199 return sync_protocol_error; 200 } 201 202 // static 203 bool SyncerProtoUtil::VerifyResponseBirthday( 204 const ClientToServerResponse& response, 205 syncable::Directory* dir) { 206 207 std::string local_birthday = dir->store_birthday(); 208 209 if (local_birthday.empty()) { 210 if (!response.has_store_birthday()) { 211 LOG(WARNING) << "Expected a birthday on first sync."; 212 return false; 213 } 214 215 DVLOG(1) << "New store birthday: " << response.store_birthday(); 216 dir->set_store_birthday(response.store_birthday()); 217 return true; 218 } 219 220 // Error situation, but we're not stuck. 221 if (!response.has_store_birthday()) { 222 LOG(WARNING) << "No birthday in server response?"; 223 return true; 224 } 225 226 if (response.store_birthday() != local_birthday) { 227 LOG(WARNING) << "Birthday changed, showing syncer stuck"; 228 return false; 229 } 230 231 return true; 232 } 233 234 // static 235 bool SyncerProtoUtil::IsSyncDisabledByAdmin( 236 const sync_pb::ClientToServerResponse& response) { 237 return (response.has_error_code() && 238 response.error_code() == sync_pb::SyncEnums::DISABLED_BY_ADMIN); 239 } 240 241 // static 242 void SyncerProtoUtil::AddRequestBirthday(syncable::Directory* dir, 243 ClientToServerMessage* msg) { 244 if (!dir->store_birthday().empty()) 245 msg->set_store_birthday(dir->store_birthday()); 246 } 247 248 // static 249 void SyncerProtoUtil::AddBagOfChips(syncable::Directory* dir, 250 ClientToServerMessage* msg) { 251 msg->mutable_bag_of_chips()->ParseFromString(dir->bag_of_chips()); 252 } 253 254 // static 255 void SyncerProtoUtil::SetProtocolVersion(ClientToServerMessage* msg) { 256 const int current_version = 257 ClientToServerMessage::default_instance().protocol_version(); 258 msg->set_protocol_version(current_version); 259 } 260 261 // static 262 bool SyncerProtoUtil::PostAndProcessHeaders(ServerConnectionManager* scm, 263 sessions::SyncSession* session, 264 const ClientToServerMessage& msg, 265 ClientToServerResponse* response) { 266 ServerConnectionManager::PostBufferParams params; 267 DCHECK(msg.has_protocol_version()); 268 DCHECK_EQ(msg.protocol_version(), 269 ClientToServerMessage::default_instance().protocol_version()); 270 msg.SerializeToString(¶ms.buffer_in); 271 272 ScopedServerStatusWatcher server_status_watcher(scm, ¶ms.response); 273 // Fills in params.buffer_out and params.response. 274 if (!scm->PostBufferWithCachedAuth(¶ms, &server_status_watcher)) { 275 LOG(WARNING) << "Error posting from syncer:" << params.response; 276 return false; 277 } 278 279 std::string new_token = params.response.update_client_auth_header; 280 if (!new_token.empty()) { 281 SyncEngineEvent event(SyncEngineEvent::UPDATED_TOKEN); 282 event.updated_token = new_token; 283 session->context()->NotifyListeners(event); 284 } 285 286 if (response->ParseFromString(params.buffer_out)) { 287 // TODO(tim): This is an egregious layering violation (bug 35060). 288 switch (response->error_code()) { 289 case sync_pb::SyncEnums::ACCESS_DENIED: 290 case sync_pb::SyncEnums::AUTH_INVALID: 291 case sync_pb::SyncEnums::USER_NOT_ACTIVATED: 292 // Fires on ScopedServerStatusWatcher 293 params.response.server_status = HttpResponse::SYNC_AUTH_ERROR; 294 return false; 295 default: 296 return true; 297 } 298 } 299 300 return false; 301 } 302 303 base::TimeDelta SyncerProtoUtil::GetThrottleDelay( 304 const ClientToServerResponse& response) { 305 base::TimeDelta throttle_delay = 306 base::TimeDelta::FromSeconds(kSyncDelayAfterThrottled); 307 if (response.has_client_command()) { 308 const sync_pb::ClientCommand& command = response.client_command(); 309 if (command.has_throttle_delay_seconds()) { 310 throttle_delay = 311 base::TimeDelta::FromSeconds(command.throttle_delay_seconds()); 312 } 313 } 314 return throttle_delay; 315 } 316 317 namespace { 318 319 // Helper function for an assertion in PostClientToServerMessage. 320 bool IsVeryFirstGetUpdates(const ClientToServerMessage& message) { 321 if (!message.has_get_updates()) 322 return false; 323 DCHECK_LT(0, message.get_updates().from_progress_marker_size()); 324 for (int i = 0; i < message.get_updates().from_progress_marker_size(); ++i) { 325 if (!message.get_updates().from_progress_marker(i).token().empty()) 326 return false; 327 } 328 return true; 329 } 330 331 // TODO(lipalani) : Rename these function names as per the CR for issue 7740067. 332 SyncProtocolError ConvertLegacyErrorCodeToNewError( 333 const sync_pb::SyncEnums::ErrorType& error_type) { 334 SyncProtocolError error; 335 error.error_type = ConvertSyncProtocolErrorTypePBToLocalType(error_type); 336 if (error_type == sync_pb::SyncEnums::CLEAR_PENDING || 337 error_type == sync_pb::SyncEnums::NOT_MY_BIRTHDAY) { 338 error.action = DISABLE_SYNC_ON_CLIENT; 339 } else if (error_type == sync_pb::SyncEnums::DISABLED_BY_ADMIN) { 340 error.action = STOP_SYNC_FOR_DISABLED_ACCOUNT; 341 } // There is no other action we can compute for legacy server. 342 return error; 343 } 344 345 } // namespace 346 347 // static 348 SyncerError SyncerProtoUtil::PostClientToServerMessage( 349 ClientToServerMessage* msg, 350 ClientToServerResponse* response, 351 SyncSession* session) { 352 CHECK(response); 353 DCHECK(!msg->get_updates().has_from_timestamp()); // Deprecated. 354 DCHECK(!msg->get_updates().has_requested_types()); // Deprecated. 355 356 // Add must-have fields. 357 SetProtocolVersion(msg); 358 AddRequestBirthday(session->context()->directory(), msg); 359 DCHECK(msg->has_store_birthday() || IsVeryFirstGetUpdates(*msg)); 360 AddBagOfChips(session->context()->directory(), msg); 361 msg->set_api_key(google_apis::GetAPIKey()); 362 msg->mutable_client_status()->CopyFrom(session->context()->client_status()); 363 msg->set_invalidator_client_id(session->context()->invalidator_client_id()); 364 365 syncable::Directory* dir = session->context()->directory(); 366 367 LogClientToServerMessage(*msg); 368 session->context()->traffic_recorder()->RecordClientToServerMessage(*msg); 369 if (!PostAndProcessHeaders(session->context()->connection_manager(), session, 370 *msg, response)) { 371 // There was an error establishing communication with the server. 372 // We can not proceed beyond this point. 373 const HttpResponse::ServerConnectionCode server_status = 374 session->context()->connection_manager()->server_status(); 375 376 DCHECK_NE(server_status, HttpResponse::NONE); 377 DCHECK_NE(server_status, HttpResponse::SERVER_CONNECTION_OK); 378 379 return ServerConnectionErrorAsSyncerError(server_status); 380 } 381 382 LogClientToServerResponse(*response); 383 session->context()->traffic_recorder()->RecordClientToServerResponse( 384 *response); 385 386 // Persist a bag of chips if it has been sent by the server. 387 PersistBagOfChips(dir, *response); 388 389 SyncProtocolError sync_protocol_error; 390 391 // The DISABLED_BY_ADMIN error overrides other errors sent by the server. 392 if (IsSyncDisabledByAdmin(*response)) { 393 sync_protocol_error.error_type = DISABLED_BY_ADMIN; 394 sync_protocol_error.action = STOP_SYNC_FOR_DISABLED_ACCOUNT; 395 } else if (!VerifyResponseBirthday(*response, dir)) { 396 // If sync isn't disabled, first check for a birthday mismatch error. 397 sync_protocol_error.error_type = NOT_MY_BIRTHDAY; 398 sync_protocol_error.action = DISABLE_SYNC_ON_CLIENT; 399 } else if (response->has_error()) { 400 // This is a new server. Just get the error from the protocol. 401 sync_protocol_error = ConvertErrorPBToLocalType(response->error()); 402 } else { 403 // Legacy server implementation. Compute the error based on |error_code|. 404 sync_protocol_error = ConvertLegacyErrorCodeToNewError( 405 response->error_code()); 406 } 407 408 // Now set the error into the status so the layers above us could read it. 409 sessions::StatusController* status = session->mutable_status_controller(); 410 status->set_sync_protocol_error(sync_protocol_error); 411 412 // Inform the delegate of the error we got. 413 session->delegate()->OnSyncProtocolError(session->TakeSnapshot()); 414 415 // Update our state for any other commands we've received. 416 if (response->has_client_command()) { 417 const sync_pb::ClientCommand& command = response->client_command(); 418 if (command.has_max_commit_batch_size()) { 419 session->context()->set_max_commit_batch_size( 420 command.max_commit_batch_size()); 421 } 422 423 if (command.has_set_sync_long_poll_interval()) { 424 session->delegate()->OnReceivedLongPollIntervalUpdate( 425 base::TimeDelta::FromSeconds(command.set_sync_long_poll_interval())); 426 } 427 428 if (command.has_set_sync_poll_interval()) { 429 session->delegate()->OnReceivedShortPollIntervalUpdate( 430 base::TimeDelta::FromSeconds(command.set_sync_poll_interval())); 431 } 432 433 if (command.has_sessions_commit_delay_seconds()) { 434 session->delegate()->OnReceivedSessionsCommitDelay( 435 base::TimeDelta::FromSeconds( 436 command.sessions_commit_delay_seconds())); 437 } 438 439 if (command.has_client_invalidation_hint_buffer_size()) { 440 session->delegate()->OnReceivedClientInvalidationHintBufferSize( 441 command.client_invalidation_hint_buffer_size()); 442 } 443 } 444 445 // Now do any special handling for the error type and decide on the return 446 // value. 447 switch (sync_protocol_error.error_type) { 448 case UNKNOWN_ERROR: 449 LOG(WARNING) << "Sync protocol out-of-date. The server is using a more " 450 << "recent version."; 451 return SERVER_RETURN_UNKNOWN_ERROR; 452 case SYNC_SUCCESS: 453 LogResponseProfilingData(*response); 454 return SYNCER_OK; 455 case THROTTLED: 456 if (sync_protocol_error.error_data_types.Empty()) { 457 DLOG(WARNING) << "Client fully throttled by syncer."; 458 session->delegate()->OnThrottled(GetThrottleDelay(*response)); 459 } else { 460 DLOG(WARNING) << "Some types throttled by syncer."; 461 session->delegate()->OnTypesThrottled( 462 sync_protocol_error.error_data_types, 463 GetThrottleDelay(*response)); 464 } 465 return SERVER_RETURN_THROTTLED; 466 case TRANSIENT_ERROR: 467 return SERVER_RETURN_TRANSIENT_ERROR; 468 case MIGRATION_DONE: 469 LOG_IF(ERROR, 0 >= response->migrated_data_type_id_size()) 470 << "MIGRATION_DONE but no types specified."; 471 // TODO(akalin): This should be a set union. 472 session->mutable_status_controller()-> 473 set_types_needing_local_migration(GetTypesToMigrate(*response)); 474 return SERVER_RETURN_MIGRATION_DONE; 475 case CLEAR_PENDING: 476 return SERVER_RETURN_CLEAR_PENDING; 477 case NOT_MY_BIRTHDAY: 478 return SERVER_RETURN_NOT_MY_BIRTHDAY; 479 case DISABLED_BY_ADMIN: 480 return SERVER_RETURN_DISABLED_BY_ADMIN; 481 default: 482 NOTREACHED(); 483 return UNSET; 484 } 485 } 486 487 // static 488 bool SyncerProtoUtil::Compare(const syncable::Entry& local_entry, 489 const sync_pb::SyncEntity& server_entry) { 490 const std::string name = NameFromSyncEntity(server_entry); 491 492 CHECK_EQ(local_entry.Get(ID), SyncableIdFromProto(server_entry.id_string())); 493 CHECK_EQ(server_entry.version(), local_entry.Get(BASE_VERSION)); 494 CHECK(!local_entry.Get(IS_UNSYNCED)); 495 496 if (local_entry.Get(IS_DEL) && server_entry.deleted()) 497 return true; 498 if (local_entry.Get(CTIME) != ProtoTimeToTime(server_entry.ctime())) { 499 LOG(WARNING) << "ctime mismatch"; 500 return false; 501 } 502 503 // These checks are somewhat prolix, but they're easier to debug than a big 504 // boolean statement. 505 string client_name = local_entry.Get(syncable::NON_UNIQUE_NAME); 506 if (client_name != name) { 507 LOG(WARNING) << "Client name mismatch"; 508 return false; 509 } 510 if (local_entry.Get(PARENT_ID) != 511 SyncableIdFromProto(server_entry.parent_id_string())) { 512 LOG(WARNING) << "Parent ID mismatch"; 513 return false; 514 } 515 if (local_entry.Get(IS_DIR) != IsFolder(server_entry)) { 516 LOG(WARNING) << "Dir field mismatch"; 517 return false; 518 } 519 if (local_entry.Get(IS_DEL) != server_entry.deleted()) { 520 LOG(WARNING) << "Deletion mismatch"; 521 return false; 522 } 523 if (!local_entry.Get(IS_DIR) && 524 (local_entry.Get(MTIME) != ProtoTimeToTime(server_entry.mtime()))) { 525 LOG(WARNING) << "mtime mismatch"; 526 return false; 527 } 528 529 return true; 530 } 531 532 // static 533 bool SyncerProtoUtil::ShouldMaintainPosition( 534 const sync_pb::SyncEntity& sync_entity) { 535 // Maintain positions for bookmarks that are not server-defined top-level 536 // folders. 537 return GetModelType(sync_entity) == BOOKMARKS 538 && !(sync_entity.folder() && 539 !sync_entity.server_defined_unique_tag().empty()); 540 } 541 542 // static 543 void SyncerProtoUtil::CopyProtoBytesIntoBlob(const std::string& proto_bytes, 544 syncable::Blob* blob) { 545 syncable::Blob proto_blob(proto_bytes.begin(), proto_bytes.end()); 546 blob->swap(proto_blob); 547 } 548 549 // static 550 bool SyncerProtoUtil::ProtoBytesEqualsBlob(const std::string& proto_bytes, 551 const syncable::Blob& blob) { 552 if (proto_bytes.size() != blob.size()) 553 return false; 554 return std::equal(proto_bytes.begin(), proto_bytes.end(), blob.begin()); 555 } 556 557 // static 558 void SyncerProtoUtil::CopyBlobIntoProtoBytes(const syncable::Blob& blob, 559 std::string* proto_bytes) { 560 std::string blob_string(blob.begin(), blob.end()); 561 proto_bytes->swap(blob_string); 562 } 563 564 // static 565 const std::string& SyncerProtoUtil::NameFromSyncEntity( 566 const sync_pb::SyncEntity& entry) { 567 if (entry.has_non_unique_name()) 568 return entry.non_unique_name(); 569 return entry.name(); 570 } 571 572 // static 573 const std::string& SyncerProtoUtil::NameFromCommitEntryResponse( 574 const sync_pb::CommitResponse_EntryResponse& entry) { 575 if (entry.has_non_unique_name()) 576 return entry.non_unique_name(); 577 return entry.name(); 578 } 579 580 // static 581 void SyncerProtoUtil::PersistBagOfChips(syncable::Directory* dir, 582 const sync_pb::ClientToServerResponse& response) { 583 if (!response.has_new_bag_of_chips()) 584 return; 585 std::string bag_of_chips; 586 if (response.new_bag_of_chips().SerializeToString(&bag_of_chips)) 587 dir->set_bag_of_chips(bag_of_chips); 588 } 589 590 std::string SyncerProtoUtil::SyncEntityDebugString( 591 const sync_pb::SyncEntity& entry) { 592 const std::string& mtime_str = 593 GetTimeDebugString(ProtoTimeToTime(entry.mtime())); 594 const std::string& ctime_str = 595 GetTimeDebugString(ProtoTimeToTime(entry.ctime())); 596 return base::StringPrintf( 597 "id: %s, parent_id: %s, " 598 "version: %" PRId64"d, " 599 "mtime: %" PRId64"d (%s), " 600 "ctime: %" PRId64"d (%s), " 601 "name: %s, sync_timestamp: %" PRId64"d, " 602 "%s ", 603 entry.id_string().c_str(), 604 entry.parent_id_string().c_str(), 605 entry.version(), 606 entry.mtime(), mtime_str.c_str(), 607 entry.ctime(), ctime_str.c_str(), 608 entry.name().c_str(), entry.sync_timestamp(), 609 entry.deleted() ? "deleted, ":""); 610 } 611 612 namespace { 613 std::string GetUpdatesResponseString( 614 const sync_pb::GetUpdatesResponse& response) { 615 std::string output; 616 output.append("GetUpdatesResponse:\n"); 617 for (int i = 0; i < response.entries_size(); i++) { 618 output.append(SyncerProtoUtil::SyncEntityDebugString(response.entries(i))); 619 output.append("\n"); 620 } 621 return output; 622 } 623 } // namespace 624 625 std::string SyncerProtoUtil::ClientToServerResponseDebugString( 626 const ClientToServerResponse& response) { 627 // Add more handlers as needed. 628 std::string output; 629 if (response.has_get_updates()) 630 output.append(GetUpdatesResponseString(response.get_updates())); 631 return output; 632 } 633 634 } // namespace syncer 635