• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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