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