1 // Copyright 2013 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/download.h" 6 7 #include <string> 8 9 #include "base/command_line.h" 10 #include "sync/engine/process_updates_util.h" 11 #include "sync/engine/sync_directory_update_handler.h" 12 #include "sync/engine/syncer.h" 13 #include "sync/engine/syncer_proto_util.h" 14 #include "sync/sessions/nudge_tracker.h" 15 #include "sync/syncable/directory.h" 16 #include "sync/syncable/nigori_handler.h" 17 #include "sync/syncable/syncable_read_transaction.h" 18 19 namespace syncer { 20 21 using sessions::StatusController; 22 using sessions::SyncSession; 23 using sessions::SyncSessionContext; 24 using std::string; 25 26 namespace download { 27 28 namespace { 29 30 typedef std::map<ModelType, size_t> TypeToIndexMap; 31 32 SyncerError HandleGetEncryptionKeyResponse( 33 const sync_pb::ClientToServerResponse& update_response, 34 syncable::Directory* dir) { 35 bool success = false; 36 if (update_response.get_updates().encryption_keys_size() == 0) { 37 LOG(ERROR) << "Failed to receive encryption key from server."; 38 return SERVER_RESPONSE_VALIDATION_FAILED; 39 } 40 syncable::ReadTransaction trans(FROM_HERE, dir); 41 syncable::NigoriHandler* nigori_handler = dir->GetNigoriHandler(); 42 success = nigori_handler->SetKeystoreKeys( 43 update_response.get_updates().encryption_keys(), 44 &trans); 45 46 DVLOG(1) << "GetUpdates returned " 47 << update_response.get_updates().encryption_keys_size() 48 << "encryption keys. Nigori keystore key " 49 << (success ? "" : "not ") << "updated."; 50 return (success ? SYNCER_OK : SERVER_RESPONSE_VALIDATION_FAILED); 51 } 52 53 sync_pb::SyncEnums::GetUpdatesOrigin ConvertConfigureSourceToOrigin( 54 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source) { 55 switch (source) { 56 // Configurations: 57 case sync_pb::GetUpdatesCallerInfo::NEWLY_SUPPORTED_DATATYPE: 58 return sync_pb::SyncEnums::NEWLY_SUPPORTED_DATATYPE; 59 case sync_pb::GetUpdatesCallerInfo::MIGRATION: 60 return sync_pb::SyncEnums::MIGRATION; 61 case sync_pb::GetUpdatesCallerInfo::RECONFIGURATION: 62 return sync_pb::SyncEnums::RECONFIGURATION; 63 case sync_pb::GetUpdatesCallerInfo::NEW_CLIENT: 64 return sync_pb::SyncEnums::NEW_CLIENT; 65 default: 66 NOTREACHED(); 67 return sync_pb::SyncEnums::UNKNOWN_ORIGIN; 68 } 69 } 70 71 bool ShouldRequestEncryptionKey( 72 SyncSessionContext* context) { 73 bool need_encryption_key = false; 74 if (context->keystore_encryption_enabled()) { 75 syncable::Directory* dir = context->directory(); 76 syncable::ReadTransaction trans(FROM_HERE, dir); 77 syncable::NigoriHandler* nigori_handler = dir->GetNigoriHandler(); 78 need_encryption_key = nigori_handler->NeedKeystoreKey(&trans); 79 } 80 return need_encryption_key; 81 } 82 83 void InitDownloadUpdatesContext( 84 SyncSession* session, 85 bool create_mobile_bookmarks_folder, 86 sync_pb::ClientToServerMessage* message) { 87 message->set_share(session->context()->account_name()); 88 message->set_message_contents(sync_pb::ClientToServerMessage::GET_UPDATES); 89 90 sync_pb::GetUpdatesMessage* get_updates = message->mutable_get_updates(); 91 92 // We want folders for our associated types, always. If we were to set 93 // this to false, the server would send just the non-container items 94 // (e.g. Bookmark URLs but not their containing folders). 95 get_updates->set_fetch_folders(true); 96 97 get_updates->set_create_mobile_bookmarks_folder( 98 create_mobile_bookmarks_folder); 99 bool need_encryption_key = ShouldRequestEncryptionKey(session->context()); 100 get_updates->set_need_encryption_key(need_encryption_key); 101 102 // Set legacy GetUpdatesMessage.GetUpdatesCallerInfo information. 103 get_updates->mutable_caller_info()->set_notifications_enabled( 104 session->context()->notifications_enabled()); 105 } 106 107 void InitDownloadUpdatesProgress( 108 ModelTypeSet proto_request_types, 109 UpdateHandlerMap* handler_map, 110 sync_pb::GetUpdatesMessage* get_updates) { 111 for (ModelTypeSet::Iterator it = proto_request_types.First(); 112 it.Good(); it.Inc()) { 113 UpdateHandlerMap::iterator handler_it = handler_map->find(it.Get()); 114 DCHECK(handler_it != handler_map->end()); 115 sync_pb::DataTypeProgressMarker* progress_marker = 116 get_updates->add_from_progress_marker(); 117 handler_it->second->GetDownloadProgress(progress_marker); 118 } 119 } 120 121 // Builds a map of ModelTypes to indices to progress markers in the given 122 // |gu_response| message. The map is returned in the |index_map| parameter. 123 void PartitionProgressMarkersByType( 124 const sync_pb::GetUpdatesResponse& gu_response, 125 ModelTypeSet request_types, 126 TypeToIndexMap* index_map) { 127 for (int i = 0; i < gu_response.new_progress_marker_size(); ++i) { 128 int field_number = gu_response.new_progress_marker(i).data_type_id(); 129 ModelType model_type = GetModelTypeFromSpecificsFieldNumber(field_number); 130 if (!IsRealDataType(model_type)) { 131 DLOG(WARNING) << "Unknown field number " << field_number; 132 continue; 133 } 134 if (!request_types.Has(model_type)) { 135 DLOG(WARNING) 136 << "Skipping unexpected progress marker for non-enabled type " 137 << ModelTypeToString(model_type); 138 continue; 139 } 140 index_map->insert(std::make_pair(model_type, i)); 141 } 142 } 143 144 // Examines the contents of the GetUpdates response message and forwards 145 // relevant data to the UpdateHandlers for processing and persisting. 146 bool ProcessUpdateResponseContents( 147 const sync_pb::GetUpdatesResponse& gu_response, 148 ModelTypeSet proto_request_types, 149 UpdateHandlerMap* handler_map, 150 StatusController* status) { 151 TypeSyncEntityMap updates_by_type; 152 PartitionUpdatesByType(gu_response, proto_request_types, &updates_by_type); 153 DCHECK_EQ(proto_request_types.Size(), updates_by_type.size()); 154 155 TypeToIndexMap progress_index_by_type; 156 PartitionProgressMarkersByType(gu_response, 157 proto_request_types, 158 &progress_index_by_type); 159 if (proto_request_types.Size() != progress_index_by_type.size()) { 160 NOTREACHED() << "Missing progress markers in GetUpdates response."; 161 return false; 162 } 163 164 // Iterate over these maps in parallel, processing updates for each type. 165 TypeToIndexMap::iterator progress_marker_iter = 166 progress_index_by_type.begin(); 167 TypeSyncEntityMap::iterator updates_iter = updates_by_type.begin(); 168 for ( ; (progress_marker_iter != progress_index_by_type.end() 169 && updates_iter != updates_by_type.end()); 170 ++progress_marker_iter, ++updates_iter) { 171 DCHECK_EQ(progress_marker_iter->first, updates_iter->first); 172 ModelType type = progress_marker_iter->first; 173 174 UpdateHandlerMap::iterator update_handler_iter = handler_map->find(type); 175 176 if (update_handler_iter != handler_map->end()) { 177 update_handler_iter->second->ProcessGetUpdatesResponse( 178 gu_response.new_progress_marker(progress_marker_iter->second), 179 updates_iter->second, 180 status); 181 } else { 182 DLOG(WARNING) 183 << "Ignoring received updates of a type we can't handle. " 184 << "Type is: " << ModelTypeToString(type); 185 continue; 186 } 187 } 188 DCHECK(progress_marker_iter == progress_index_by_type.end() 189 && updates_iter == updates_by_type.end()); 190 191 return true; 192 } 193 194 } // namespace 195 196 void BuildNormalDownloadUpdates( 197 SyncSession* session, 198 bool create_mobile_bookmarks_folder, 199 ModelTypeSet request_types, 200 const sessions::NudgeTracker& nudge_tracker, 201 sync_pb::ClientToServerMessage* client_to_server_message) { 202 // Request updates for all requested types. 203 DVLOG(1) << "Getting updates for types " 204 << ModelTypeSetToString(request_types); 205 DCHECK(!request_types.Empty()); 206 207 InitDownloadUpdatesContext( 208 session, 209 create_mobile_bookmarks_folder, 210 client_to_server_message); 211 212 BuildNormalDownloadUpdatesImpl( 213 Intersection(request_types, ProtocolTypes()), 214 session->context()->update_handler_map(), 215 nudge_tracker, 216 client_to_server_message->mutable_get_updates()); 217 } 218 219 void BuildNormalDownloadUpdatesImpl( 220 ModelTypeSet proto_request_types, 221 UpdateHandlerMap* update_handler_map, 222 const sessions::NudgeTracker& nudge_tracker, 223 sync_pb::GetUpdatesMessage* get_updates) { 224 DCHECK(!proto_request_types.Empty()); 225 226 InitDownloadUpdatesProgress( 227 proto_request_types, 228 update_handler_map, 229 get_updates); 230 231 // Set legacy GetUpdatesMessage.GetUpdatesCallerInfo information. 232 get_updates->mutable_caller_info()->set_source( 233 nudge_tracker.updates_source()); 234 235 // Set the new and improved version of source, too. 236 get_updates->set_get_updates_origin(sync_pb::SyncEnums::GU_TRIGGER); 237 238 // Fill in the notification hints. 239 for (int i = 0; i < get_updates->from_progress_marker_size(); ++i) { 240 sync_pb::DataTypeProgressMarker* progress_marker = 241 get_updates->mutable_from_progress_marker(i); 242 ModelType type = GetModelTypeFromSpecificsFieldNumber( 243 progress_marker->data_type_id()); 244 245 DCHECK(!nudge_tracker.IsTypeThrottled(type)) 246 << "Throttled types should have been removed from the request_types."; 247 248 nudge_tracker.SetLegacyNotificationHint(type, progress_marker); 249 nudge_tracker.FillProtoMessage( 250 type, 251 progress_marker->mutable_get_update_triggers()); 252 } 253 } 254 255 void BuildDownloadUpdatesForConfigure( 256 SyncSession* session, 257 bool create_mobile_bookmarks_folder, 258 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source, 259 ModelTypeSet request_types, 260 sync_pb::ClientToServerMessage* client_to_server_message) { 261 // Request updates for all enabled types. 262 DVLOG(1) << "Initial download for types " 263 << ModelTypeSetToString(request_types); 264 265 InitDownloadUpdatesContext( 266 session, 267 create_mobile_bookmarks_folder, 268 client_to_server_message); 269 BuildDownloadUpdatesForConfigureImpl( 270 Intersection(request_types, ProtocolTypes()), 271 session->context()->update_handler_map(), 272 source, 273 client_to_server_message->mutable_get_updates()); 274 } 275 276 void BuildDownloadUpdatesForConfigureImpl( 277 ModelTypeSet proto_request_types, 278 UpdateHandlerMap* update_handler_map, 279 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source, 280 sync_pb::GetUpdatesMessage* get_updates) { 281 DCHECK(!proto_request_types.Empty()); 282 283 InitDownloadUpdatesProgress( 284 proto_request_types, 285 update_handler_map, 286 get_updates); 287 288 // Set legacy GetUpdatesMessage.GetUpdatesCallerInfo information. 289 get_updates->mutable_caller_info()->set_source(source); 290 291 // Set the new and improved version of source, too. 292 sync_pb::SyncEnums::GetUpdatesOrigin origin = 293 ConvertConfigureSourceToOrigin(source); 294 get_updates->set_get_updates_origin(origin); 295 } 296 297 void BuildDownloadUpdatesForPoll( 298 SyncSession* session, 299 bool create_mobile_bookmarks_folder, 300 ModelTypeSet request_types, 301 sync_pb::ClientToServerMessage* client_to_server_message) { 302 DVLOG(1) << "Polling for types " 303 << ModelTypeSetToString(request_types); 304 305 InitDownloadUpdatesContext( 306 session, 307 create_mobile_bookmarks_folder, 308 client_to_server_message); 309 BuildDownloadUpdatesForPollImpl( 310 Intersection(request_types, ProtocolTypes()), 311 session->context()->update_handler_map(), 312 client_to_server_message->mutable_get_updates()); 313 } 314 315 void BuildDownloadUpdatesForPollImpl( 316 ModelTypeSet proto_request_types, 317 UpdateHandlerMap* update_handler_map, 318 sync_pb::GetUpdatesMessage* get_updates) { 319 DCHECK(!proto_request_types.Empty()); 320 321 InitDownloadUpdatesProgress( 322 proto_request_types, 323 update_handler_map, 324 get_updates); 325 326 // Set legacy GetUpdatesMessage.GetUpdatesCallerInfo information. 327 get_updates->mutable_caller_info()->set_source( 328 sync_pb::GetUpdatesCallerInfo::PERIODIC); 329 330 // Set the new and improved version of source, too. 331 get_updates->set_get_updates_origin(sync_pb::SyncEnums::PERIODIC); 332 } 333 334 SyncerError ExecuteDownloadUpdates( 335 ModelTypeSet request_types, 336 SyncSession* session, 337 sync_pb::ClientToServerMessage* msg) { 338 sync_pb::ClientToServerResponse update_response; 339 StatusController* status = session->mutable_status_controller(); 340 bool need_encryption_key = ShouldRequestEncryptionKey(session->context()); 341 342 if (session->context()->debug_info_getter()) { 343 sync_pb::DebugInfo* debug_info = msg->mutable_debug_info(); 344 CopyClientDebugInfo(session->context()->debug_info_getter(), debug_info); 345 } 346 347 SyncerError result = SyncerProtoUtil::PostClientToServerMessage( 348 msg, 349 &update_response, 350 session); 351 352 DVLOG(2) << SyncerProtoUtil::ClientToServerResponseDebugString( 353 update_response); 354 355 if (result != SYNCER_OK) { 356 LOG(ERROR) << "PostClientToServerMessage() failed during GetUpdates"; 357 return result; 358 } 359 360 DVLOG(1) << "GetUpdates " 361 << " returned " << update_response.get_updates().entries_size() 362 << " updates and indicated " 363 << update_response.get_updates().changes_remaining() 364 << " updates left on server."; 365 366 if (session->context()->debug_info_getter()) { 367 // Clear debug info now that we have successfully sent it to the server. 368 DVLOG(1) << "Clearing client debug info."; 369 session->context()->debug_info_getter()->ClearDebugInfo(); 370 } 371 372 if (need_encryption_key || 373 update_response.get_updates().encryption_keys_size() > 0) { 374 syncable::Directory* dir = session->context()->directory(); 375 status->set_last_get_key_result( 376 HandleGetEncryptionKeyResponse(update_response, dir)); 377 } 378 379 const ModelTypeSet proto_request_types = 380 Intersection(request_types, ProtocolTypes()); 381 382 return ProcessResponse(update_response.get_updates(), 383 proto_request_types, 384 session->context()->update_handler_map(), 385 status); 386 } 387 388 SyncerError ProcessResponse( 389 const sync_pb::GetUpdatesResponse& gu_response, 390 ModelTypeSet proto_request_types, 391 UpdateHandlerMap* handler_map, 392 StatusController* status) { 393 status->increment_num_updates_downloaded_by(gu_response.entries_size()); 394 395 // The changes remaining field is used to prevent the client from looping. If 396 // that field is being set incorrectly, we're in big trouble. 397 if (!gu_response.has_changes_remaining()) { 398 return SERVER_RESPONSE_VALIDATION_FAILED; 399 } 400 status->set_num_server_changes_remaining(gu_response.changes_remaining()); 401 402 403 if (!ProcessUpdateResponseContents(gu_response, 404 proto_request_types, 405 handler_map, 406 status)) { 407 return SERVER_RESPONSE_VALIDATION_FAILED; 408 } 409 410 if (gu_response.changes_remaining() == 0) { 411 return SYNCER_OK; 412 } else { 413 return SERVER_MORE_TO_DOWNLOAD; 414 } 415 } 416 417 void CopyClientDebugInfo( 418 sessions::DebugInfoGetter* debug_info_getter, 419 sync_pb::DebugInfo* debug_info) { 420 DVLOG(1) << "Copying client debug info to send."; 421 debug_info_getter->GetDebugInfo(debug_info); 422 } 423 424 } // namespace download 425 426 } // namespace syncer 427