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 "chrome/browser/sync/about_sync_util.h" 6 7 #include <string> 8 9 #include "base/strings/string16.h" 10 #include "base/strings/stringprintf.h" 11 #include "base/values.h" 12 #include "chrome/browser/sync/profile_sync_service.h" 13 #include "chrome/common/chrome_version_info.h" 14 #include "components/signin/core/browser/signin_manager.h" 15 #include "sync/api/time.h" 16 #include "sync/internal_api/public/util/sync_string_conversions.h" 17 #include "sync/protocol/proto_enum_conversions.h" 18 19 using base::DictionaryValue; 20 using base::ListValue; 21 22 const char kIdentityTitle[] = "Identity"; 23 const char kDetailsKey[] = "details"; 24 25 namespace { 26 27 // Creates a 'section' for display on about:sync, consisting of a title and a 28 // list of fields. Returns a pointer to the new section. Note that 29 // |parent_list|, not the caller, owns the newly added section. 30 base::ListValue* AddSection(base::ListValue* parent_list, 31 const std::string& title) { 32 base::DictionaryValue* section = new base::DictionaryValue(); 33 base::ListValue* section_contents = new base::ListValue(); 34 section->SetString("title", title); 35 section->Set("data", section_contents); 36 section->SetBoolean("is_sensitive", false); 37 parent_list->Append(section); 38 return section_contents; 39 } 40 41 // Same as AddSection, but for data that should be elided when dumped into text 42 // form and posted in a public forum (e.g. unique identifiers). 43 base::ListValue* AddSensitiveSection(base::ListValue* parent_list, 44 const std::string& title) { 45 base::DictionaryValue* section = new base::DictionaryValue(); 46 base::ListValue* section_contents = new base::ListValue(); 47 section->SetString("title", title); 48 section->Set("data", section_contents); 49 section->SetBoolean("is_sensitive", true); 50 parent_list->Append(section); 51 return section_contents; 52 } 53 54 // The following helper classes help manage the about:sync fields which will be 55 // populated in method in ConstructAboutInformation. 56 // 57 // Each instance of one of thse classes indicates a field in about:sync. Each 58 // field will be serialized to a DictionaryValue with entries for 'stat_name', 59 // 'stat_value' and 'is_valid'. 60 61 class StringSyncStat { 62 public: 63 StringSyncStat(base::ListValue* section, const std::string& key); 64 void SetValue(const std::string& value); 65 void SetValue(const base::string16& value); 66 67 private: 68 // Owned by the |section| passed in during construction. 69 base::DictionaryValue* stat_; 70 }; 71 72 StringSyncStat::StringSyncStat(base::ListValue* section, 73 const std::string& key) { 74 stat_ = new base::DictionaryValue(); 75 stat_->SetString("stat_name", key); 76 stat_->SetString("stat_value", "Uninitialized"); 77 stat_->SetBoolean("is_valid", false); 78 section->Append(stat_); 79 } 80 81 void StringSyncStat::SetValue(const std::string& value) { 82 stat_->SetString("stat_value", value); 83 stat_->SetBoolean("is_valid", true); 84 } 85 86 void StringSyncStat::SetValue(const base::string16& value) { 87 stat_->SetString("stat_value", value); 88 stat_->SetBoolean("is_valid", true); 89 } 90 91 class BoolSyncStat { 92 public: 93 BoolSyncStat(base::ListValue* section, const std::string& key); 94 void SetValue(bool value); 95 96 private: 97 // Owned by the |section| passed in during construction. 98 base::DictionaryValue* stat_; 99 }; 100 101 BoolSyncStat::BoolSyncStat(base::ListValue* section, const std::string& key) { 102 stat_ = new base::DictionaryValue(); 103 stat_->SetString("stat_name", key); 104 stat_->SetBoolean("stat_value", false); 105 stat_->SetBoolean("is_valid", false); 106 section->Append(stat_); 107 } 108 109 void BoolSyncStat::SetValue(bool value) { 110 stat_->SetBoolean("stat_value", value); 111 stat_->SetBoolean("is_valid", true); 112 } 113 114 class IntSyncStat { 115 public: 116 IntSyncStat(base::ListValue* section, const std::string& key); 117 void SetValue(int value); 118 119 private: 120 // Owned by the |section| passed in during construction. 121 base::DictionaryValue* stat_; 122 }; 123 124 IntSyncStat::IntSyncStat(base::ListValue* section, const std::string& key) { 125 stat_ = new base::DictionaryValue(); 126 stat_->SetString("stat_name", key); 127 stat_->SetInteger("stat_value", 0); 128 stat_->SetBoolean("is_valid", false); 129 section->Append(stat_); 130 } 131 132 void IntSyncStat::SetValue(int value) { 133 stat_->SetInteger("stat_value", value); 134 stat_->SetBoolean("is_valid", true); 135 } 136 137 // Returns a string describing the chrome version environment. Version format: 138 // <Build Info> <OS> <Version number> (<Last change>)<channel or "-devel"> 139 // If version information is unavailable, returns "invalid." 140 // TODO(zea): this approximately matches MakeUserAgentForSyncApi in 141 // sync_backend_host.cc. Unify the two if possible. 142 std::string GetVersionString() { 143 // Build a version string that matches MakeUserAgentForSyncApi with the 144 // addition of channel info and proper OS names. 145 chrome::VersionInfo chrome_version; 146 if (!chrome_version.is_valid()) 147 return "invalid"; 148 // GetVersionStringModifier returns empty string for stable channel or 149 // unofficial builds, the channel string otherwise. We want to have "-devel" 150 // for unofficial builds only. 151 std::string version_modifier = 152 chrome::VersionInfo::GetVersionStringModifier(); 153 if (version_modifier.empty()) { 154 if (chrome::VersionInfo::GetChannel() != 155 chrome::VersionInfo::CHANNEL_STABLE) { 156 version_modifier = "-devel"; 157 } 158 } else { 159 version_modifier = " " + version_modifier; 160 } 161 return chrome_version.Name() + " " + chrome_version.OSType() + " " + 162 chrome_version.Version() + " (" + chrome_version.LastChange() + ")" + 163 version_modifier; 164 } 165 166 std::string GetTimeStr(base::Time time, const std::string& default_msg) { 167 std::string time_str; 168 if (time.is_null()) 169 time_str = default_msg; 170 else 171 time_str = syncer::GetTimeDebugString(time); 172 return time_str; 173 } 174 175 std::string GetConnectionStatus( 176 const ProfileSyncService::SyncTokenStatus& status) { 177 std::string message; 178 switch (status.connection_status) { 179 case syncer::CONNECTION_NOT_ATTEMPTED: 180 base::StringAppendF(&message, "not attempted"); 181 break; 182 case syncer::CONNECTION_OK: 183 base::StringAppendF( 184 &message, "OK since %s", 185 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 186 break; 187 case syncer::CONNECTION_AUTH_ERROR: 188 base::StringAppendF( 189 &message, "auth error since %s", 190 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 191 break; 192 case syncer::CONNECTION_SERVER_ERROR: 193 base::StringAppendF( 194 &message, "server error since %s", 195 GetTimeStr(status.connection_status_update_time, "n/a").c_str()); 196 break; 197 default: 198 NOTREACHED(); 199 } 200 return message; 201 } 202 203 } // namespace 204 205 namespace sync_ui_util { 206 207 // This function both defines the structure of the message to be returned and 208 // its contents. Most of the message consists of simple fields in about:sync 209 // which are grouped into sections and populated with the help of the SyncStat 210 // classes defined above. 211 scoped_ptr<base::DictionaryValue> ConstructAboutInformation( 212 ProfileSyncService* service) { 213 scoped_ptr<base::DictionaryValue> about_info(new base::DictionaryValue()); 214 215 // 'details': A list of sections. 216 base::ListValue* stats_list = new base::ListValue(); 217 218 // The following lines define the sections and their fields. For each field, 219 // a class is instantiated, which allows us to reference the fields in 220 // 'setter' code later on in this function. 221 base::ListValue* section_summary = AddSection(stats_list, "Summary"); 222 StringSyncStat summary_string(section_summary, "Summary"); 223 224 base::ListValue* section_version = AddSection(stats_list, "Version Info"); 225 StringSyncStat client_version(section_version, "Client Version"); 226 StringSyncStat server_url(section_version, "Server URL"); 227 228 base::ListValue* section_identity = 229 AddSensitiveSection(stats_list, kIdentityTitle); 230 StringSyncStat sync_id(section_identity, "Sync Client ID"); 231 StringSyncStat invalidator_id(section_identity, "Invalidator Client ID"); 232 StringSyncStat username(section_identity, "Username"); 233 234 base::ListValue* section_credentials = AddSection(stats_list, "Credentials"); 235 StringSyncStat request_token_time(section_credentials, "Requested Token"); 236 StringSyncStat receive_token_time(section_credentials, "Received Token"); 237 StringSyncStat token_request_status(section_credentials, 238 "Token Request Status"); 239 StringSyncStat next_token_request(section_credentials, 240 "Next Token Request"); 241 242 base::ListValue* section_local = AddSection(stats_list, "Local State"); 243 StringSyncStat server_connection(section_local, 244 "Server Connection"); 245 StringSyncStat last_synced(section_local, "Last Synced"); 246 BoolSyncStat is_setup_complete(section_local, 247 "Sync First-Time Setup Complete"); 248 StringSyncStat backend_initialization(section_local, 249 "Sync Backend Initialization"); 250 BoolSyncStat is_syncing(section_local, "Syncing"); 251 252 base::ListValue* section_network = AddSection(stats_list, "Network"); 253 BoolSyncStat is_throttled(section_network, "Throttled"); 254 StringSyncStat retry_time(section_network, "Retry time (maybe stale)"); 255 BoolSyncStat are_notifications_enabled(section_network, 256 "Notifications Enabled"); 257 258 base::ListValue* section_encryption = AddSection(stats_list, "Encryption"); 259 BoolSyncStat is_using_explicit_passphrase(section_encryption, 260 "Explicit Passphrase"); 261 BoolSyncStat is_passphrase_required(section_encryption, 262 "Passphrase Required"); 263 BoolSyncStat is_cryptographer_ready(section_encryption, 264 "Cryptographer Ready"); 265 BoolSyncStat has_pending_keys(section_encryption, 266 "Cryptographer Has Pending Keys"); 267 StringSyncStat encrypted_types(section_encryption, "Encrypted Types"); 268 BoolSyncStat has_keystore_key(section_encryption, "Has Keystore Key"); 269 StringSyncStat keystore_migration_time(section_encryption, 270 "Keystore Migration Time"); 271 StringSyncStat passphrase_type(section_encryption, 272 "Passphrase Type"); 273 StringSyncStat passphrase_time(section_encryption, 274 "Passphrase Time"); 275 276 base::ListValue* section_last_session = AddSection( 277 stats_list, "Status from Last Completed Session"); 278 StringSyncStat session_source(section_last_session, "Sync Source"); 279 StringSyncStat get_key_result(section_last_session, "GetKey Step Result"); 280 StringSyncStat download_result(section_last_session, "Download Step Result"); 281 StringSyncStat commit_result(section_last_session, "Commit Step Result"); 282 283 base::ListValue* section_counters = AddSection(stats_list, "Running Totals"); 284 IntSyncStat notifications_received(section_counters, 285 "Notifications Received"); 286 IntSyncStat updates_received(section_counters, "Updates Downloaded"); 287 IntSyncStat tombstone_updates(section_counters, "Tombstone Updates"); 288 IntSyncStat reflected_updates(section_counters, "Reflected Updates"); 289 IntSyncStat successful_commits(section_counters, "Successful Commits"); 290 IntSyncStat conflicts_resolved_local_wins(section_counters, 291 "Conflicts Resolved: Client Wins"); 292 IntSyncStat conflicts_resolved_server_wins(section_counters, 293 "Conflicts Resolved: Server Wins"); 294 295 base::ListValue *section_this_cycle = AddSection(stats_list, 296 "Transient Counters (this cycle)"); 297 IntSyncStat encryption_conflicts(section_this_cycle, "Encryption Conflicts"); 298 IntSyncStat hierarchy_conflicts(section_this_cycle, "Hierarchy Conflicts"); 299 IntSyncStat server_conflicts(section_this_cycle, "Server Conflicts"); 300 IntSyncStat committed_items(section_this_cycle, "Committed Items"); 301 302 base::ListValue* section_that_cycle = AddSection( 303 stats_list, "Transient Counters (last cycle of last completed session)"); 304 IntSyncStat updates_downloaded(section_that_cycle, "Updates Downloaded"); 305 IntSyncStat committed_count(section_that_cycle, "Committed Count"); 306 IntSyncStat entries(section_that_cycle, "Entries"); 307 308 base::ListValue* section_nudge_info = AddSection( 309 stats_list, "Nudge Source Counters"); 310 IntSyncStat nudge_source_notification( 311 section_nudge_info, "Server Invalidations"); 312 IntSyncStat nudge_source_local(section_nudge_info, "Local Changes"); 313 IntSyncStat nudge_source_local_refresh(section_nudge_info, "Local Refreshes"); 314 315 // This list of sections belongs in the 'details' field of the returned 316 // message. 317 about_info->Set(kDetailsKey, stats_list); 318 319 // Populate all the fields we declared above. 320 client_version.SetValue(GetVersionString()); 321 322 if (!service) { 323 summary_string.SetValue("Sync service does not exist"); 324 return about_info.Pass(); 325 } 326 327 syncer::SyncStatus full_status; 328 bool is_status_valid = service->QueryDetailedSyncStatus(&full_status); 329 bool sync_initialized = service->sync_initialized(); 330 const syncer::sessions::SyncSessionSnapshot& snapshot = 331 sync_initialized ? 332 service->GetLastSessionSnapshot() : 333 syncer::sessions::SyncSessionSnapshot(); 334 335 if (is_status_valid) 336 summary_string.SetValue(service->QuerySyncStatusSummaryString()); 337 338 server_url.SetValue(service->sync_service_url().spec()); 339 340 if (is_status_valid && !full_status.sync_id.empty()) 341 sync_id.SetValue(full_status.sync_id); 342 if (is_status_valid && !full_status.invalidator_client_id.empty()) 343 invalidator_id.SetValue(full_status.invalidator_client_id); 344 if (service->signin()) 345 username.SetValue(service->signin()->GetAuthenticatedUsername()); 346 347 const ProfileSyncService::SyncTokenStatus& token_status = 348 service->GetSyncTokenStatus(); 349 server_connection.SetValue(GetConnectionStatus(token_status)); 350 request_token_time.SetValue(GetTimeStr(token_status.token_request_time, 351 "n/a")); 352 receive_token_time.SetValue(GetTimeStr(token_status.token_receive_time, 353 "n/a")); 354 std::string err = token_status.last_get_token_error.error_message(); 355 token_request_status.SetValue(err.empty() ? "OK" : err); 356 next_token_request.SetValue( 357 GetTimeStr(token_status.next_token_request_time, "not scheduled")); 358 359 last_synced.SetValue(service->GetLastSyncedTimeString()); 360 is_setup_complete.SetValue(service->HasSyncSetupCompleted()); 361 backend_initialization.SetValue( 362 service->GetBackendInitializationStateString()); 363 if (is_status_valid) { 364 is_syncing.SetValue(full_status.syncing); 365 retry_time.SetValue(GetTimeStr(full_status.retry_time, 366 "Scheduler is not in backoff or throttled")); 367 } 368 369 if (snapshot.is_initialized()) 370 is_throttled.SetValue(snapshot.is_silenced()); 371 if (is_status_valid) { 372 are_notifications_enabled.SetValue( 373 full_status.notifications_enabled); 374 } 375 376 if (sync_initialized) { 377 is_using_explicit_passphrase.SetValue( 378 service->IsUsingSecondaryPassphrase()); 379 is_passphrase_required.SetValue(service->IsPassphraseRequired()); 380 passphrase_time.SetValue( 381 GetTimeStr(service->GetExplicitPassphraseTime(), "No Passphrase Time")); 382 } 383 if (is_status_valid) { 384 is_cryptographer_ready.SetValue(full_status.cryptographer_ready); 385 has_pending_keys.SetValue(full_status.crypto_has_pending_keys); 386 encrypted_types.SetValue( 387 ModelTypeSetToString(full_status.encrypted_types)); 388 has_keystore_key.SetValue(full_status.has_keystore_key); 389 keystore_migration_time.SetValue( 390 GetTimeStr(full_status.keystore_migration_time, "Not Migrated")); 391 passphrase_type.SetValue( 392 PassphraseTypeToString(full_status.passphrase_type)); 393 } 394 395 if (snapshot.is_initialized()) { 396 if (snapshot.legacy_updates_source() != 397 sync_pb::GetUpdatesCallerInfo::UNKNOWN) { 398 session_source.SetValue( 399 syncer::GetUpdatesSourceString(snapshot.legacy_updates_source())); 400 } 401 get_key_result.SetValue( 402 GetSyncerErrorString( 403 snapshot.model_neutral_state().last_get_key_result)); 404 download_result.SetValue( 405 GetSyncerErrorString( 406 snapshot.model_neutral_state().last_download_updates_result)); 407 commit_result.SetValue( 408 GetSyncerErrorString( 409 snapshot.model_neutral_state().commit_result)); 410 } 411 412 if (is_status_valid) { 413 notifications_received.SetValue(full_status.notifications_received); 414 updates_received.SetValue(full_status.updates_received); 415 tombstone_updates.SetValue(full_status.tombstone_updates_received); 416 reflected_updates.SetValue(full_status.reflected_updates_received); 417 successful_commits.SetValue(full_status.num_commits_total); 418 conflicts_resolved_local_wins.SetValue( 419 full_status.num_local_overwrites_total); 420 conflicts_resolved_server_wins.SetValue( 421 full_status.num_server_overwrites_total); 422 } 423 424 if (is_status_valid) { 425 encryption_conflicts.SetValue(full_status.encryption_conflicts); 426 hierarchy_conflicts.SetValue(full_status.hierarchy_conflicts); 427 server_conflicts.SetValue(full_status.server_conflicts); 428 committed_items.SetValue(full_status.committed_count); 429 } 430 431 if (is_status_valid) { 432 nudge_source_notification.SetValue(full_status.nudge_source_notification); 433 nudge_source_local.SetValue(full_status.nudge_source_local); 434 nudge_source_local_refresh.SetValue(full_status.nudge_source_local_refresh); 435 } 436 437 if (snapshot.is_initialized()) { 438 updates_downloaded.SetValue( 439 snapshot.model_neutral_state().num_updates_downloaded_total); 440 committed_count.SetValue( 441 snapshot.model_neutral_state().num_successful_commits); 442 entries.SetValue(snapshot.num_entries()); 443 } 444 445 // The values set from this point onwards do not belong in the 446 // details list. 447 448 // We don't need to check is_status_valid here. 449 // full_status.sync_protocol_error is exported directly from the 450 // ProfileSyncService, even if the backend doesn't exist. 451 const bool actionable_error_detected = 452 full_status.sync_protocol_error.error_type != syncer::UNKNOWN_ERROR && 453 full_status.sync_protocol_error.error_type != syncer::SYNC_SUCCESS; 454 455 about_info->SetBoolean("actionable_error_detected", 456 actionable_error_detected); 457 458 // NOTE: We won't bother showing any of the following values unless 459 // actionable_error_detected is set. 460 461 base::ListValue* actionable_error = new base::ListValue(); 462 about_info->Set("actionable_error", actionable_error); 463 464 StringSyncStat error_type(actionable_error, "Error Type"); 465 StringSyncStat action(actionable_error, "Action"); 466 StringSyncStat url(actionable_error, "URL"); 467 StringSyncStat description(actionable_error, "Error Description"); 468 469 if (actionable_error_detected) { 470 error_type.SetValue(syncer::GetSyncErrorTypeString( 471 full_status.sync_protocol_error.error_type)); 472 action.SetValue(syncer::GetClientActionString( 473 full_status.sync_protocol_error.action)); 474 url.SetValue(full_status.sync_protocol_error.url); 475 description.SetValue(full_status.sync_protocol_error.error_description); 476 } 477 478 about_info->SetBoolean("unrecoverable_error_detected", 479 service->HasUnrecoverableError()); 480 481 if (service->HasUnrecoverableError()) { 482 tracked_objects::Location loc(service->unrecoverable_error_location()); 483 std::string location_str; 484 loc.Write(true, true, &location_str); 485 std::string unrecoverable_error_message = 486 "Unrecoverable error detected at " + location_str + 487 ": " + service->unrecoverable_error_message(); 488 about_info->SetString("unrecoverable_error_message", 489 unrecoverable_error_message); 490 } 491 492 about_info->Set("type_status", service->GetTypeStatusMap()); 493 494 return about_info.Pass(); 495 } 496 497 } // namespace sync_ui_util 498