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