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
HandleGetEncryptionKeyResponse(const sync_pb::ClientToServerResponse & update_response,syncable::Directory * dir)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
ConvertConfigureSourceToOrigin(sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source)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
ShouldRequestEncryptionKey(SyncSessionContext * context)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
InitDownloadUpdatesContext(SyncSession * session,bool create_mobile_bookmarks_folder,sync_pb::ClientToServerMessage * message)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
InitDownloadUpdatesProgress(ModelTypeSet proto_request_types,UpdateHandlerMap * handler_map,sync_pb::GetUpdatesMessage * get_updates)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.
PartitionProgressMarkersByType(const sync_pb::GetUpdatesResponse & gu_response,ModelTypeSet request_types,TypeToIndexMap * index_map)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.
ProcessUpdateResponseContents(const sync_pb::GetUpdatesResponse & gu_response,ModelTypeSet proto_request_types,UpdateHandlerMap * handler_map,StatusController * status)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
BuildNormalDownloadUpdates(SyncSession * session,bool create_mobile_bookmarks_folder,ModelTypeSet request_types,const sessions::NudgeTracker & nudge_tracker,sync_pb::ClientToServerMessage * client_to_server_message)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
BuildNormalDownloadUpdatesImpl(ModelTypeSet proto_request_types,UpdateHandlerMap * update_handler_map,const sessions::NudgeTracker & nudge_tracker,sync_pb::GetUpdatesMessage * get_updates)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
BuildDownloadUpdatesForConfigure(SyncSession * session,bool create_mobile_bookmarks_folder,sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source,ModelTypeSet request_types,sync_pb::ClientToServerMessage * client_to_server_message)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
BuildDownloadUpdatesForConfigureImpl(ModelTypeSet proto_request_types,UpdateHandlerMap * update_handler_map,sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source,sync_pb::GetUpdatesMessage * get_updates)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
BuildDownloadUpdatesForPoll(SyncSession * session,bool create_mobile_bookmarks_folder,ModelTypeSet request_types,sync_pb::ClientToServerMessage * client_to_server_message)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
BuildDownloadUpdatesForPollImpl(ModelTypeSet proto_request_types,UpdateHandlerMap * update_handler_map,sync_pb::GetUpdatesMessage * get_updates)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
ExecuteDownloadUpdates(ModelTypeSet request_types,SyncSession * session,sync_pb::ClientToServerMessage * msg)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
ProcessResponse(const sync_pb::GetUpdatesResponse & gu_response,ModelTypeSet proto_request_types,UpdateHandlerMap * handler_map,StatusController * status)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
CopyClientDebugInfo(sessions::DebugInfoGetter * debug_info_getter,sync_pb::DebugInfo * debug_info)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