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
19namespace syncer {
20
21using sessions::StatusController;
22using sessions::SyncSession;
23using sessions::SyncSessionContext;
24using std::string;
25
26namespace download {
27
28namespace {
29
30typedef std::map<ModelType, size_t> TypeToIndexMap;
31
32SyncerError 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
53sync_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
71bool 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
83void 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
107void 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.
123void 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.
146bool 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
196void 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
219void 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
255void 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
276void 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
297void 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
315void 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
334SyncerError 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
388SyncerError 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
417void 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