1// Copyright 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 "sync/engine/conflict_resolver.h"
6
7#include <list>
8#include <set>
9#include <string>
10
11#include "base/metrics/histogram.h"
12#include "sync/engine/conflict_util.h"
13#include "sync/engine/syncer_util.h"
14#include "sync/sessions/status_controller.h"
15#include "sync/syncable/directory.h"
16#include "sync/syncable/mutable_entry.h"
17#include "sync/syncable/syncable_write_transaction.h"
18#include "sync/util/cryptographer.h"
19
20using std::list;
21using std::set;
22
23namespace syncer {
24
25using sessions::StatusController;
26using syncable::Directory;
27using syncable::Entry;
28using syncable::Id;
29using syncable::MutableEntry;
30using syncable::WriteTransaction;
31
32ConflictResolver::ConflictResolver() {
33}
34
35ConflictResolver::~ConflictResolver() {
36}
37
38void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
39                                             const Id& id,
40                                             const Cryptographer* cryptographer,
41                                             StatusController* status) {
42  MutableEntry entry(trans, syncable::GET_BY_ID, id);
43  // Must be good as the entry won't have been cleaned up.
44  CHECK(entry.good());
45
46  // This function can only resolve simple conflicts.  Simple conflicts have
47  // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set.
48  if (!entry.GetIsUnappliedUpdate() || !entry.GetIsUnsynced()) {
49    // This is very unusual, but it can happen in tests.  We may be able to
50    // assert NOTREACHED() here when those tests are updated.
51    return;
52  }
53
54  if (entry.GetIsDel() && entry.GetServerIsDel()) {
55    // we've both deleted it, so lets just drop the need to commit/update this
56    // entry.
57    entry.PutIsUnsynced(false);
58    entry.PutIsUnappliedUpdate(false);
59    // we've made changes, but they won't help syncing progress.
60    // METRIC simple conflict resolved by merge.
61    return;
62  }
63
64  // This logic determines "client wins" vs. "server wins" strategy picking.
65  // By the time we get to this point, we rely on the following to be true:
66  // a) We can decrypt both the local and server data (else we'd be in
67  //    conflict encryption and not attempting to resolve).
68  // b) All unsynced changes have been re-encrypted with the default key (
69  //    occurs either in AttemptToUpdateEntry, SetEncryptionPassphrase,
70  //    SetDecryptionPassphrase, or RefreshEncryption).
71  // c) Base_server_specifics having a valid datatype means that we received
72  //    an undecryptable update that only changed specifics, and since then have
73  //    not received any further non-specifics-only or decryptable updates.
74  // d) If the server_specifics match specifics, server_specifics are
75  //    encrypted with the default key, and all other visible properties match,
76  //    then we can safely ignore the local changes as redundant.
77  // e) Otherwise if the base_server_specifics match the server_specifics, no
78  //    functional change must have been made server-side (else
79  //    base_server_specifics would have been cleared), and we can therefore
80  //    safely ignore the server changes as redundant.
81  // f) Otherwise, it's in general safer to ignore local changes, with the
82  //    exception of deletion conflicts (choose to undelete) and conflicts
83  //    where the non_unique_name or parent don't match.
84  if (!entry.GetServerIsDel()) {
85    // TODO(nick): The current logic is arbitrary; instead, it ought to be made
86    // consistent with the ModelAssociator behavior for a datatype.  It would
87    // be nice if we could route this back to ModelAssociator code to pick one
88    // of three options: CLIENT, SERVER, or MERGE.  Some datatypes (autofill)
89    // are easily mergeable.
90    // See http://crbug.com/77339.
91    bool name_matches = entry.GetNonUniqueName() ==
92        entry.GetServerNonUniqueName();
93    bool parent_matches = entry.GetParentId() == entry.GetServerParentId();
94    bool entry_deleted = entry.GetIsDel();
95    // The position check might fail spuriously if one of the positions was
96    // based on a legacy random suffix, rather than a deterministic one based on
97    // originator_cache_guid and originator_item_id.  If an item is being
98    // modified regularly, it shouldn't take long for the suffix and position to
99    // be updated, so such false failures shouldn't be a problem for long.
100    //
101    // Lucky for us, it's OK to be wrong here.  The position_matches check is
102    // allowed to return false negatives, as long as it returns no false
103    // positives.
104    bool position_matches = parent_matches &&
105         entry.GetServerUniquePosition().Equals(entry.GetUniquePosition());
106    const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics();
107    const sync_pb::EntitySpecifics& server_specifics =
108        entry.GetServerSpecifics();
109    const sync_pb::EntitySpecifics& base_server_specifics =
110        entry.GetBaseServerSpecifics();
111    std::string decrypted_specifics, decrypted_server_specifics;
112    bool specifics_match = false;
113    bool server_encrypted_with_default_key = false;
114    if (specifics.has_encrypted()) {
115      DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
116      decrypted_specifics = cryptographer->DecryptToString(
117          specifics.encrypted());
118    } else {
119      decrypted_specifics = specifics.SerializeAsString();
120    }
121    if (server_specifics.has_encrypted()) {
122      server_encrypted_with_default_key =
123          cryptographer->CanDecryptUsingDefaultKey(
124              server_specifics.encrypted());
125      decrypted_server_specifics = cryptographer->DecryptToString(
126          server_specifics.encrypted());
127    } else {
128      decrypted_server_specifics = server_specifics.SerializeAsString();
129    }
130    if (decrypted_server_specifics == decrypted_specifics &&
131        server_encrypted_with_default_key == specifics.has_encrypted()) {
132      specifics_match = true;
133    }
134    bool base_server_specifics_match = false;
135    if (server_specifics.has_encrypted() &&
136        IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
137      std::string decrypted_base_server_specifics;
138      if (!base_server_specifics.has_encrypted()) {
139        decrypted_base_server_specifics =
140            base_server_specifics.SerializeAsString();
141      } else {
142        decrypted_base_server_specifics = cryptographer->DecryptToString(
143            base_server_specifics.encrypted());
144      }
145      if (decrypted_server_specifics == decrypted_base_server_specifics)
146          base_server_specifics_match = true;
147    }
148
149    if (!entry_deleted && name_matches && parent_matches && specifics_match &&
150        position_matches) {
151      DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
152               << "changes for: " << entry;
153      conflict_util::IgnoreConflict(&entry);
154      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
155                                CHANGES_MATCH,
156                                CONFLICT_RESOLUTION_SIZE);
157    } else if (base_server_specifics_match) {
158      DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
159               << " changes for: " << entry;
160      status->increment_num_server_overwrites();
161      conflict_util::OverwriteServerChanges(&entry);
162      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
163                                IGNORE_ENCRYPTION,
164                                CONFLICT_RESOLUTION_SIZE);
165    } else if (entry_deleted || !name_matches || !parent_matches) {
166      // NOTE: The update application logic assumes that conflict resolution
167      // will never result in changes to the local hierarchy.  The entry_deleted
168      // and !parent_matches cases here are critical to maintaining that
169      // assumption.
170      conflict_util::OverwriteServerChanges(&entry);
171      status->increment_num_server_overwrites();
172      DVLOG(1) << "Resolving simple conflict, overwriting server changes "
173               << "for: " << entry;
174      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
175                                OVERWRITE_SERVER,
176                                CONFLICT_RESOLUTION_SIZE);
177    } else {
178      DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
179               << entry;
180      conflict_util::IgnoreLocalChanges(&entry);
181      status->increment_num_local_overwrites();
182      UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
183                                OVERWRITE_LOCAL,
184                                CONFLICT_RESOLUTION_SIZE);
185    }
186    // Now that we've resolved the conflict, clear the prev server
187    // specifics.
188    entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics());
189  } else {  // SERVER_IS_DEL is true
190    if (entry.GetIsDir()) {
191      Directory::Metahandles children;
192      trans->directory()->GetChildHandlesById(trans,
193                                              entry.GetId(),
194                                              &children);
195      // If a server deleted folder has local contents it should be a hierarchy
196      // conflict.  Hierarchy conflicts should not be processed by this
197      // function.
198      DCHECK(children.empty());
199    }
200
201    // The entry is deleted on the server but still exists locally.
202    // We undelete it by overwriting the server's tombstone with the local
203    // data.
204    conflict_util::OverwriteServerChanges(&entry);
205    status->increment_num_server_overwrites();
206    DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
207             << entry;
208    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
209                              UNDELETE,
210                              CONFLICT_RESOLUTION_SIZE);
211  }
212}
213
214void ConflictResolver::ResolveConflicts(
215    syncable::WriteTransaction* trans,
216    const Cryptographer* cryptographer,
217    const std::set<syncable::Id>& simple_conflict_ids,
218    sessions::StatusController* status) {
219  // Iterate over simple conflict items.
220  set<Id>::const_iterator it;
221  for (it = simple_conflict_ids.begin();
222       it != simple_conflict_ids.end();
223       ++it) {
224    // We don't resolve conflicts for control types here.
225    Entry conflicting_node(trans, syncable::GET_BY_ID, *it);
226    CHECK(conflicting_node.good());
227    if (IsControlType(
228        GetModelTypeFromSpecifics(conflicting_node.GetSpecifics()))) {
229      continue;
230    }
231
232    ProcessSimpleConflict(trans, *it, cryptographer, status);
233  }
234  return;
235}
236
237}  // namespace syncer
238